diff --git a/arena/arena.go b/arena/arena.go index a842390..cbdcd10 100644 --- a/arena/arena.go +++ b/arena/arena.go @@ -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. diff --git a/arena/arena_test.go b/arena/arena_test.go index 1dfaaed..9b8a7aa 100644 --- a/arena/arena_test.go +++ b/arena/arena_test.go @@ -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) + }) +} diff --git a/mem/mem.go b/mem/mem.go index cbb3041..7a2aea3 100644 --- a/mem/mem.go +++ b/mem/mem.go @@ -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 } diff --git a/mem/mem_test.go b/mem/mem_test.go index 1d22501..2c3f2cd 100644 --- a/mem/mem_test.go +++ b/mem/mem_test.go @@ -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)