Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions runtime/internal/runtime/z_slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,23 @@ func SliceCopy(dst Slice, data unsafe.Pointer, num int, etSize int) int {
}

func MakeSlice(len, cap int, etSize int, hasPointers bool) Slice {
return MakeSliceAligned(len, cap, etSize, 1, hasPointers)
mem := makeSliceMemory(len, cap, etSize)
// This entry point requires only natural alignment. Call the raw allocator
// directly so LLVM can inline it without the dynamic alignment dispatcher.
var data unsafe.Pointer
if hasPointers {
data = AllocZ(mem)
} else {
data = AllocAtomicZ(mem)
}
return Slice{data, len, cap}
}

func MakeSliceAligned(len, cap, etSize, etAlign int, hasPointers bool) Slice {
return Slice{allocZTyped(makeSliceMemory(len, cap, etSize), uintptr(etAlign), hasPointers), len, cap}
}

func makeSliceMemory(len, cap, etSize int) uintptr {
mem, overflow := math.MulUintptr(uintptr(etSize), uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(uintptr(etSize), uintptr(len))
Expand All @@ -209,7 +222,7 @@ func MakeSliceAligned(len, cap, etSize, etAlign int, hasPointers bool) Slice {
}
panicmakeslicecap()
}
return Slice{allocZTyped(mem, uintptr(etAlign), hasPointers), len, cap}
return mem
}

// MakeSliceBuffer uses caller-owned aligned storage for a valid small make.
Expand Down
82 changes: 82 additions & 0 deletions runtime/internal/test/natural_slice_alloc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//go:build llgo

package test

import (
"testing"
"unsafe"

rt "github.com/xgo-dev/llgo/runtime/internal/runtime"
)

func TestNaturalMakeSlice(t *testing.T) {
for _, hasPointers := range []bool{false, true} {
for _, tc := range []struct{ length, capacity, size int }{
{0, 0, 1}, {0, 9, 1}, {7, 17, 1}, {3, 9, 8}, {7, 17, 0},
} {
for _, aligned := range []bool{false, true} {
var result rt.Slice
if aligned {
result = rt.MakeSliceAligned(tc.length, tc.capacity, tc.size, 1, hasPointers)
} else {
result = rt.MakeSlice(tc.length, tc.capacity, tc.size, hasPointers)
}
view := *(*[]byte)(unsafe.Pointer(&result))
data := unsafe.SliceData(view)
if len(view) != tc.length || cap(view) != tc.capacity || data == nil {
t.Fatalf("pointers=%v aligned=%v case=%+v: len=%d cap=%d data=%p", hasPointers, aligned, tc, len(view), cap(view), data)
}
for _, b := range unsafe.Slice(data, tc.capacity*tc.size) {
if b != 0 {
t.Fatalf("pointers=%v aligned=%v case=%+v: nonzero capacity", hasPointers, aligned, tc)
}
}
}
}
maxInt := int(^uint(0) >> 1)
for _, tc := range []struct {
length, capacity, size int
want string
}{
{-1, -1, 1, "runtime error: makeslice: len out of range"},
{2, 1, 1, "runtime error: makeslice: cap out of range"},
{0, -1, 1, "runtime error: makeslice: cap out of range"},
{maxInt, maxInt, 2, "runtime error: makeslice: len out of range"},
{1, maxInt, 2, "runtime error: makeslice: cap out of range"},
} {
for _, aligned := range []bool{false, true} {
func() {
defer func() {
got := recover()
err, ok := got.(error)
if !ok || err.Error() != tc.want {
t.Errorf("pointers=%v aligned=%v case=%+v: panic=%v", hasPointers, aligned, tc, got)
}
}()
if aligned {
rt.MakeSliceAligned(tc.length, tc.capacity, tc.size, 1, hasPointers)
} else {
rt.MakeSlice(tc.length, tc.capacity, tc.size, hasPointers)
}
}()
}
}
}
}

var naturalMakeSliceBenchmarkSink rt.Slice

func BenchmarkNaturalMakeSlice(b *testing.B) {
for _, tc := range []struct {
name string
size int
}{
{"small", 4}, {"medium", 64}, {"page", 4096},
} {
b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
naturalMakeSliceBenchmarkSink = rt.MakeSlice(tc.size, tc.size, 1, false)
}
})
}
}
4 changes: 4 additions & 0 deletions runtime/internal/test/typed_alloc_noscan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func TestTypedAllocationCollectorKinds(t *testing.T) {
rstr.Grow(100)
rnew := reflect.New(reflect.TypeOf(scalarObject{}))
rptr := reflect.New(reflect.TypeOf(struct{ S string }{}))
naturalBytes := rt.MakeSlice(3, 7, 1, false)
naturalPointers := rt.MakeSlice(3, 7, int(unsafe.Sizeof(unsafe.Pointer(nil))), true)
// Small slice buffers must preserve the allocation kind when capacity
// exceeds the caller's buffer or no buffer is available.
var buffer [4]byte
Expand All @@ -50,6 +52,8 @@ func TestTypedAllocationCollectorKinds(t *testing.T) {
ptr unsafe.Pointer
noscan bool
}{
{"natural bytes", unsafe.Pointer(unsafe.SliceData(*(*[]byte)(unsafe.Pointer(&naturalBytes)))), true},
{"natural pointers", unsafe.Pointer(unsafe.SliceData(*(*[]unsafe.Pointer)(unsafe.Pointer(&naturalPointers)))), false},
{"make bytes", unsafe.Pointer(unsafe.SliceData(bytes)), true},
{"grow runes", unsafe.Pointer(unsafe.SliceData(runes)), true},
{"literal floats", unsafe.Pointer(unsafe.SliceData(floats)), true},
Expand Down
Loading