runtime: require explicit GC layouts

This commit is contained in:
Jake Bailey
2026-08-07 10:21:19 -07:00
committed by Ron Evans
parent 1a4cb2032e
commit a7360d5ad3
28 changed files with 279 additions and 108 deletions
+69
View File
@@ -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 {