diff --git a/builder/testdata/binary-size.txt b/builder/testdata/binary-size.txt index 363860cc9..ef8e6c1a4 100644 --- a/builder/testdata/binary-size.txt +++ b/builder/testdata/binary-size.txt @@ -1,4 +1,4 @@ target package code rodata data bss hifive1b examples/echo 4321 323 0 2268 microbit examples/serial 2842 382 8 2264 -wioterminal examples/pininterrupt 8039 1665 132 7496 +wioterminal examples/pininterrupt 8039 1669 132 7496 diff --git a/compiler/channel.go b/compiler/channel.go index a562e97e3..e03995b52 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -14,8 +14,10 @@ import ( ) func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { - elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem())) + elementType := b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()) + elementSize := b.targetData.TypeAllocSize(elementType) elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) + elementLayout := b.createObjectLayout(elementType, expr.Pos()) bufSize := b.getValue(expr.Size, getPos(expr)) b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { @@ -23,7 +25,7 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { } else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { bufSize = b.CreateTrunc(bufSize, b.uintptrType, "") } - return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "") + return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize, elementLayout}, "") } // createChanSend emits a pseudo chan send operation. It is lowered to the diff --git a/compiler/interface.go b/compiler/interface.go index 84f91cc44..10bffd799 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -284,6 +284,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "sliceOf", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "layout", types.Typ[types.UnsafePointer]), ) case *types.Map: typeFieldTypes = append(typeFieldTypes, @@ -291,6 +292,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "keyType", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "hashmapTypeInfo", types.Typ[types.UnsafePointer]), ) case *types.Struct: typeFieldTypes = append(typeFieldTypes, @@ -299,6 +301,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]), types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]), + types.NewVar(token.NoPos, nil, "layout", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))), ) if len(methods) > 0 { @@ -418,6 +421,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { c.getTypeCode(typ.Elem()), // elementType llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length c.getTypeCode(types.NewSlice(typ.Elem())), // slicePtr + c.createObjectLayout(c.getLLVMType(typ), token.NoPos), // layout } case *types.Map: typeFields = []llvm.Value{ @@ -425,6 +429,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elem c.getTypeCode(typ.Key()), // key + c.getHashmapTypeInfo(typ, token.NoPos), // hashmapTypeInfo } case *types.Struct: var pkgpath string @@ -450,6 +455,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields + c.createObjectLayout(llvmStructType, token.NoPos), // layout } structFieldType := c.getLLVMRuntimeType("structField") @@ -510,7 +516,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} // TODO: params, return values, etc } - // Prepend metadata byte. + // Prepend the common RawType field. typeFields = append([]llvm.Value{ llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false), }, typeFields...) diff --git a/compiler/map.go b/compiler/map.go index ea8a49f43..58dd47620 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -13,6 +13,12 @@ import ( const hashArrayUnrollLimit = 4 +const ( + hashmapBucketSlots = 8 + hashmapMaxKeySize = 128 + hashmapMaxValueSize = 128 +) + // createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { @@ -25,6 +31,8 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { valueSize := b.targetData.TypeAllocSize(llvmValueType) llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false) llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false) + mapLayout := b.getHashmapTypeInfo(mapType, expr.Pos()) + sizeHint := llvm.ConstInt(b.uintptrType, 8, false) if expr.Reserve != nil { sizeHint = b.getValue(expr.Reserve, getPos(expr)) @@ -54,11 +62,72 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { hashmap := b.createRuntimeCall("hashmapMakeGeneric", []llvm.Value{ llvmKeySize, llvmValueSize, sizeHint, + mapLayout, hashFn, equalFn, }, "") return hashmap, nil } +func (c *compilerContext) getHashmapTypeInfo(mapType *types.Map, pos token.Pos) llvm.Value { + llvmKeyType := c.getLLVMType(mapType.Key().Underlying()) + llvmValueType := c.getLLVMType(mapType.Elem().Underlying()) + keySize := c.targetData.TypeAllocSize(llvmKeyType) + valueSize := c.targetData.TypeAllocSize(llvmValueType) + keyLayout := c.createObjectLayout(llvmKeyType, pos) + valueLayout := c.createObjectLayout(llvmValueType, pos) + + llvmKeySlotType := llvmKeyType + if keySize > hashmapMaxKeySize { + llvmKeySlotType = c.dataPtrType + } + llvmValueSlotType := llvmValueType + if valueSize > hashmapMaxValueSize { + llvmValueSlotType = c.dataPtrType + } + + // Keep this in sync with runtime.hashmapBucket and + // runtime.hashmapBucketHeaderSize. + pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) + headerSize := (uint64(8) + pointerSize + 7) &^ 7 + headerPadding := headerSize - uint64(8) - pointerSize + bucketFields := []llvm.Type{ + llvm.ArrayType(c.ctx.Int8Type(), hashmapBucketSlots), + c.dataPtrType, + } + if headerPadding != 0 { + bucketFields = append(bucketFields, llvm.ArrayType(c.ctx.Int8Type(), int(headerPadding))) + } + bucketFields = append(bucketFields, + llvm.ArrayType(llvmKeySlotType, hashmapBucketSlots), + llvm.ArrayType(llvmValueSlotType, hashmapBucketSlots), + ) + bucketType := c.ctx.StructType(bucketFields, true) + bucketSize := headerSize + + c.targetData.TypeAllocSize(llvmKeySlotType)*hashmapBucketSlots + + c.targetData.TypeAllocSize(llvmValueSlotType)*hashmapBucketSlots + if c.targetData.TypeAllocSize(bucketType) != bucketSize { + panic("compiler hashmap bucket layout does not match runtime") + } + bucketLayout := c.createObjectLayout(bucketType, pos) + mapLayoutName := "runtime.hashmapType:" + + hashmapCanonicalTypeName(mapType.Key()) + ":" + + hashmapCanonicalTypeName(mapType.Elem()) + mapLayout := c.mod.NamedGlobal(mapLayoutName) + if mapLayout.IsNil() { + initializer := c.ctx.ConstStruct([]llvm.Value{ + keyLayout, + valueLayout, + bucketLayout, + }, false) + mapLayout = llvm.AddGlobal(c.mod, initializer.Type(), mapLayoutName) + mapLayout.SetInitializer(initializer) + mapLayout.SetGlobalConstant(true) + mapLayout.SetUnnamedAddr(true) + mapLayout.SetLinkage(llvm.LinkOnceODRLinkage) + } + return mapLayout +} + // getRuntimeFunctionValue returns a TinyGo function value (with nil context) // for the named runtime function. func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llvm.Value { diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 664309518..00d7146da 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -166,13 +166,13 @@ entry: } ; Function Attrs: nounwind -define hidden void @main.clearMap(ptr dereferenceable_or_null(48) %m, ptr %context) unnamed_addr #1 { +define hidden void @main.clearMap(ptr dereferenceable_or_null(52) %m, ptr %context) unnamed_addr #1 { entry: call void @runtime.hashmapClear(ptr %m, ptr undef) #4 ret void } -declare void @runtime.hashmapClear(ptr dereferenceable_or_null(48), ptr) #0 +declare void @runtime.hashmapClear(ptr dereferenceable_or_null(52), ptr) #0 attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } diff --git a/compiler/testdata/go1.27.ll b/compiler/testdata/go1.27.ll index d52ba19a3..522cb54a9 100644 --- a/compiler/testdata/go1.27.ll +++ b/compiler/testdata/go1.27.ll @@ -14,7 +14,7 @@ target triple = "wasm32-unknown-wasi" @"main$string" = internal unnamed_addr constant [18 x i8] c"main.genericMethod", align 1 @"main$string.1" = internal unnamed_addr constant [7 x i8] c"Regular", align 1 @"pointer:named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(*main.genericMethod).Regular" } } -@"reflect/types.type:struct:{}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, [0 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{}", ptr @"reflect/types.type.pkgpath.empty", i32 0, i16 0, [0 x %runtime.structField] zeroinitializer }, align 4 +@"reflect/types.type:struct:{}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, ptr, [0 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{}", ptr @"reflect/types.type.pkgpath.empty", i32 0, i16 0, ptr inttoptr (i32 3 to ptr), [0 x %runtime.structField] zeroinitializer }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 @"reflect/types.type:pointer:struct:{}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:struct:{}" }, align 4 @"named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(main.genericMethod).Regular$invoke" } } diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll index cc1f8556b..0026b7ab5 100644 --- a/compiler/testdata/large.ll +++ b/compiler/testdata/large.ll @@ -9,6 +9,7 @@ target triple = "wasm32-unknown-wasi" @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002" = linkonce_odr unnamed_addr constant { i32, [33 x i8] } { i32 258, [33 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02" } @"reflect/types.typeid:named:main.largeValue" = external constant i8 +@"runtime.hashmapType:[1025]byte:[1025]byte" = linkonce_odr unnamed_addr constant { ptr, ptr, ptr } { ptr inttoptr (i32 3 to ptr), ptr inttoptr (i32 3 to ptr), ptr inttoptr (i32 67108137 to ptr) } @llvm.used = appending global [15 x ptr] [ptr @"(main.largeReceiver).makeLargeValue", ptr @"(main.largeReceiver).readLargeValue", ptr @main.makeLargeValue, ptr @main.makeZeroLargeValue, ptr @main.readLargeValue, ptr @main.deferLargeValue, ptr @main.goLargeValue, ptr @main.makeLargeResults, ptr @main.makeTwoLargeResults, ptr @main.makeMixedLargeResults, ptr @main.chooseLargeValue, ptr @main.makePointerLargeValue, ptr @main.useLargeMap, ptr @main.useLargeChannel, ptr @main.selectLargeChannel] @"main$string" = internal unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1 @"main$pack" = internal unnamed_addr constant { %runtime._string } { %runtime._string { ptr @"main$string", i32 31 } } @@ -350,7 +351,7 @@ declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #7 define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9 + %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr nonnull @"runtime.hashmapType:[1025]byte:[1025]byte", ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9 call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9 call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9 %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 @@ -381,11 +382,11 @@ declare i32 @runtime.hash32(ptr, i32, i32, ptr) #0 declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0 -declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0 +declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr) #0 -declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0 +declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(52), ptr, ptr, ptr) #0 -declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0 +declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(52), ptr, ptr, i32, ptr) #0 ; Function Attrs: nounwind define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index 3becf83aa..2140a5031 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -6,6 +6,12 @@ target triple = "wasm32-unknown-wasi" %main.hasPadding = type { i1, i32, i1 } %runtime._string = type { ptr, i32 } +@"runtime/gc.layout:44-545555550500" = linkonce_odr unnamed_addr constant { i32, [6 x i8] } { i32 44, [6 x i8] c"TUUU\05\00" } +@"runtime.hashmapType:struct{string; string}:int" = linkonce_odr unnamed_addr constant { ptr, ptr, ptr } { ptr inttoptr (i32 329 to ptr), ptr inttoptr (i32 3 to ptr), ptr @"runtime/gc.layout:44-545555550500" } +@"runtime.hashmapType:[2]string:int" = linkonce_odr unnamed_addr constant { ptr, ptr, ptr } { ptr inttoptr (i32 69 to ptr), ptr inttoptr (i32 3 to ptr), ptr @"runtime/gc.layout:44-545555550500" } +@"runtime/gc.layout:92-545555555555555555550500" = linkonce_odr unnamed_addr constant { i32, [12 x i8] } { i32 92, [12 x i8] c"TUUUUUUUUU\05\00" } +@"runtime.hashmapType:[5]string:int" = linkonce_odr unnamed_addr constant { ptr, ptr, ptr } { ptr inttoptr (i32 69 to ptr), ptr inttoptr (i32 3 to ptr), ptr @"runtime/gc.layout:92-545555555555555555550500" } + declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0 ; Function Attrs: nounwind @@ -15,7 +21,7 @@ entry: } ; Function Attrs: noinline nounwind -define hidden i32 @main.testZeroGet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { +define hidden i32 @main.testZeroGet(ptr dereferenceable_or_null(52) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { entry: %hashmap.key = alloca %main.hasPadding, align 8 %hashmap.value = alloca i32, align 4 @@ -35,13 +41,13 @@ entry: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(ptr nocapture) #3 -declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0 +declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(52), ptr nocapture, ptr nocapture, i32, ptr) #0 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(ptr nocapture) #3 ; Function Attrs: noinline nounwind -define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { +define hidden void @main.testZeroSet(ptr dereferenceable_or_null(52) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { entry: %hashmap.key = alloca %main.hasPadding, align 8 %hashmap.value = alloca i32, align 4 @@ -58,10 +64,10 @@ entry: ret void } -declare void @runtime.hashmapGenericSet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, ptr) #0 +declare void @runtime.hashmapGenericSet(ptr dereferenceable_or_null(52), ptr nocapture, ptr nocapture, ptr) #0 ; Function Attrs: noinline nounwind -define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(48) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 { +define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(52) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 { entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 @@ -80,7 +86,7 @@ entry: } ; Function Attrs: noinline nounwind -define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(48) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 { +define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(52) %m, [2 x %main.hasPadding] %s, ptr %context) unnamed_addr #2 { entry: %hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.value = alloca i32, align 4 @@ -102,7 +108,7 @@ entry: define hidden ptr @main.makeStringStructMap(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 16, i32 4, i32 8, ptr null, ptr nonnull @"hashmapKeyHash.struct{string; string}", ptr null, ptr nonnull @"hashmapKeyEqual.struct{string; string}", ptr undef) #4 + %0 = call ptr @runtime.hashmapMakeGeneric(i32 16, i32 4, i32 8, ptr nonnull @"runtime.hashmapType:struct{string; string}:int", ptr null, ptr nonnull @"hashmapKeyHash.struct{string; string}", ptr null, ptr nonnull @"hashmapKeyEqual.struct{string; string}", ptr undef) #4 call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #4 ret ptr %0 } @@ -145,13 +151,13 @@ entry: declare i1 @runtime.stringEqual(ptr readonly, i32, ptr readonly, i32, ptr) #0 -declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0 +declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr) #0 ; Function Attrs: noinline nounwind define hidden ptr @main.makeShortStringArrayMap(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 16, i32 4, i32 8, ptr null, ptr nonnull @"hashmapKeyHash.[2]string", ptr null, ptr nonnull @"hashmapKeyEqual.[2]string", ptr undef) #4 + %0 = call ptr @runtime.hashmapMakeGeneric(i32 16, i32 4, i32 8, ptr nonnull @"runtime.hashmapType:[2]string:int", ptr null, ptr nonnull @"hashmapKeyHash.[2]string", ptr null, ptr nonnull @"hashmapKeyEqual.[2]string", ptr undef) #4 call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #4 ret ptr %0 } @@ -194,7 +200,7 @@ entry: define hidden ptr @main.makeLongStringArrayMap(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 40, i32 4, i32 8, ptr null, ptr nonnull @"hashmapKeyHash.[5]string", ptr null, ptr nonnull @"hashmapKeyEqual.[5]string", ptr undef) #4 + %0 = call ptr @runtime.hashmapMakeGeneric(i32 40, i32 4, i32 8, ptr nonnull @"runtime.hashmapType:[5]string:int", ptr null, ptr nonnull @"hashmapKeyHash.[5]string", ptr null, ptr nonnull @"hashmapKeyEqual.[5]string", ptr undef) #4 call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #4 ret ptr %0 } diff --git a/interp/memory.go b/interp/memory.go index 7c1eb2d33..c9c09898a 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -1278,13 +1278,14 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { // integer value, or can be nil. ptr, err := layoutValue.asPointer(r) if err == errIntegerAsPointer { - // It's an integer, which means it's a small object or unknown. + // It's an integer, which means it's a small object. layout := layoutValue.Uint(r) if layout == 0 { - // Nil pointer, which means the layout is unknown. - return 0, nil + panic("runtime.alloc called without a GC layout") } if layout%2 != 1 { + // Conservative layouts are reserved for stack storage and cannot + // reach interpreted heap allocations. // Sanity check: the least significant bit must be set. This is how // the runtime can separate pointers from integers. panic("unexpected layout") @@ -1331,11 +1332,6 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { // have some additional repetition, for example in the buffer of a slice. func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type { objectSizeWords, bitmap := r.readObjectLayout(layoutValue) - if bitmap == nil { - // No information available. - return llvm.Type{} - } - if bitmap.BitLen() == 0 { // There are no pointers in this object, so treat this as a raw byte // buffer. This is important because objects without pointers may have diff --git a/interp/testdata/alloc.ll b/interp/testdata/alloc.ll index 82fbb5b27..3e3c1ca22 100644 --- a/interp/testdata/alloc.ll +++ b/interp/testdata/alloc.ll @@ -11,6 +11,7 @@ target triple = "wasm32--wasi" @layout3 = global ptr null @layout4 = global ptr null @bigobj1 = global ptr null +@pointerFree10 = global ptr null declare ptr @runtime.alloc(i32, ptr) unnamed_addr @@ -49,5 +50,9 @@ define internal void @main.init() unnamed_addr { ; Large object that needs to be stored in a separate global. %bigobj1 = call ptr @runtime.alloc(i32 248, ptr @"runtime/gc.layout:62-2000000000000001") store ptr %bigobj1, ptr @bigobj1 + + ; Another pointer-free object. + %pointerFree10 = call ptr @runtime.alloc(i32 10, ptr inttoptr (i32 3 to ptr)) + store ptr %pointerFree10, ptr @pointerFree10 ret void } diff --git a/interp/testdata/alloc.out.ll b/interp/testdata/alloc.out.ll index b9da6291f..641bad4dd 100644 --- a/interp/testdata/alloc.out.ll +++ b/interp/testdata/alloc.out.ll @@ -10,6 +10,7 @@ target triple = "wasm32--wasi" @layout3 = local_unnamed_addr global ptr @"main$alloc.6" @layout4 = local_unnamed_addr global ptr @"main$alloc.7" @bigobj1 = local_unnamed_addr global ptr @"main$alloc.8" +@pointerFree10 = local_unnamed_addr global ptr @"main$alloc.9" @"main$alloc" = internal global [12 x i8] zeroinitializer, align 4 @"main$alloc.1" = internal global [7 x i8] zeroinitializer, align 4 @"main$alloc.2" = internal global [3 x i8] zeroinitializer, align 4 @@ -19,6 +20,7 @@ target triple = "wasm32--wasi" @"main$alloc.6" = internal global { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr } zeroinitializer, align 4 @"main$alloc.7" = internal global [3 x { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr }] zeroinitializer, align 4 @"main$alloc.8" = internal global { ptr, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, ptr } zeroinitializer, align 4 +@"main$alloc.9" = internal global [10 x i8] zeroinitializer, align 4 define void @runtime.initAll() unnamed_addr { ret void diff --git a/src/internal/gclayout/gclayout.go b/src/internal/gclayout/gclayout.go index d6235889f..3ed750d13 100644 --- a/src/internal/gclayout/gclayout.go +++ b/src/internal/gclayout/gclayout.go @@ -17,10 +17,15 @@ const ( sizeShift = sizeBits + 1 - NoPtrs = Layout((0 << sizeShift) | (1 << 1) | 1) - Pointer = Layout((1 << sizeShift) | ((unsafe.Sizeof(unsafe.Pointer(nil)) / ptrAlign) << 1) | 1) - String = Layout((1 << sizeShift) | ((unsafe.Sizeof("") / ptrAlign) << 1) | 1) - Slice = Layout((1 << sizeShift) | ((unsafe.Sizeof([]byte{}) / ptrAlign) << 1) | 1) + NoPtrs = Layout((0 << sizeShift) | (1 << 1) | 1) + Pointer = Layout((1 << sizeShift) | ((unsafe.Sizeof(unsafe.Pointer(nil)) / ptrAlign) << 1) | 1) + PointerPair = Layout((3 << sizeShift) | ((2 * unsafe.Sizeof(unsafe.Pointer(nil)) / ptrAlign) << 1) | 1) + String = Layout((1 << sizeShift) | ((unsafe.Sizeof("") / ptrAlign) << 1) | 1) + Slice = Layout((1 << sizeShift) | ((unsafe.Sizeof([]byte{}) / ptrAlign) << 1) | 1) + + // Conservative is reserved for stack storage, which does not have an + // ordinary Go object layout. + Conservative = Layout(2) ) func (l Layout) AsPtr() unsafe.Pointer { return unsafe.Pointer(l) } diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index 5ced5d357..0189530a7 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -166,6 +166,11 @@ type RawType struct { meta uint8 // metadata byte, contains kind and flags (see constants above) } +type basicType struct { + RawType + ptrTo *RawType +} + // All types that have an element type: named, chan, slice, array, map (but not // pointer because it doesn't have ptrTo). type elemType struct { @@ -200,6 +205,7 @@ type arrayType struct { elem *RawType arrayLen uintptr slicePtr *RawType + layout unsafe.Pointer } type mapType struct { @@ -208,6 +214,7 @@ type mapType struct { ptrTo *RawType elem *RawType key *RawType + typeInfo unsafe.Pointer } // namedType is the type descriptor for named types. The numMethod field uses @@ -243,6 +250,7 @@ type structType struct { pkgpath *byte size uint32 numField uint16 + layout unsafe.Pointer fields [1]structField // the remaining fields are all of type structField // methods methodSet follows after fields, only when numMethod & numMethodHasMethodSet != 0 } @@ -298,6 +306,8 @@ func pointerTo(t *RawType) *RawType { } switch t.Kind() { + case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Complex64, Complex128, Float32, Float64, String, UnsafePointer: + return (*basicType)(unsafe.Pointer(t)).ptrTo case Pointer: if tag := t.ptrtag(); tag < 3 { return (*RawType)(unsafe.Add(unsafe.Pointer(t), 1)) @@ -306,6 +316,8 @@ func pointerTo(t *RawType) *RawType { // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 // We need to be able to create types that match existing types to prevent typecode equality. panic("reflect: cannot make *****T type") + case Interface, Func: + return (*interfaceType)(unsafe.Pointer(t)).ptrTo case Struct: return (*structType)(unsafe.Pointer(t)).ptrTo default: @@ -729,6 +741,7 @@ func (t *RawType) Align() int { } func (r *RawType) gcLayout() unsafe.Pointer { + r = r.underlying() kind := r.Kind() if kind < String { @@ -736,16 +749,26 @@ func (r *RawType) gcLayout() unsafe.Pointer { } switch kind { - case Pointer, UnsafePointer, Chan, Map: - return gclayout.Pointer.AsPtr() case String: return gclayout.String.AsPtr() + case UnsafePointer, Chan, Pointer, Map: + return gclayout.Pointer.AsPtr() + case Interface, Func: + return gclayout.PointerPair.AsPtr() case Slice: return gclayout.Slice.AsPtr() + case Array: + return (*arrayType)(unsafe.Pointer(r)).layout + case Struct: + return (*structType)(unsafe.Pointer(r)).layout + default: + panic("reflect: invalid GC layout kind") } +} - // Unknown (for now); let the conservative pointer scanning handle it - return nil +func (r *RawType) hashmapTypeInfo() unsafe.Pointer { + r = r.underlying() + return (*mapType)(unsafe.Pointer(r)).typeInfo } // FieldAlign returns the alignment if this type is used in a struct field. It diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 18ebe3df7..e0591ec86 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -1,6 +1,7 @@ package reflectlite import ( + "internal/gclayout" "math" "unsafe" ) @@ -1644,7 +1645,7 @@ func makeInt(flags valueFlags, bits uint64, t *RawType) Value { ptr := unsafe.Pointer(&v.value) if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) + ptr = alloc(size, gclayout.NoPtrs.AsPtr()) v.value = ptr } @@ -1671,7 +1672,7 @@ func makeFloat(flags valueFlags, f float64, t *RawType) Value { ptr := unsafe.Pointer(&v.value) if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) + ptr = alloc(size, gclayout.NoPtrs.AsPtr()) v.value = ptr } @@ -1703,7 +1704,7 @@ func makeComplex(flags valueFlags, f complex128, t *RawType) Value { ptr := unsafe.Pointer(&v.value) if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) + ptr = alloc(size, gclayout.NoPtrs.AsPtr()) v.value = ptr } @@ -1834,7 +1835,7 @@ func Zero(typ Type) Value { return Value{ typecode: typ.(*RawType), - value: alloc(size, nil), + value: alloc(size, typ.(*RawType).gcLayout()), flags: valueFlagExported | valueFlagRO, } } @@ -1844,7 +1845,7 @@ func Zero(typ Type) Value { func New(typ Type) Value { return Value{ typecode: pointerTo(typ.(*RawType)), - value: alloc(typ.Size(), nil), + value: alloc(typ.Size(), typ.(*RawType).gcLayout()), flags: valueFlagExported, } } @@ -2203,13 +2204,13 @@ func (v Value) FieldByNameFunc(match func(string) bool) Value { } //go:linkname hashmapMake runtime.hashmapMake -func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, typeInfo unsafe.Pointer, alg uint8) unsafe.Pointer //go:linkname hashmapMakeReflect runtime.hashmapMakeReflect -func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, keyType unsafe.Pointer) unsafe.Pointer +func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, typeInfo, keyType unsafe.Pointer) unsafe.Pointer //go:linkname chanMake runtime.chanMake -func chanMake(elementSize uintptr, bufSize uintptr) unsafe.Pointer +func chanMake(elementSize uintptr, bufSize uintptr, elementLayout unsafe.Pointer) unsafe.Pointer // MakeMapWithSize creates a new map with the specified type and initial space // for approximately n elements. @@ -2231,18 +2232,19 @@ func MakeMapWithSize(typ Type, n int) Value { key := typ.Key().(*RawType) val := typ.Elem().(*RawType) + typeInfo := typ.(*RawType).hashmapTypeInfo() var m unsafe.Pointer if key.Kind() == String { - m = hashmapMake(key.Size(), val.Size(), uintptr(n), hashmapAlgorithmString) + m = hashmapMake(key.Size(), val.Size(), uintptr(n), typeInfo, hashmapAlgorithmString) } else if key.isBinary() { - m = hashmapMake(key.Size(), val.Size(), uintptr(n), hashmapAlgorithmBinary) + m = hashmapMake(key.Size(), val.Size(), uintptr(n), typeInfo, hashmapAlgorithmBinary) } else { // Composite key type (struct with strings, floats, etc.). // Use runtime-generated hash/equal closures that walk the // type structure, matching the compiler-generated functions. - m = hashmapMakeReflect(key.Size(), val.Size(), uintptr(n), unsafe.Pointer(key)) + m = hashmapMakeReflect(key.Size(), val.Size(), uintptr(n), typeInfo, unsafe.Pointer(key)) } return Value{ @@ -2269,7 +2271,7 @@ func MakeChan(typ Type, size int) Value { panic("reflect.MakeChan: unidirectional channel type") } elem := typ.Elem().(*RawType) - ch := chanMake(elem.Size(), uintptr(size)) + ch := chanMake(elem.Size(), uintptr(size), elem.gcLayout()) return Value{ typecode: typ.(*RawType), value: ch, diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index 4d78e1937..3bc41b3a2 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -3,6 +3,7 @@ package task import ( + "internal/gclayout" "unsafe" ) @@ -73,7 +74,7 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { s.args = args // Create a stack. - stack := runtime_alloc(stackSize, nil) + stack := runtime_alloc(stackSize, gclayout.Conservative.AsPtr()) // Set up the stack canary, a random number that should be checked when // switching from the task back to the scheduler. The stack canary pointer diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 23f3b9097..eaab211bb 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -3,6 +3,7 @@ package task import ( + "internal/gclayout" "unsafe" ) @@ -36,7 +37,7 @@ func taskExit() { // initialize the state and prepare to call the specified function with the specified argument bundle. func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // Create a stack. - stack := runtime_alloc(stackSize, nil) + stack := runtime_alloc(stackSize, gclayout.Conservative.AsPtr()) // Set up the stack canary, a random number that should be checked when // switching from the task back to the scheduler. The stack canary pointer diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index b31f1e48e..3456b2941 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -994,6 +994,20 @@ func TestTypeAssertPanic(t *testing.T) { }) } +type tinyMakeChanElement struct { + ptr *int + text string +} + +var tinyMakeChanChurn []*int + +//go:noinline +func fillTinyMakeChan(ch chan tinyMakeChanElement) { + value := new(int) + *value = 42 + ch <- tinyMakeChanElement{ptr: value, text: "hello"} +} + func TestTinyMakeChan(t *testing.T) { // Value.Send and Value.Recv are not implemented yet, so the channel is // exercised through Interface(): that proves MakeChan returns a working @@ -1027,6 +1041,22 @@ func TestTinyMakeChan(t *testing.T) { } }) + t.Run("buffered pointers survive GC", func(t *testing.T) { + v := MakeChan(TypeOf(make(chan tinyMakeChanElement)), 1) + ch := v.Interface().(chan tinyMakeChanElement) + fillTinyMakeChan(ch) + runtime.GC() + tinyMakeChanChurn = make([]*int, 128) + for i := range tinyMakeChanChurn { + tinyMakeChanChurn[i] = new(int) + } + + got := <-ch + if *got.ptr != 42 || got.text != "hello" { + t.Errorf("<-ch=%v, want {42 hello}", got) + } + }) + t.Run("unbuffered", func(t *testing.T) { v := MakeChan(TypeOf(make(chan string)), 0) if got, want := v.Cap(), 0; got != want { diff --git a/src/runtime/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index df824881e..694840af9 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -2,7 +2,10 @@ package runtime -import "unsafe" +import ( + "internal/gclayout" + "unsafe" +) // The below functions override the default allocator of wasi-libc. This ensures // code linked from other languages can allocate memory without colliding with @@ -21,7 +24,7 @@ func libc_malloc(size uintptr) unsafe.Pointer { if size == 0 { return nil } - ptr := alloc(size, nil) + ptr := alloc(size, gclayout.NoPtrs.AsPtr()) allocs[(*byte)(ptr)] = size return ptr } @@ -54,7 +57,7 @@ func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer { // It's hard to optimize this to expand the current buffer with our GC, but // it is theoretically possible. For now, just always allocate fresh. // TODO: we could skip this if the new allocation is smaller than the old. - ptr := alloc(size, nil) + ptr := alloc(size, gclayout.NoPtrs.AsPtr()) if oldPtr != nil { if oldSize, ok := allocs[(*byte)(oldPtr)]; ok { diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 6dd29e490..4893297fe 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -3,6 +3,7 @@ package runtime import ( + "internal/gclayout" "sync/atomic" "unsafe" ) @@ -11,7 +12,7 @@ import ( func libc_malloc(size uintptr) unsafe.Pointer { // Note: this zeroes the returned buffer which is not necessary. // The same goes for bytealg.MakeNoZero. - return alloc(size, nil) + return alloc(size, gclayout.NoPtrs.AsPtr()) } //export calloc diff --git a/src/runtime/chan.go b/src/runtime/chan.go index a85e9b661..f425daf5d 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -137,11 +137,11 @@ type chanSelectState struct { value unsafe.Pointer } -func chanMake(elementSize uintptr, bufSize uintptr) *channel { +func chanMake(elementSize uintptr, bufSize uintptr, elementLayout unsafe.Pointer) *channel { return &channel{ elementSize: elementSize, bufCap: bufSize, - buf: alloc(elementSize*bufSize, nil), + buf: alloc(elementSize*bufSize, elementLayout), } } diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 3afed0a3e..c27401d62 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -31,6 +31,7 @@ package runtime // Moss. import ( + "internal/gclayout" "internal/reflectlite" "internal/task" "runtime/interrupt" @@ -501,7 +502,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { if ptr == nil { - return alloc(size, nil) + return alloc(size, gclayout.NoPtrs.AsPtr()) } // Find the first block of the original allocation. @@ -526,7 +527,7 @@ func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { } // Create a new allocation and copy the old data. - newAlloc := alloc(size, nil) + newAlloc := alloc(size, gclayout.NoPtrs.AsPtr()) memcpy(newAlloc, ptr, oldSize) free(ptr) diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 839acd8d9..3ebee0989 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -7,6 +7,7 @@ package runtime // may be the only memory allocator possible. import ( + "internal/gclayout" "internal/task" "sync/atomic" "unsafe" @@ -69,7 +70,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { } func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { - newAlloc := alloc(size, nil) + newAlloc := alloc(size, gclayout.NoPtrs.AsPtr()) if ptr == nil { return newAlloc } diff --git a/src/runtime/gc_precise.go b/src/runtime/gc_precise.go index 062cc46af..7f05c4f09 100644 --- a/src/runtime/gc_precise.go +++ b/src/runtime/gc_precise.go @@ -55,7 +55,10 @@ package runtime -import "unsafe" +import ( + "internal/gclayout" + "unsafe" +) const sizeFieldBits = 4 + (unsafe.Sizeof(uintptr(0)) / 4) @@ -76,9 +79,7 @@ func (layout gcLayout) pointerFree() bool { // The length is rounded down to a multiple of the element size. func (layout gcLayout) scan(start, len uintptr) { switch { - case layout == 0: - // This is an unknown layout. - // Scan conservatively. + case layout == gcLayout(gclayout.Conservative): // NOTE: This is *NOT* equivalent to a slice of pointers on AVR. scanConservative(start, len) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 56405dd10..5b43edd2d 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -14,6 +14,7 @@ import ( // The underlying hashmap structure for Go. type hashmap struct { buckets unsafe.Pointer // pointer to array of buckets + typeInfo *hashmapTypeInfo seed uintptr count uintptr keySize uintptr @@ -26,6 +27,17 @@ type hashmap struct { keyHash func(key unsafe.Pointer, size, seed uintptr) uint32 } +type hashmapTypeInfo struct { + keyLayout unsafe.Pointer + valueLayout unsafe.Pointer + bucketLayout unsafe.Pointer +} + +//go:inline +func hashmapType(m *hashmap) *hashmapTypeInfo { + return m.typeInfo +} + const ( hashmapMaxKeySize = 128 hashmapMaxValueSize = 128 @@ -113,7 +125,7 @@ func hashmapTopHash(hash uint32) uint8 { } // Create a new hashmap with the given keySize and valueSize. -func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashmap { +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, typeInfo unsafe.Pointer, alg uint8) *hashmap { bucketBits := uint8(0) for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) { bucketBits++ @@ -132,13 +144,14 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashm } bucketBufSize := hashmapBucketHeaderSize + keySlotSize*8 + valueSlotSize*8 - buckets := alloc(bucketBufSize*(1<