Files
llgo/runtime/abi/map_test.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)
})
}
}