Skip to content

gc: drop support for 'precise' globals #2879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 0 additions & 35 deletions src/runtime/gc_globals_precise.go

This file was deleted.

26 changes: 24 additions & 2 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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 changes: 11 additions & 0 deletions src/runtime/os_other.go
Original file line number Diff line number Diff line change
@@ -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"
134 changes: 0 additions & 134 deletions transform/gc.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package transform

import (
"math/big"

"tinygo.org/x/go-llvm"
)

Expand Down Expand Up @@ -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.
Expand Down
7 changes: 0 additions & 7 deletions transform/gc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 1 addition & 2 deletions transform/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,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")}
Expand Down
33 changes: 0 additions & 33 deletions transform/testdata/gc-globals.ll

This file was deleted.

31 changes: 0 additions & 31 deletions transform/testdata/gc-globals.out.ll

This file was deleted.