compiler: move GC passes to the transform package
Этот коммит содержится в:
родитель
3d3e48179e
коммит
f0bb3c092d
9 изменённых файлов: 611 добавлений и 359 удалений
357
compiler/gc.go
357
compiler/gc.go
|
@ -5,7 +5,6 @@ package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
|
@ -105,359 +104,3 @@ func typeHasPointers(t llvm.Type) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
|
|
||||||
// stores to stack slots that are scannable by the GC.
|
|
||||||
func (c *Compiler) makeGCStackSlots() bool {
|
|
||||||
// Check whether there are allocations at all.
|
|
||||||
alloc := c.mod.NamedFunction("runtime.alloc")
|
|
||||||
if alloc.IsNil() {
|
|
||||||
// Nothing to. Make sure all remaining bits and pieces for stack
|
|
||||||
// chains are neutralized.
|
|
||||||
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
|
|
||||||
call.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
|
|
||||||
if !stackChainStart.IsNil() {
|
|
||||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
|
|
||||||
stackChainStart.SetGlobalConstant(true)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
|
|
||||||
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
|
|
||||||
return false // nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look at *all* functions to see whether they are free of function pointer
|
|
||||||
// calls.
|
|
||||||
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
|
|
||||||
// faster when written in C++ (to avoid the CGo overhead).
|
|
||||||
funcsWithFPCall := map[llvm.Value]struct{}{}
|
|
||||||
n := 0
|
|
||||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
||||||
n++
|
|
||||||
if _, ok := funcsWithFPCall[fn]; ok {
|
|
||||||
continue // already found
|
|
||||||
}
|
|
||||||
done := false
|
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
|
|
||||||
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
|
|
||||||
if call.IsACallInst().IsNil() {
|
|
||||||
continue // only looking at calls
|
|
||||||
}
|
|
||||||
called := call.CalledValue()
|
|
||||||
if !called.IsAFunction().IsNil() {
|
|
||||||
continue // only looking for function pointers
|
|
||||||
}
|
|
||||||
funcsWithFPCall[fn] = struct{}{}
|
|
||||||
markParentFunctions(funcsWithFPCall, fn)
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine which functions need stack objects. Many leaf functions don't
|
|
||||||
// need it: it only causes overhead for them.
|
|
||||||
// Actually, in one test it was only able to eliminate stack object from 12%
|
|
||||||
// of functions that had a call to runtime.trackPointer (8 out of 68
|
|
||||||
// functions), so this optimization is not as big as it may seem.
|
|
||||||
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
|
|
||||||
|
|
||||||
// Work from runtime.alloc and trace all parents to check which functions do
|
|
||||||
// a heap allocation (and thus which functions do not).
|
|
||||||
markParentFunctions(allocatingFunctions, alloc)
|
|
||||||
|
|
||||||
// Also trace all functions that call a function pointer.
|
|
||||||
for fn := range funcsWithFPCall {
|
|
||||||
// Assume that functions that call a function pointer do a heap
|
|
||||||
// allocation as a conservative guess because the called function might
|
|
||||||
// do a heap allocation.
|
|
||||||
allocatingFunctions[fn] = struct{}{}
|
|
||||||
markParentFunctions(allocatingFunctions, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect some variables used below in the loop.
|
|
||||||
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
|
|
||||||
if stackChainStart.IsNil() {
|
|
||||||
// This may be reached in a weird scenario where we call runtime.alloc but the garbage collector is unreachable.
|
|
||||||
// This can be accomplished by allocating 0 bytes.
|
|
||||||
// There is no point in tracking anything.
|
|
||||||
for _, use := range getUses(trackPointer) {
|
|
||||||
use.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
stackChainStartType := stackChainStart.Type().ElementType()
|
|
||||||
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
|
|
||||||
|
|
||||||
// Iterate until runtime.trackPointer has no uses left.
|
|
||||||
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
|
|
||||||
// Pick the first use of runtime.trackPointer.
|
|
||||||
call := use.User()
|
|
||||||
if call.IsACallInst().IsNil() {
|
|
||||||
panic("expected runtime.trackPointer use to be a call")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick the parent function.
|
|
||||||
fn := call.InstructionParent().Parent()
|
|
||||||
|
|
||||||
if _, ok := allocatingFunctions[fn]; !ok {
|
|
||||||
// This function nor any of the functions it calls (recursively)
|
|
||||||
// allocate anything from the heap, so it will not trigger a garbage
|
|
||||||
// collection cycle. Thus, it does not need to track local pointer
|
|
||||||
// values.
|
|
||||||
// This is a useful optimization but not as big as you might guess,
|
|
||||||
// as described above (it avoids stack objects for ~12% of
|
|
||||||
// functions).
|
|
||||||
call.EraseFromParentAsInstruction()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all calls to runtime.trackPointer in this function.
|
|
||||||
var calls []llvm.Value
|
|
||||||
var returns []llvm.Value
|
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
||||||
switch inst.InstructionOpcode() {
|
|
||||||
case llvm.Call:
|
|
||||||
if inst.CalledValue() == trackPointer {
|
|
||||||
calls = append(calls, inst)
|
|
||||||
}
|
|
||||||
case llvm.Ret:
|
|
||||||
returns = append(returns, inst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine what to do with each call.
|
|
||||||
var allocas, pointers []llvm.Value
|
|
||||||
for _, call := range calls {
|
|
||||||
ptr := call.Operand(0)
|
|
||||||
call.EraseFromParentAsInstruction()
|
|
||||||
if ptr.IsAInstruction().IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some trivial optimizations.
|
|
||||||
if ptr.IsAInstruction().IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch ptr.InstructionOpcode() {
|
|
||||||
case llvm.PHI, llvm.GetElementPtr:
|
|
||||||
// These values do not create new values: the values already
|
|
||||||
// existed locally in this function so must have been tracked
|
|
||||||
// already.
|
|
||||||
continue
|
|
||||||
case llvm.ExtractValue, llvm.BitCast:
|
|
||||||
// These instructions do not create new values, but their
|
|
||||||
// original value may not be tracked. So keep tracking them for
|
|
||||||
// now.
|
|
||||||
// With more analysis, it should be possible to optimize a
|
|
||||||
// significant chunk of these away.
|
|
||||||
case llvm.Call, llvm.Load, llvm.IntToPtr:
|
|
||||||
// These create new values so must be stored locally. But
|
|
||||||
// perhaps some of these can be fused when they actually refer
|
|
||||||
// to the same value.
|
|
||||||
default:
|
|
||||||
// Ambiguous. These instructions are uncommon, but perhaps could
|
|
||||||
// be optimized if needed.
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ptr.IsAAllocaInst().IsNil() {
|
|
||||||
if typeHasPointers(ptr.Type().ElementType()) {
|
|
||||||
allocas = append(allocas, ptr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pointers = append(pointers, ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allocas) == 0 && len(pointers) == 0 {
|
|
||||||
// This function does not need to keep track of stack pointers.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the type of the required stack slot.
|
|
||||||
fields := []llvm.Type{
|
|
||||||
stackChainStartType, // Pointer to parent frame.
|
|
||||||
c.uintptrType, // Number of elements in this frame.
|
|
||||||
}
|
|
||||||
for _, alloca := range allocas {
|
|
||||||
fields = append(fields, alloca.Type().ElementType())
|
|
||||||
}
|
|
||||||
for _, ptr := range pointers {
|
|
||||||
fields = append(fields, ptr.Type())
|
|
||||||
}
|
|
||||||
stackObjectType := c.ctx.StructType(fields, false)
|
|
||||||
|
|
||||||
// Create the stack object at the function entry.
|
|
||||||
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
|
||||||
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
|
|
||||||
initialStackObject := llvm.ConstNull(stackObjectType)
|
|
||||||
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
|
|
||||||
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
|
|
||||||
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
|
|
||||||
c.builder.CreateStore(initialStackObject, stackObject)
|
|
||||||
|
|
||||||
// Update stack start.
|
|
||||||
parent := c.builder.CreateLoad(stackChainStart, "")
|
|
||||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
}, "")
|
|
||||||
c.builder.CreateStore(parent, gep)
|
|
||||||
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
|
|
||||||
c.builder.CreateStore(stackObjectCast, stackChainStart)
|
|
||||||
|
|
||||||
// Replace all independent allocas with GEPs in the stack object.
|
|
||||||
for i, alloca := range allocas {
|
|
||||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
|
|
||||||
}, "")
|
|
||||||
alloca.ReplaceAllUsesWith(gep)
|
|
||||||
alloca.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a store to the stack object after each new pointer that is created.
|
|
||||||
for i, ptr := range pointers {
|
|
||||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
|
|
||||||
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
|
|
||||||
}, "")
|
|
||||||
c.builder.CreateStore(ptr, gep)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure this stack object is popped from the linked list of stack
|
|
||||||
// objects at return.
|
|
||||||
for _, ret := range returns {
|
|
||||||
c.builder.SetInsertPointBefore(ret)
|
|
||||||
c.builder.CreateStore(parent, stackChainStart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) addGlobalsBitmap() bool {
|
|
||||||
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
|
|
||||||
return false // nothing to do: no GC in use
|
|
||||||
}
|
|
||||||
|
|
||||||
var trackedGlobals []llvm.Value
|
|
||||||
var trackedGlobalTypes []llvm.Type
|
|
||||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
|
||||||
if global.IsDeclaration() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
typ := global.Type().ElementType()
|
|
||||||
ptrs := c.getPointerBitmap(typ, global.Name())
|
|
||||||
if ptrs.BitLen() == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
trackedGlobals = append(trackedGlobals, global)
|
|
||||||
trackedGlobalTypes = append(trackedGlobalTypes, typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
|
|
||||||
globalsBundle := llvm.AddGlobal(c.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(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
|
||||||
})
|
|
||||||
global.ReplaceAllUsesWith(gep)
|
|
||||||
global.EraseFromParentAsGlobal()
|
|
||||||
}
|
|
||||||
globalsBundle.SetInitializer(initializer)
|
|
||||||
|
|
||||||
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
|
|
||||||
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
|
|
||||||
|
|
||||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
|
||||||
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
|
|
||||||
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
|
|
||||||
|
|
||||||
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
|
|
||||||
bitmapValues := make([]llvm.Value, len(bitmapBytes))
|
|
||||||
for i, b := range bitmapBytes {
|
|
||||||
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
|
|
||||||
}
|
|
||||||
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
|
|
||||||
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
|
||||||
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
|
||||||
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
|
|
||||||
bitmapNew.SetInitializer(bitmapArray)
|
|
||||||
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
|
||||||
|
|
||||||
return true // the IR was changed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
|
|
||||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
|
||||||
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 := c.getPointerBitmap(subtyp, name)
|
|
||||||
if subptrs.BitLen() == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
offset := c.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 := c.getPointerBitmap(subtyp, name)
|
|
||||||
ptrs := big.NewInt(0)
|
|
||||||
if subptrs.BitLen() == 0 {
|
|
||||||
return ptrs
|
|
||||||
}
|
|
||||||
elementSize := c.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.
|
|
||||||
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
|
|
||||||
worklist := []llvm.Value{fn}
|
|
||||||
for len(worklist) != 0 {
|
|
||||||
fn := worklist[len(worklist)-1]
|
|
||||||
worklist = worklist[:len(worklist)-1]
|
|
||||||
for _, use := range getUses(fn) {
|
|
||||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
|
||||||
// Not the parent function.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parent := use.InstructionParent().Parent()
|
|
||||||
if _, ok := marked[parent]; !ok {
|
|
||||||
marked[parent] = struct{}{}
|
|
||||||
worklist = append(worklist, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -137,8 +137,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
hasGCPass := c.addGlobalsBitmap()
|
hasGCPass := transform.AddGlobalsBitmap(c.mod)
|
||||||
hasGCPass = c.makeGCStackSlots() || hasGCPass
|
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||||
if hasGCPass {
|
if hasGCPass {
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("GC pass caused a verification failure")
|
return errors.New("GC pass caused a verification failure")
|
||||||
|
|
398
transform/gc.go
Обычный файл
398
transform/gc.go
Обычный файл
|
@ -0,0 +1,398 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeGCStackSlots converts all calls to runtime.trackPointer to explicit
|
||||||
|
// stores to stack slots that are scannable by the GC.
|
||||||
|
func MakeGCStackSlots(mod llvm.Module) bool {
|
||||||
|
// Check whether there are allocations at all.
|
||||||
|
alloc := mod.NamedFunction("runtime.alloc")
|
||||||
|
if alloc.IsNil() {
|
||||||
|
// Nothing to. Make sure all remaining bits and pieces for stack
|
||||||
|
// chains are neutralized.
|
||||||
|
for _, call := range getUses(mod.NamedFunction("runtime.trackPointer")) {
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
stackChainStart := mod.NamedGlobal("runtime.stackChainStart")
|
||||||
|
if !stackChainStart.IsNil() {
|
||||||
|
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
|
||||||
|
stackChainStart.SetGlobalConstant(true)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||||
|
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
|
||||||
|
return false // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := mod.Context()
|
||||||
|
builder := ctx.NewBuilder()
|
||||||
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
|
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
|
||||||
|
|
||||||
|
// Look at *all* functions to see whether they are free of function pointer
|
||||||
|
// calls.
|
||||||
|
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
|
||||||
|
// faster when written in C++ (to avoid the CGo overhead).
|
||||||
|
funcsWithFPCall := map[llvm.Value]struct{}{}
|
||||||
|
n := 0
|
||||||
|
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
|
n++
|
||||||
|
if _, ok := funcsWithFPCall[fn]; ok {
|
||||||
|
continue // already found
|
||||||
|
}
|
||||||
|
done := false
|
||||||
|
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
|
||||||
|
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
|
||||||
|
if call.IsACallInst().IsNil() {
|
||||||
|
continue // only looking at calls
|
||||||
|
}
|
||||||
|
called := call.CalledValue()
|
||||||
|
if !called.IsAFunction().IsNil() {
|
||||||
|
continue // only looking for function pointers
|
||||||
|
}
|
||||||
|
funcsWithFPCall[fn] = struct{}{}
|
||||||
|
markParentFunctions(funcsWithFPCall, fn)
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which functions need stack objects. Many leaf functions don't
|
||||||
|
// need it: it only causes overhead for them.
|
||||||
|
// Actually, in one test it was only able to eliminate stack object from 12%
|
||||||
|
// of functions that had a call to runtime.trackPointer (8 out of 68
|
||||||
|
// functions), so this optimization is not as big as it may seem.
|
||||||
|
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
|
||||||
|
|
||||||
|
// Work from runtime.alloc and trace all parents to check which functions do
|
||||||
|
// a heap allocation (and thus which functions do not).
|
||||||
|
markParentFunctions(allocatingFunctions, alloc)
|
||||||
|
|
||||||
|
// Also trace all functions that call a function pointer.
|
||||||
|
for fn := range funcsWithFPCall {
|
||||||
|
// Assume that functions that call a function pointer do a heap
|
||||||
|
// allocation as a conservative guess because the called function might
|
||||||
|
// do a heap allocation.
|
||||||
|
allocatingFunctions[fn] = struct{}{}
|
||||||
|
markParentFunctions(allocatingFunctions, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect some variables used below in the loop.
|
||||||
|
stackChainStart := mod.NamedGlobal("runtime.stackChainStart")
|
||||||
|
if stackChainStart.IsNil() {
|
||||||
|
// This may be reached in a weird scenario where we call runtime.alloc but the garbage collector is unreachable.
|
||||||
|
// This can be accomplished by allocating 0 bytes.
|
||||||
|
// There is no point in tracking anything.
|
||||||
|
for _, use := range getUses(trackPointer) {
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
stackChainStartType := stackChainStart.Type().ElementType()
|
||||||
|
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
|
||||||
|
|
||||||
|
// Iterate until runtime.trackPointer has no uses left.
|
||||||
|
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
|
||||||
|
// Pick the first use of runtime.trackPointer.
|
||||||
|
call := use.User()
|
||||||
|
if call.IsACallInst().IsNil() {
|
||||||
|
panic("expected runtime.trackPointer use to be a call")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick the parent function.
|
||||||
|
fn := call.InstructionParent().Parent()
|
||||||
|
|
||||||
|
if _, ok := allocatingFunctions[fn]; !ok {
|
||||||
|
// This function nor any of the functions it calls (recursively)
|
||||||
|
// allocate anything from the heap, so it will not trigger a garbage
|
||||||
|
// collection cycle. Thus, it does not need to track local pointer
|
||||||
|
// values.
|
||||||
|
// This is a useful optimization but not as big as you might guess,
|
||||||
|
// as described above (it avoids stack objects for ~12% of
|
||||||
|
// functions).
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all calls to runtime.trackPointer in this function.
|
||||||
|
var calls []llvm.Value
|
||||||
|
var returns []llvm.Value
|
||||||
|
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
|
switch inst.InstructionOpcode() {
|
||||||
|
case llvm.Call:
|
||||||
|
if inst.CalledValue() == trackPointer {
|
||||||
|
calls = append(calls, inst)
|
||||||
|
}
|
||||||
|
case llvm.Ret:
|
||||||
|
returns = append(returns, inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine what to do with each call.
|
||||||
|
var allocas, pointers []llvm.Value
|
||||||
|
for _, call := range calls {
|
||||||
|
ptr := call.Operand(0)
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
if ptr.IsAInstruction().IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some trivial optimizations.
|
||||||
|
if ptr.IsAInstruction().IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch ptr.InstructionOpcode() {
|
||||||
|
case llvm.PHI, llvm.GetElementPtr:
|
||||||
|
// These values do not create new values: the values already
|
||||||
|
// existed locally in this function so must have been tracked
|
||||||
|
// already.
|
||||||
|
continue
|
||||||
|
case llvm.ExtractValue, llvm.BitCast:
|
||||||
|
// These instructions do not create new values, but their
|
||||||
|
// original value may not be tracked. So keep tracking them for
|
||||||
|
// now.
|
||||||
|
// With more analysis, it should be possible to optimize a
|
||||||
|
// significant chunk of these away.
|
||||||
|
case llvm.Call, llvm.Load, llvm.IntToPtr:
|
||||||
|
// These create new values so must be stored locally. But
|
||||||
|
// perhaps some of these can be fused when they actually refer
|
||||||
|
// to the same value.
|
||||||
|
default:
|
||||||
|
// Ambiguous. These instructions are uncommon, but perhaps could
|
||||||
|
// be optimized if needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ptr.IsAAllocaInst().IsNil() {
|
||||||
|
if typeHasPointers(ptr.Type().ElementType()) {
|
||||||
|
allocas = append(allocas, ptr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pointers = append(pointers, ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allocas) == 0 && len(pointers) == 0 {
|
||||||
|
// This function does not need to keep track of stack pointers.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the type of the required stack slot.
|
||||||
|
fields := []llvm.Type{
|
||||||
|
stackChainStartType, // Pointer to parent frame.
|
||||||
|
uintptrType, // Number of elements in this frame.
|
||||||
|
}
|
||||||
|
for _, alloca := range allocas {
|
||||||
|
fields = append(fields, alloca.Type().ElementType())
|
||||||
|
}
|
||||||
|
for _, ptr := range pointers {
|
||||||
|
fields = append(fields, ptr.Type())
|
||||||
|
}
|
||||||
|
stackObjectType := ctx.StructType(fields, false)
|
||||||
|
|
||||||
|
// Create the stack object at the function entry.
|
||||||
|
builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||||
|
stackObject := builder.CreateAlloca(stackObjectType, "gc.stackobject")
|
||||||
|
initialStackObject := llvm.ConstNull(stackObjectType)
|
||||||
|
numSlots := (targetData.TypeAllocSize(stackObjectType) - uint64(targetData.PointerSize())*2) / uint64(targetData.ABITypeAlignment(uintptrType))
|
||||||
|
numSlotsValue := llvm.ConstInt(uintptrType, numSlots, false)
|
||||||
|
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
|
||||||
|
builder.CreateStore(initialStackObject, stackObject)
|
||||||
|
|
||||||
|
// Update stack start.
|
||||||
|
parent := builder.CreateLoad(stackChainStart, "")
|
||||||
|
gep := builder.CreateGEP(stackObject, []llvm.Value{
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
}, "")
|
||||||
|
builder.CreateStore(parent, gep)
|
||||||
|
stackObjectCast := builder.CreateBitCast(stackObject, stackChainStartType, "")
|
||||||
|
builder.CreateStore(stackObjectCast, stackChainStart)
|
||||||
|
|
||||||
|
// Replace all independent allocas with GEPs in the stack object.
|
||||||
|
for i, alloca := range allocas {
|
||||||
|
gep := builder.CreateGEP(stackObject, []llvm.Value{
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), uint64(2+i), false),
|
||||||
|
}, "")
|
||||||
|
alloca.ReplaceAllUsesWith(gep)
|
||||||
|
alloca.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a store to the stack object after each new pointer that is created.
|
||||||
|
for i, ptr := range pointers {
|
||||||
|
builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
|
||||||
|
gep := builder.CreateGEP(stackObject, []llvm.Value{
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
|
llvm.ConstInt(ctx.Int32Type(), uint64(2+len(allocas)+i), false),
|
||||||
|
}, "")
|
||||||
|
builder.CreateStore(ptr, gep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure this stack object is popped from the linked list of stack
|
||||||
|
// objects at return.
|
||||||
|
for _, ret := range returns {
|
||||||
|
builder.SetInsertPointBefore(ret)
|
||||||
|
builder.CreateStore(parent, stackChainStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
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() {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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").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")
|
||||||
|
|
||||||
|
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.
|
||||||
|
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
|
||||||
|
worklist := []llvm.Value{fn}
|
||||||
|
for len(worklist) != 0 {
|
||||||
|
fn := worklist[len(worklist)-1]
|
||||||
|
worklist = worklist[:len(worklist)-1]
|
||||||
|
for _, use := range getUses(fn) {
|
||||||
|
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||||
|
// Not the parent function.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent := use.InstructionParent().Parent()
|
||||||
|
if _, ok := marked[parent]; !ok {
|
||||||
|
marked[parent] = struct{}{}
|
||||||
|
worklist = append(worklist, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
transform/gc_test.go
Обычный файл
21
transform/gc_test.go
Обычный файл
|
@ -0,0 +1,21 @@
|
||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddGlobalsBitmap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testTransform(t, "testdata/gc-globals", func(mod llvm.Module) {
|
||||||
|
AddGlobalsBitmap(mod)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeGCStackSlots(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testTransform(t, "testdata/gc-stackslots", func(mod llvm.Module) {
|
||||||
|
MakeGCStackSlots(mod)
|
||||||
|
})
|
||||||
|
}
|
|
@ -65,3 +65,27 @@ func replaceGlobalIntWithArray(mod llvm.Module, name string, buf interface{}) ll
|
||||||
global.SetName(name)
|
global.SetName(name)
|
||||||
return global
|
return global
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// typeHasPointers returns whether this type is a pointer or contains pointers.
|
||||||
|
// If the type is an aggregate type, it will check whether there is a pointer
|
||||||
|
// inside.
|
||||||
|
func typeHasPointers(t llvm.Type) bool {
|
||||||
|
switch t.TypeKind() {
|
||||||
|
case llvm.PointerTypeKind:
|
||||||
|
return true
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
for _, subType := range t.StructElementTypes() {
|
||||||
|
if typeHasPointers(subType) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case llvm.ArrayTypeKind:
|
||||||
|
if typeHasPointers(t.ElementType()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
29
transform/testdata/gc-globals.ll
предоставленный
Обычный файл
29
transform/testdata/gc-globals.ll
предоставленный
Обычный файл
|
@ -0,0 +1,29 @@
|
||||||
|
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 = constant i32 5
|
||||||
|
@globalString = constant %runtime._string zeroinitializer
|
||||||
|
@globalInterface = 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
|
||||||
|
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
|
||||||
|
}
|
27
transform/testdata/gc-globals.out.ll
предоставленный
Обычный файл
27
transform/testdata/gc-globals.out.ll
предоставленный
Обычный файл
|
@ -0,0 +1,27 @@
|
||||||
|
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 = constant i32 5
|
||||||
|
@runtime.trackedGlobalsLength = global i32 4
|
||||||
|
@runtime.trackedGlobalsBitmap = external global [0 x i8]
|
||||||
|
@runtime.trackedGlobalsStart = 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 = global [1 x i8] c"\09"
|
||||||
|
|
||||||
|
define void @main() {
|
||||||
|
%1 = load i32, i32* @globalInt
|
||||||
|
%2 = load %runtime._string, %runtime._string* getelementptr inbounds ({ %runtime._string, %runtime._interface }, { %runtime._string, %runtime._interface }* @tinygo.trackedGlobals, i32 0, i32 0)
|
||||||
|
%3 = load %runtime._interface, %runtime._interface* getelementptr inbounds ({ %runtime._string, %runtime._interface }, { %runtime._string, %runtime._interface }* @tinygo.trackedGlobals, i32 0, i32 1)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @runtime.markGlobals() {
|
||||||
|
%1 = load i32, i32* @runtime.trackedGlobalsStart
|
||||||
|
%2 = load i32, i32* @runtime.trackedGlobalsLength
|
||||||
|
%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
|
||||||
|
ret void
|
||||||
|
}
|
52
transform/testdata/gc-stackslots.ll
предоставленный
Обычный файл
52
transform/testdata/gc-stackslots.ll
предоставленный
Обычный файл
|
@ -0,0 +1,52 @@
|
||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32-unknown-unknown-wasm"
|
||||||
|
|
||||||
|
%runtime.stackChainObject = type { %runtime.stackChainObject*, i32 }
|
||||||
|
|
||||||
|
@runtime.stackChainStart = external global %runtime.stackChainObject*
|
||||||
|
@someGlobal = global i8 3
|
||||||
|
|
||||||
|
declare void @runtime.trackPointer(i8* nocapture readonly)
|
||||||
|
|
||||||
|
declare noalias nonnull i8* @runtime.alloc(i32)
|
||||||
|
|
||||||
|
; Generic function that returns a pointer (that must be tracked).
|
||||||
|
define i8* @getPointer() {
|
||||||
|
ret i8* @someGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
define i8* @needsStackSlots() {
|
||||||
|
; Tracked pointer. Although, in this case the value is immediately returned
|
||||||
|
; so tracking it is not really necessary.
|
||||||
|
%ptr = call i8* @runtime.alloc(i32 4)
|
||||||
|
call void @runtime.trackPointer(i8* %ptr)
|
||||||
|
ret i8* %ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
; Check some edge cases of pointer tracking.
|
||||||
|
define i8* @needsStackSlots2() {
|
||||||
|
; Only one stack slot should be created for this (but at the moment, one is
|
||||||
|
; created for each call to runtime.trackPointer).
|
||||||
|
%ptr1 = call i8* @getPointer()
|
||||||
|
call void @runtime.trackPointer(i8* %ptr1)
|
||||||
|
call void @runtime.trackPointer(i8* %ptr1)
|
||||||
|
call void @runtime.trackPointer(i8* %ptr1)
|
||||||
|
|
||||||
|
; Create a pointer that does not need to be tracked (but is tracked).
|
||||||
|
%ptr2 = getelementptr i8, i8* @someGlobal, i32 0
|
||||||
|
call void @runtime.trackPointer(i8* %ptr2)
|
||||||
|
|
||||||
|
; Here is finally the point where an allocation happens.
|
||||||
|
%unused = call i8* @runtime.alloc(i32 4)
|
||||||
|
call void @runtime.trackPointer(i8* %unused)
|
||||||
|
|
||||||
|
ret i8* %ptr1
|
||||||
|
}
|
||||||
|
|
||||||
|
; Return a pointer from a caller. Because it doesn't allocate, no stack objects
|
||||||
|
; need to be created.
|
||||||
|
define i8* @noAllocatingFunction() {
|
||||||
|
%ptr = call i8* @getPointer()
|
||||||
|
call void @runtime.trackPointer(i8* %ptr)
|
||||||
|
ret i8* %ptr
|
||||||
|
}
|
58
transform/testdata/gc-stackslots.out.ll
предоставленный
Обычный файл
58
transform/testdata/gc-stackslots.out.ll
предоставленный
Обычный файл
|
@ -0,0 +1,58 @@
|
||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32-unknown-unknown-wasm"
|
||||||
|
|
||||||
|
%runtime.stackChainObject = type { %runtime.stackChainObject*, i32 }
|
||||||
|
|
||||||
|
@runtime.stackChainStart = global %runtime.stackChainObject* null
|
||||||
|
@someGlobal = global i8 3
|
||||||
|
|
||||||
|
declare void @runtime.trackPointer(i8* nocapture readonly)
|
||||||
|
|
||||||
|
declare noalias nonnull i8* @runtime.alloc(i32)
|
||||||
|
|
||||||
|
define i8* @getPointer() {
|
||||||
|
ret i8* @someGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
define i8* @needsStackSlots() {
|
||||||
|
%gc.stackobject = alloca { %runtime.stackChainObject*, i32, i8* }
|
||||||
|
store { %runtime.stackChainObject*, i32, i8* } { %runtime.stackChainObject* null, i32 1, i8* null }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject
|
||||||
|
%1 = load %runtime.stackChainObject*, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
%2 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 0
|
||||||
|
store %runtime.stackChainObject* %1, %runtime.stackChainObject** %2
|
||||||
|
%3 = bitcast { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject to %runtime.stackChainObject*
|
||||||
|
store %runtime.stackChainObject* %3, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
%ptr = call i8* @runtime.alloc(i32 4)
|
||||||
|
%4 = getelementptr { %runtime.stackChainObject*, i32, i8* }, { %runtime.stackChainObject*, i32, i8* }* %gc.stackobject, i32 0, i32 2
|
||||||
|
store i8* %ptr, i8** %4
|
||||||
|
store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
ret i8* %ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
define i8* @needsStackSlots2() {
|
||||||
|
%gc.stackobject = alloca { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }
|
||||||
|
store { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* } { %runtime.stackChainObject* null, i32 4, i8* null, i8* null, i8* null, i8* null }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject
|
||||||
|
%1 = load %runtime.stackChainObject*, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
%2 = getelementptr { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject, i32 0, i32 0
|
||||||
|
store %runtime.stackChainObject* %1, %runtime.stackChainObject** %2
|
||||||
|
%3 = bitcast { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject to %runtime.stackChainObject*
|
||||||
|
store %runtime.stackChainObject* %3, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
%ptr1 = call i8* @getPointer()
|
||||||
|
%4 = getelementptr { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject, i32 0, i32 4
|
||||||
|
store i8* %ptr1, i8** %4
|
||||||
|
%5 = getelementptr { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject, i32 0, i32 3
|
||||||
|
store i8* %ptr1, i8** %5
|
||||||
|
%6 = getelementptr { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject, i32 0, i32 2
|
||||||
|
store i8* %ptr1, i8** %6
|
||||||
|
%ptr2 = getelementptr i8, i8* @someGlobal, i32 0
|
||||||
|
%unused = call i8* @runtime.alloc(i32 4)
|
||||||
|
%7 = getelementptr { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }, { %runtime.stackChainObject*, i32, i8*, i8*, i8*, i8* }* %gc.stackobject, i32 0, i32 5
|
||||||
|
store i8* %unused, i8** %7
|
||||||
|
store %runtime.stackChainObject* %1, %runtime.stackChainObject** @runtime.stackChainStart
|
||||||
|
ret i8* %ptr1
|
||||||
|
}
|
||||||
|
|
||||||
|
define i8* @noAllocatingFunction() {
|
||||||
|
%ptr = call i8* @getPointer()
|
||||||
|
ret i8* %ptr
|
||||||
|
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче