338 lines
8.1 KiB
Go
338 lines
8.1 KiB
Go
//go:build !llgo
|
|
// +build !llgo
|
|
|
|
package abi
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"go/types"
|
|
)
|
|
|
|
const (
|
|
MAXKEYSIZE = 128
|
|
MAXELEMSIZE = 128
|
|
BUCKETSIZE = 8
|
|
)
|
|
|
|
type mockSizes struct {
|
|
alignof func(types.Type) int64
|
|
sizeof func(types.Type) int64
|
|
}
|
|
|
|
func (m mockSizes) Alignof(t types.Type) int64 { return m.alignof(t) }
|
|
func (m mockSizes) Sizeof(t types.Type) int64 { return m.sizeof(t) }
|
|
func (m mockSizes) Offsetsof(f []*types.Var) []int64 {
|
|
// Mock implementation for testing alignment checks
|
|
return []int64{0, 8, 16} // Typical offsets for bucket fields
|
|
}
|
|
|
|
// isPointer reports whether t is a pointer type.
|
|
func isPointer(t types.Type) (ok bool) {
|
|
_, ok = t.Underlying().(*types.Pointer)
|
|
return
|
|
}
|
|
|
|
// MapTypeFlags computes the flags for the given map type.
|
|
func MapTypeFlags(t *types.Map, sizes types.Sizes) (flags int) {
|
|
if sizes.Sizeof(t.Key()) > MAXKEYSIZE {
|
|
flags |= 1 // indirect key
|
|
}
|
|
if sizes.Sizeof(t.Elem()) > MAXELEMSIZE {
|
|
flags |= 2 // indirect value
|
|
}
|
|
return
|
|
}
|
|
|
|
// MapBucketType returns the bucket type for a map.
|
|
func MapBucketType(t *types.Map, sizes types.Sizes) *types.Struct {
|
|
keytype := t.Key()
|
|
elemtype := t.Elem()
|
|
bucket := types.NewStruct([]*types.Var{}, nil)
|
|
|
|
if !types.Comparable(keytype) {
|
|
log.Fatalf("unsupported map key type for %v", t)
|
|
}
|
|
if BUCKETSIZE < 8 {
|
|
log.Fatalf("bucket size %d too small for proper alignment %d", BUCKETSIZE, 8)
|
|
}
|
|
if uint8(sizes.Alignof(keytype)) > BUCKETSIZE {
|
|
log.Fatalf("key align too big for %v", t)
|
|
}
|
|
if uint8(sizes.Alignof(elemtype)) > BUCKETSIZE {
|
|
log.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", sizes.Alignof(elemtype), t, BUCKETSIZE)
|
|
}
|
|
if sizes.Alignof(keytype) > MAXKEYSIZE {
|
|
log.Fatalf("key align too big for %v", t)
|
|
}
|
|
if sizes.Alignof(elemtype) > MAXELEMSIZE {
|
|
log.Fatalf("elem align too big for %v", t)
|
|
}
|
|
if sizes.Alignof(keytype) > MAXKEYSIZE && !isPointer(keytype) {
|
|
log.Fatalf("key indirect incorrect for %v", t)
|
|
}
|
|
if sizes.Alignof(elemtype) > MAXELEMSIZE && !isPointer(elemtype) {
|
|
log.Fatalf("elem indirect incorrect for %v", t)
|
|
}
|
|
if sizes.Sizeof(keytype)%sizes.Alignof(keytype) != 0 {
|
|
log.Fatalf("key size not a multiple of key align for %v", t)
|
|
}
|
|
if sizes.Sizeof(elemtype)%sizes.Alignof(elemtype) != 0 {
|
|
log.Fatalf("elem size not a multiple of elem align for %v", t)
|
|
}
|
|
if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(keytype)) != 0 {
|
|
log.Fatalf("bucket align not multiple of key align %v", t)
|
|
}
|
|
if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(elemtype)) != 0 {
|
|
log.Fatalf("bucket align not multiple of elem align %v", t)
|
|
}
|
|
offs := sizes.Offsetsof(nil)
|
|
if len(offs) >= 3 && offs[1]%sizes.Alignof(keytype) != 0 {
|
|
log.Fatalf("bad alignment of keys in bmap for %v", t)
|
|
}
|
|
if len(offs) >= 3 && offs[2]%sizes.Alignof(elemtype) != 0 {
|
|
log.Fatalf("bad alignment of elems in bmap for %v", t)
|
|
}
|
|
|
|
return bucket
|
|
}
|
|
|
|
// logger interface for testing
|
|
type logger interface {
|
|
Fatalf(format string, args ...interface{})
|
|
}
|
|
|
|
var log logger = &defaultLogger{}
|
|
|
|
type defaultLogger struct{}
|
|
|
|
func (l *defaultLogger) Fatalf(format string, args ...interface{}) {
|
|
panic(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
type testLogger struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func (l *testLogger) Fatalf(format string, args ...interface{}) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
panic(msg)
|
|
}
|
|
|
|
func TestIsPointer(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
typ types.Type
|
|
want bool
|
|
}{
|
|
{
|
|
name: "pointer type",
|
|
typ: types.NewPointer(types.Typ[types.Int]),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "non-pointer type",
|
|
typ: types.Typ[types.Int],
|
|
want: false,
|
|
},
|
|
{
|
|
name: "pointer to struct",
|
|
typ: types.NewPointer(types.NewStruct([]*types.Var{}, nil)),
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isPointer(tt.typ); got != tt.want {
|
|
t.Errorf("isPointer() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMapTypeFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
key types.Type
|
|
elem types.Type
|
|
sizeFn func(types.Type) int64
|
|
alignFn func(types.Type) int64
|
|
wantFlag int
|
|
}{
|
|
{
|
|
name: "small key and elem",
|
|
key: types.Typ[types.Int],
|
|
elem: types.Typ[types.Int],
|
|
sizeFn: func(t types.Type) int64 {
|
|
return 8 // 64-bit integers
|
|
},
|
|
alignFn: func(t types.Type) int64 {
|
|
return 8
|
|
},
|
|
wantFlag: 0,
|
|
},
|
|
{
|
|
name: "large key",
|
|
key: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
|
|
elem: types.Typ[types.Int],
|
|
sizeFn: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Array); ok {
|
|
return 160
|
|
}
|
|
return 8
|
|
},
|
|
alignFn: func(t types.Type) int64 {
|
|
return 8
|
|
},
|
|
wantFlag: 1, // indirect key
|
|
},
|
|
{
|
|
name: "large elem",
|
|
key: types.Typ[types.Int],
|
|
elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
|
|
sizeFn: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Array); ok {
|
|
return 160
|
|
}
|
|
return 8
|
|
},
|
|
alignFn: func(t types.Type) int64 {
|
|
return 8
|
|
},
|
|
wantFlag: 2, // indirect elem
|
|
},
|
|
{
|
|
name: "large key and elem",
|
|
key: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
|
|
elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
|
|
sizeFn: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Array); ok {
|
|
return 160
|
|
}
|
|
return 8
|
|
},
|
|
alignFn: func(t types.Type) int64 {
|
|
return 8
|
|
},
|
|
wantFlag: 3, // both indirect
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sizes := &mockSizes{
|
|
sizeof: tt.sizeFn,
|
|
alignof: tt.alignFn,
|
|
}
|
|
mapType := types.NewMap(tt.key, tt.elem)
|
|
if got := MapTypeFlags(mapType, sizes); got != tt.wantFlag {
|
|
t.Errorf("MapTypeFlags() = %v, want %v", got, tt.wantFlag)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMapTypeChecks(t *testing.T) {
|
|
origLog := log
|
|
defer func() { log = origLog }()
|
|
|
|
tests := []struct {
|
|
name string
|
|
setup func() (*types.Map, types.Sizes)
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "non-comparable key type",
|
|
setup: func() (*types.Map, types.Sizes) {
|
|
key := types.NewSlice(types.Typ[types.Int])
|
|
elem := types.Typ[types.Int]
|
|
return types.NewMap(key, elem), &mockSizes{
|
|
sizeof: func(t types.Type) int64 { return 8 },
|
|
alignof: func(t types.Type) int64 { return 8 },
|
|
}
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key align too big",
|
|
setup: func() (*types.Map, types.Sizes) {
|
|
return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
|
|
sizeof: func(t types.Type) int64 { return 8 },
|
|
alignof: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Basic); ok && t.String() == "int" {
|
|
return BUCKETSIZE + 1
|
|
}
|
|
return 8
|
|
},
|
|
}
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "key size not multiple of align",
|
|
setup: func() (*types.Map, types.Sizes) {
|
|
return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
|
|
sizeof: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Basic); ok && t.String() == "int" {
|
|
return 7
|
|
}
|
|
return 8
|
|
},
|
|
alignof: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Basic); ok && t.String() == "int" {
|
|
return 4
|
|
}
|
|
return 8
|
|
},
|
|
}
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "elem align too big",
|
|
setup: func() (*types.Map, types.Sizes) {
|
|
return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
|
|
sizeof: func(t types.Type) int64 { return 8 },
|
|
alignof: func(t types.Type) int64 {
|
|
if _, ok := t.(*types.Basic); ok && t.String() == "int" {
|
|
return 4
|
|
}
|
|
return BUCKETSIZE + 1
|
|
},
|
|
}
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "valid map type",
|
|
setup: func() (*types.Map, types.Sizes) {
|
|
return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
|
|
sizeof: func(t types.Type) int64 { return 8 },
|
|
alignof: func(t types.Type) int64 { return 8 },
|
|
}
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
log = &testLogger{t: t}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("MapBucketType() panicked unexpectedly: %v", r)
|
|
}
|
|
} else if tt.wantErr {
|
|
t.Error("expected panic but got none")
|
|
}
|
|
}()
|
|
|
|
mapType, sizes := tt.setup()
|
|
MapBucketType(mapType, sizes)
|
|
})
|
|
}
|
|
}
|