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
Этот коммит содержится в:
Ayke van Laethem 2022-05-30 13:47:09 +02:00 коммит произвёл Ron Evans
родитель 5c488e3145
коммит 2d61972475
9 изменённых файлов: 39 добавлений и 244 удалений

Просмотреть файл

@ -4,6 +4,9 @@
package runtime
// This file implements markGlobals for all the files that don't have a more
// specific implementation.
// markGlobals marks all globals, which are reachable by definition.
//
// This implementation marks all globals conservatively and assumes it can use

Просмотреть файл

@ -1,35 +0,0 @@
//go:build gc.conservative && !baremetal && !darwin && !nintendoswitch && !tinygo.wasm && !windows
// +build gc.conservative,!baremetal,!darwin,!nintendoswitch,!tinygo.wasm,!windows
package runtime
import (
"unsafe"
)
//go:extern runtime.trackedGlobalsStart
var trackedGlobalsStart uintptr
//go:extern runtime.trackedGlobalsLength
var trackedGlobalsLength uintptr
//go:extern runtime.trackedGlobalsBitmap
var trackedGlobalsBitmap [0]uint8
// markGlobals marks all globals, which are reachable by definition.
//
// This implementation relies on a compiler pass that stores all globals in a
// single global (adjusting all uses of them accordingly) and creates a bit
// vector with the locations of each pointer. This implementation then walks the
// bit vector and for each pointer it indicates, it marks the root.
//
//go:nobounds
func markGlobals() {
for i := uintptr(0); i < trackedGlobalsLength; i++ {
if trackedGlobalsBitmap[i/8]&(1<<(i%8)) != 0 {
addr := trackedGlobalsStart + i*unsafe.Alignof(uintptr(0))
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
}
}
}

Просмотреть файл

@ -1,8 +1,13 @@
//go:build linux
// +build linux
//go:build linux && !baremetal && !nintendoswitch && !wasi
// +build linux,!baremetal,!nintendoswitch,!wasi
package runtime
// This file is for systems that are _actually_ Linux (not systems that pretend
// to be Linux, like baremetal systems).
import "unsafe"
const GOOS = "linux"
const (
@ -18,3 +23,20 @@ const (
clock_REALTIME = 0
clock_MONOTONIC_RAW = 4
)
//go:extern _edata
var globalsStartSymbol [0]byte
//go:extern _end
var globalsEndSymbol [0]byte
// markGlobals marks all globals, which are reachable by definition.
//
// This implementation marks all globals conservatively and assumes it can use
// linker-defined symbols for the start and end of the .data section.
func markGlobals() {
start := uintptr(unsafe.Pointer(&globalsStartSymbol))
end := uintptr(unsafe.Pointer(&globalsEndSymbol))
start = (start + unsafe.Alignof(uintptr(0)) - 1) &^ (unsafe.Alignof(uintptr(0)) - 1) // align on word boundary
markRoots(start, end)
}

11
src/runtime/os_other.go Обычный файл
Просмотреть файл

@ -0,0 +1,11 @@
//go:build linux && (baremetal || nintendoswitch || wasi)
// +build linux
// +build baremetal nintendoswitch wasi
// Other systems that aren't operating systems supported by the Go toolchain
// need to pretend to be an existing operating system. Linux seems like a good
// choice for this for its wide hardware support.
package runtime
const GOOS = "linux"

Просмотреть файл

@ -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.

Просмотреть файл

@ -7,13 +7,6 @@ import (
"tinygo.org/x/go-llvm"
)
func TestAddGlobalsBitmap(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/gc-globals", func(mod llvm.Module) {
transform.AddGlobalsBitmap(mod)
})
}
func TestMakeGCStackSlots(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/gc-stackslots", func(mod llvm.Module) {

Просмотреть файл

@ -189,8 +189,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
builder.Populate(modPasses)
modPasses.Run(mod)
hasGCPass := AddGlobalsBitmap(mod)
hasGCPass = MakeGCStackSlots(mod) || hasGCPass
hasGCPass := MakeGCStackSlots(mod)
if hasGCPass {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return []error{errors.New("GC pass caused a verification failure")}

33
transform/testdata/gc-globals.ll предоставленный
Просмотреть файл

@ -1,33 +0,0 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown-wasm"
%runtime._string = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
@globalInt = global i32 5
@globalString = global %runtime._string zeroinitializer
@globalInterface = global %runtime._interface zeroinitializer
@constString = constant %runtime._string zeroinitializer
@constInterface = constant %runtime._interface zeroinitializer
@runtime.trackedGlobalsLength = external global i32
@runtime.trackedGlobalsBitmap = external global [0 x i8]
@runtime.trackedGlobalsStart = external global i32
define void @main() {
%1 = load i32, i32* @globalInt
%2 = load %runtime._string, %runtime._string* @globalString
%3 = load %runtime._interface, %runtime._interface* @globalInterface
%4 = load %runtime._string, %runtime._string* @constString
%5 = load %runtime._interface, %runtime._interface* @constInterface
ret void
}
define void @runtime.markGlobals() {
; Very small subset of what runtime.markGlobals would really do.
; Just enough to make sure the transformation is correct.
%1 = load i32, i32* @runtime.trackedGlobalsStart
%2 = load i32, i32* @runtime.trackedGlobalsLength
%3 = getelementptr inbounds [0 x i8], [0 x i8]* @runtime.trackedGlobalsBitmap, i32 0, i32 0
%4 = load i8, i8* %3
ret void
}

31
transform/testdata/gc-globals.out.ll предоставленный
Просмотреть файл

@ -1,31 +0,0 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown-wasm"
%runtime._string = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
@globalInt = global i32 5
@constString = constant %runtime._string zeroinitializer
@constInterface = constant %runtime._interface zeroinitializer
@runtime.trackedGlobalsLength = internal global i32 4
@runtime.trackedGlobalsBitmap = external global [0 x i8]
@runtime.trackedGlobalsStart = internal global i32 ptrtoint ({ %runtime._string, %runtime._interface }* @tinygo.trackedGlobals to i32)
@tinygo.trackedGlobals = internal unnamed_addr global { %runtime._string, %runtime._interface } zeroinitializer
@runtime.trackedGlobalsBitmap.1 = internal global [1 x i8] c"\09"
define void @main() {
%1 = load i32, i32* @globalInt, align 4
%2 = load %runtime._string, %runtime._string* getelementptr inbounds ({ %runtime._string, %runtime._interface }, { %runtime._string, %runtime._interface }* @tinygo.trackedGlobals, i32 0, i32 0), align 4
%3 = load %runtime._interface, %runtime._interface* getelementptr inbounds ({ %runtime._string, %runtime._interface }, { %runtime._string, %runtime._interface }* @tinygo.trackedGlobals, i32 0, i32 1), align 4
%4 = load %runtime._string, %runtime._string* @constString, align 4
%5 = load %runtime._interface, %runtime._interface* @constInterface, align 4
ret void
}
define void @runtime.markGlobals() {
%1 = load i32, i32* @runtime.trackedGlobalsStart, align 4
%2 = load i32, i32* @runtime.trackedGlobalsLength, align 4
%3 = getelementptr inbounds [0 x i8], [0 x i8]* bitcast ([1 x i8]* @runtime.trackedGlobalsBitmap.1 to [0 x i8]*), i32 0, i32 0
%4 = load i8, i8* %3, align 1
ret void
}