From 385d1d0a5d5c8713ca5eaf35fd6dffd1f3b0ed55 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 14 May 2019 15:18:05 +0200 Subject: [PATCH] compiler,runtime: implement a portable conservative GC --- compiler/compiler.go | 19 +- compiler/defer.go | 3 + compiler/gc.go | 367 +++++++++++++++++++++++++ compiler/optimizer.go | 8 + interp/frame.go | 16 ++ interp/scan.go | 2 + src/runtime/gc_conservative.go | 30 +- src/runtime/gc_globals_conservative.go | 12 + src/runtime/gc_globals_precise.go | 35 +++ src/runtime/gc_stack_portable.go | 39 +++ src/runtime/gc_stack_raw.go | 13 + targets/avr.json | 1 + 12 files changed, 528 insertions(+), 17 deletions(-) create mode 100644 compiler/gc.go create mode 100644 src/runtime/gc_globals_conservative.go create mode 100644 src/runtime/gc_globals_precise.go create mode 100644 src/runtime/gc_stack_portable.go create mode 100644 src/runtime/gc_stack_raw.go diff --git a/compiler/compiler.go b/compiler/compiler.go index d82c00f2..ec13e5d2 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -167,11 +167,10 @@ func (c *Compiler) TargetData() llvm.TargetData { // selectGC picks an appropriate GC strategy if none was provided. func (c *Compiler) selectGC() string { - gc := c.GC - if gc == "" { - gc = "leaking" + if c.GC != "" { + return c.GC } - return gc + return "conservative" } // Compile the given package path or .go file path. Return an error when this @@ -362,6 +361,15 @@ func (c *Compiler) Compile(mainPath string) []error { // See emitNilCheck in asserts.go. c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture) + // This function is necessary for tracking pointers on the stack in a + // portable way (see gc.go). Indicate to the optimizer that the only thing + // we'll do is read the pointer. + trackPointer := c.mod.NamedFunction("runtime.trackPointer") + if !trackPointer.IsNil() { + trackPointer.AddAttributeAtIndex(1, nocapture) + trackPointer.AddAttributeAtIndex(1, readonly) + } + // Memory copy operations do not capture pointers, even though some weird // pointer arithmetic is happening in the Go implementation. for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} { @@ -1009,6 +1017,9 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type())) } else { frame.locals[instr] = value + if len(*instr.Referrers()) != 0 && c.needsStackObjects() { + c.trackExpr(frame, instr, value) + } } case *ssa.DebugRef: // ignore diff --git a/compiler/defer.go b/compiler/defer.go index 21fff3aa..d929ea57 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -130,6 +130,9 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { // Put this struct in an alloca. alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca") c.builder.CreateStore(deferFrame, alloca) + if c.needsStackObjects() { + c.trackPointer(alloca) + } // Push it on top of the linked list by replacing deferPtr. allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") diff --git a/compiler/gc.go b/compiler/gc.go new file mode 100644 index 00000000..6b523299 --- /dev/null +++ b/compiler/gc.go @@ -0,0 +1,367 @@ +package compiler + +// This file provides IR transformations necessary for precise and portable +// garbage collectors. + +import ( + "go/token" + "math/big" + + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +// needsStackObjects returns true if the compiler should insert stack objects +// that can be traced by the garbage collector. +func (c *Compiler) needsStackObjects() bool { + if c.selectGC() != "conservative" { + return false + } + for _, tag := range c.BuildTags { + if tag == "cortexm" { + return false + } + } + + return true +} + +// trackExpr inserts pointer tracking intrinsics for the GC if the expression is +// one of the expressions that need this. +func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) { + // There are uses of this expression, Make sure the pointers + // are tracked during GC. + switch expr := expr.(type) { + case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap: + // These values are always of pointer type in IR. + c.trackPointer(value) + case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next: + if !value.IsNil() { + c.trackValue(value) + } + case *ssa.Select: + if alloca, ok := frame.selectRecvBuf[expr]; ok { + if alloca.IsAUndefValue().IsNil() { + c.trackPointer(alloca) + } + } + case *ssa.UnOp: + switch expr.Op { + case token.MUL: + // Pointer dereference. + c.trackValue(value) + case token.ARROW: + // Channel receive operator. + // It's not necessary to look at commaOk here, because in that + // case it's just an aggregate and trackValue will extract the + // pointer in there (if there is one). + c.trackValue(value) + } + } +} + +// trackValue locates pointers in a value (possibly an aggregate) and tracks the +// individual pointers +func (c *Compiler) trackValue(value llvm.Value) { + typ := value.Type() + switch typ.TypeKind() { + case llvm.PointerTypeKind: + c.trackPointer(value) + case llvm.StructTypeKind: + if !typeHasPointers(typ) { + return + } + numElements := typ.StructElementTypesCount() + for i := 0; i < numElements; i++ { + subValue := c.builder.CreateExtractValue(value, i, "") + c.trackValue(subValue) + } + case llvm.ArrayTypeKind: + if !typeHasPointers(typ) { + return + } + numElements := typ.ArrayLength() + for i := 0; i < numElements; i++ { + subValue := c.builder.CreateExtractValue(value, i, "") + c.trackValue(subValue) + } + } +} + +// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner +// first if needed. The input value must be of LLVM pointer type. +func (c *Compiler) trackPointer(value llvm.Value) { + if value.Type() != c.i8ptrType { + value = c.builder.CreateBitCast(value, c.i8ptrType, "") + } + c.createRuntimeCall("trackPointer", []llvm.Value{value}, "") +} + +// 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 + } +} + +// makeGCStackSlots converts all calls to runtime.trackPointer to explicit +// stores to stack slots that are scannable by the GC. +func (c *Compiler) makeGCStackSlots() bool { + if c.mod.NamedFunction("runtime.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(c.getZeroValue(stackChainStart.Type().ElementType())) + stackChainStart.SetGlobalConstant(true) + } + } + + trackPointer := c.mod.NamedFunction("runtime.trackPointer") + if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() { + return false // nothing to do + } + + // Collect some variables used below in the loop. + stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart") + if stackChainStart.IsNil() { + panic("stack chain start not found!") + } + stackChainStartType := stackChainStart.Type().ElementType() + stackChainStart.SetInitializer(c.getZeroValue(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() + + // 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() || !ptr.IsAPHINode().IsNil() { + continue + } + + 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 := c.getZeroValue(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(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues) + bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp") + bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap") + bitmapOld.ReplaceAllUsesWith(bitmapNew) + 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) + } +} diff --git a/compiler/optimizer.go b/compiler/optimizer.go index 4efea247..10ae1a8a 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -114,6 +114,14 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro builder.Populate(modPasses) modPasses.Run(c.mod) + hasGCPass := c.addGlobalsBitmap() + hasGCPass = c.makeGCStackSlots() || hasGCPass + if hasGCPass { + if err := c.Verify(); err != nil { + return errors.New("GC pass caused a verification failure") + } + } + return nil } diff --git a/interp/frame.go b/interp/frame.go index 60d2cb7c..81432ef3 100644 --- a/interp/frame.go +++ b/interp/frame.go @@ -176,6 +176,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re continue // special case: bitcast of alloc } } + if _, ok := fr.getLocal(operand).(*MapValue); ok { + // Special case for runtime.trackPointer calls. + // Note: this might not be entirely sound in some rare cases + // where the map is stored in a dirty global. + uses := getUses(inst) + if len(uses) == 1 { + use := uses[0] + if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" { + continue + } + } + // It is not possible in Go to bitcast a map value to a pointer. + panic("unimplemented: bitcast of map") + } value := fr.getLocal(operand).(*LocalValue) fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")} @@ -368,6 +382,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)} case callee.Name() == "llvm.dbg.value": // do nothing + case callee.Name() == "runtime.trackPointer": + // do nothing case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": // This are all print instructions, which necessarily have side // effects but no results. diff --git a/interp/scan.go b/interp/scan.go index 1d5476ab..1d95bcff 100644 --- a/interp/scan.go +++ b/interp/scan.go @@ -35,6 +35,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult { return &sideEffectResult{severity: sideEffectLimited} case "runtime.interfaceImplements": return &sideEffectResult{severity: sideEffectNone} + case "runtime.trackPointer": + return &sideEffectResult{severity: sideEffectNone} case "llvm.dbg.value": return &sideEffectResult{severity: sideEffectNone} } diff --git a/src/runtime/gc_conservative.go b/src/runtime/gc_conservative.go index dd1e5211..c8cb535a 100644 --- a/src/runtime/gc_conservative.go +++ b/src/runtime/gc_conservative.go @@ -282,8 +282,8 @@ func GC() { } // Mark phase: mark all reachable objects, recursively. - markRoots(globalsStart, globalsEnd) - markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack + markGlobals() + markStack() // Sweep phase: free all non-marked objects and unmark marked objects for // the next collection cycle. @@ -311,18 +311,22 @@ func markRoots(start, end uintptr) { for addr := start; addr != end; addr += unsafe.Sizeof(addr) { root := *(*uintptr)(unsafe.Pointer(addr)) - if looksLikePointer(root) { - block := blockFromAddr(root) - head := block.findHead() - if head.state() != blockStateMark { - if gcDebug { - println("found unmarked pointer", root, "at address", addr) - } - head.setState(blockStateMark) - next := block.findNext() - // TODO: avoid recursion as much as possible - markRoots(head.address(), next.address()) + markRoot(addr, root) + } +} + +func markRoot(addr, root uintptr) { + if looksLikePointer(root) { + block := blockFromAddr(root) + head := block.findHead() + if head.state() != blockStateMark { + if gcDebug { + println("found unmarked pointer", root, "at address", addr) } + head.setState(blockStateMark) + next := block.findNext() + // TODO: avoid recursion as much as possible + markRoots(head.address(), next.address()) } } } diff --git a/src/runtime/gc_globals_conservative.go b/src/runtime/gc_globals_conservative.go new file mode 100644 index 00000000..6efbd3f9 --- /dev/null +++ b/src/runtime/gc_globals_conservative.go @@ -0,0 +1,12 @@ +// +build gc.conservative +// +build cortexm + +package runtime + +// 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() { + markRoots(globalsStart, globalsEnd) +} diff --git a/src/runtime/gc_globals_precise.go b/src/runtime/gc_globals_precise.go new file mode 100644 index 00000000..9ebcfa82 --- /dev/null +++ b/src/runtime/gc_globals_precise.go @@ -0,0 +1,35 @@ +// +build gc.conservative +// +build !cortexm + +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) + } + } +} diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go new file mode 100644 index 00000000..97c3e93a --- /dev/null +++ b/src/runtime/gc_stack_portable.go @@ -0,0 +1,39 @@ +// +build gc.conservative +// +build !cortexm + +package runtime + +import ( + "unsafe" +) + +//go:extern runtime.stackChainStart +var stackChainStart *stackChainObject + +type stackChainObject struct { + parent *stackChainObject + numSlots uintptr +} + +// markStack marks all root pointers found on the stack. +// +// This implementation is conservative and relies on the compiler inserting code +// to manually push/pop stack objects that are stored in a linked list starting +// with stackChainStart. Manually keeping track of stack values is _much_ more +// expensive than letting the compiler do it and it inhibits a few important +// optimizations, but it has the big advantage of being portable to basically +// any ISA, including WebAssembly. +func markStack() { + stackObject := stackChainStart + for stackObject != nil { + start := uintptr(unsafe.Pointer(stackObject)) + unsafe.Sizeof(uintptr(0))*2 + end := start + stackObject.numSlots*unsafe.Alignof(uintptr(0)) + markRoots(start, end) + stackObject = stackObject.parent + } +} + +// trackPointer is a stub function call inserted by the compiler during IR +// construction. Calls to it are later replaced with regular stack bookkeeping +// code. +func trackPointer(ptr unsafe.Pointer) diff --git a/src/runtime/gc_stack_raw.go b/src/runtime/gc_stack_raw.go new file mode 100644 index 00000000..e899eb7c --- /dev/null +++ b/src/runtime/gc_stack_raw.go @@ -0,0 +1,13 @@ +// +build gc.conservative +// +build cortexm + +package runtime + +// markStack marks all root pointers found on the stack. +// +// This implementation is conservative and relies on the stack top (provided by +// the linker) and getting the current stack pointer from a register. Also, it +// assumes a descending stack. Thus, it is not very portable. +func markStack() { + markRoots(getCurrentStackPointer(), stackTop) +} diff --git a/targets/avr.json b/targets/avr.json index 9e0f1a99..a7a3773f 100644 --- a/targets/avr.json +++ b/targets/avr.json @@ -3,6 +3,7 @@ "goos": "linux", "goarch": "arm", "compiler": "avr-gcc", + "gc": "leaking", "linker": "avr-gcc", "ldflags": [ "-T", "targets/avr.ld",