gc: drop support for 'precise' globals

Precise globals require a whole program optimization pass that is hard
to support when building packages separately. This patch removes support
for these globals by converting the last use (Linux) to use
linker-defined symbols instead.

For details, see: https://github.com/tinygo-org/tinygo/issues/2870
This commit is contained in:
Ayke van Laethem
2022-05-30 13:47:09 +02:00
committed by Ron Evans
parent 5c488e3145
commit 2d61972475
9 changed files with 39 additions and 244 deletions
-134
View File
@@ -1,8 +1,6 @@
package transform
import (
"math/big"
"tinygo.org/x/go-llvm"
)
@@ -311,138 +309,6 @@ func MakeGCStackSlots(mod llvm.Module) bool {
return true
}
// AddGlobalsBitmap performs a few related functions. It is needed for scanning
// globals on platforms where the .data/.bss section is not easily accessible by
// the GC, and thus all globals that contain pointers must be made reachable by
// the GC in some other way.
//
// First, it scans all globals, and bundles all globals that contain a pointer
// into one large global (updating all uses in the process). Then it creates a
// bitmap (bit vector) to locate all the pointers in this large global. This
// bitmap allows the GC to know in advance where exactly all the pointers live
// in the large globals bundle, to avoid false positives.
func AddGlobalsBitmap(mod llvm.Module) bool {
if mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return false // nothing to do: no GC in use
}
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
// Collect all globals that contain pointers (and thus must be scanned by
// the GC).
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() || global.IsGlobalConstant() {
continue
}
typ := global.Type().ElementType()
ptrs := getPointerBitmap(targetData, typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
// Make a new global that bundles all existing globals, and remove the
// existing globals. All uses of the previous independent globals are
// replaced with a GEP into the new globals bundle.
globalsBundleType := ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
// Update trackedGlobalsStart, which points to the globals bundle.
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, uintptrType)
mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
mod.NamedGlobal("runtime.trackedGlobalsStart").SetLinkage(llvm.InternalLinkage)
// Update trackedGlobalsLength, which contains the length (in words) of the
// globals bundle.
alignment := targetData.PrefTypeAlignment(llvm.PointerType(ctx.Int8Type(), 0))
trackedGlobalsLength := llvm.ConstInt(uintptrType, targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
mod.NamedGlobal("runtime.trackedGlobalsLength").SetLinkage(llvm.InternalLinkage)
mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
// Create a bitmap (a new global) that stores for each word in the globals
// bundle whether it contains a pointer. This allows globals to be scanned
// precisely: no non-pointers will be considered pointers if the bit pattern
// looks like one.
// This code assumes that pointers are self-aligned. For example, that a
// 32-bit (4-byte) pointer is also aligned to 4 bytes.
bitmapBytes := getPointerBitmap(targetData, globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(ctx.Int8Type(), bitmapValues)
bitmapNew := llvm.AddGlobal(mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
bitmapNew.SetLinkage(llvm.InternalLinkage)
return true // the IR was changed
}
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive.
func getPointerBitmap(targetData llvm.TargetData, typ llvm.Type, name string) *big.Int {
alignment := targetData.PrefTypeAlignment(llvm.PointerType(typ.Context().Int8Type(), 0))
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := getPointerBitmap(targetData, subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := getPointerBitmap(targetData, subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.