add arena.ExtendSlice; add tests

This commit is contained in:
Judah Caruso 2026-02-17 16:32:43 -07:00
parent 5a76937255
commit 5579115eb4
4 changed files with 88 additions and 4 deletions

View file

@ -1,6 +1,7 @@
package arena
import (
"fmt"
"unsafe"
"git.brut.systems/judah/xx/mem"
@ -36,6 +37,30 @@ func MakeSlice[T any](arena Arena, len, cap int) []T {
return unsafe.Slice((*T)(ptr), cap)[:len]
}
// ExtendSlice returns a slice pointing to the same underlying memory
// but with an increased capacity.
//
// Note: If extending the slice results in non-contiguous memory, ExtendSlice will panic.
func ExtendSlice[T any](arena Arena, slice []T, newcap int) []T {
if cap(slice) >= newcap {
return slice
}
count := newcap - cap(slice)
ptr, err := arena(ACTION_ALLOC, mem.Sizeof[T]()*uintptr(count), mem.Alignof[T](), nil)
if err != nil {
panic(err)
}
base := uintptr(unsafe.Pointer(unsafe.SliceData(slice)))
expected := base + uintptr(cap(slice)*int(mem.Sizeof[T]()))
if uintptr(ptr) != expected {
panic(fmt.Sprintf("extendslice: extension of %d was non-contiguous (%d vs %d)", count, uintptr(ptr), expected))
}
return unsafe.Slice((*T)(unsafe.SliceData(slice)), newcap)[:len(slice)]
}
// Reset restores an Arena to its initial state.
//
// Note: Accessing memory returned by an Arena after calling Reset is unsafe and may result in a fault.

View file

@ -28,3 +28,20 @@ func TestMakeSlice(t *testing.T) {
testx.Expect(t, p != &s[0], "p = %p, expected %p", p, &s[0])
}
func TestExtendSlice(t *testing.T) {
a := arena.Linear(1024 * mem.Kilobyte)
defer arena.Reset(a)
s := arena.MakeSlice[int](a, 0, 5)
testx.Expect(t, cap(s) == 5)
s = arena.ExtendSlice(a, s, cap(s)+2)
testx.Expect(t, cap(s) == 7)
testx.ShouldPanic(t, func() {
// cause a non-contiguous slice extension (which will panic)
_ = arena.New[int](a)
s = arena.ExtendSlice(a, s, cap(s)*2)
})
}

View file

@ -72,10 +72,8 @@ func Copy(dst, src unsafe.Pointer, size uintptr) unsafe.Pointer {
//
// Returns dst.
func Clear(dst unsafe.Pointer, value byte, count uintptr) unsafe.Pointer {
b := (*byte)(dst)
for range count {
*b = value
b = (*byte)(unsafe.Add(unsafe.Pointer(b), 1))
for i := range count {
*(*byte)(unsafe.Add(unsafe.Pointer(dst), i)) = value
}
return dst
}

View file

@ -27,6 +27,50 @@ func TestBitCast(t *testing.T) {
}
}
func TestMemoryPrimitives(t *testing.T) {
t.Run("copy", func(t *testing.T) {
{ // non-overlapping
a := []int{1, 2, 3, 4, 5, 6}
b := make([]int, 3)
mem.Copy(unsafe.Pointer(&b[0]), unsafe.Pointer(&a[0]), uintptr(len(b))*unsafe.Sizeof(int(0)))
for i := range len(b) {
testx.Expect(t, a[i] == b[i], "%d != %d [%d]", a[i], b[i], i)
}
}
{ // overlapping
a := []int{1, 2, 3, 4, 5}
b := &a[0]
mem.Copy(unsafe.Pointer(b), unsafe.Pointer(&a[0]), uintptr(len(a))*unsafe.Sizeof(int(0)))
for i := range len(a) {
testx.Expect(t, a[i] == i+1, "[%d] %d != %d", i, a[i], i+1)
}
}
})
t.Run("clear", func(t *testing.T) {
{ // zero
v := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
mem.Clear(unsafe.Pointer(&v[0]), 0, uintptr(len(v))*unsafe.Sizeof(int(0)))
for i := range len(v) {
testx.Expect(t, v[i] == 0, "[%d] %d != 0", i, v[i])
}
}
{ // fill
v := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
mem.Clear(unsafe.Pointer(&v[0]), 0xFF, uintptr(len(v))*unsafe.Sizeof(int(0)))
for i := range len(v) {
testx.Expect(t, v[i] == 0xFF, "[%d] %d != 0xFF", i, v[i])
}
}
})
}
func TestAllocationPrimitives(t *testing.T) {
t.Run("reserve, unreserve", func(t *testing.T) {
data, err := mem.Reserve(1 * mem.Gigabyte)