Skip to content

Commit f0bb3c0

Browse files
aykevldeadprogram
authored andcommitted
compiler: move GC passes to the transform package
1 parent 3d3e481 commit f0bb3c0

File tree

9 files changed

+611
-359
lines changed

9 files changed

+611
-359
lines changed

compiler/gc.go

Lines changed: 0 additions & 357 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package compiler
55

66
import (
77
"go/token"
8-
"math/big"
98

109
"golang.org/x/tools/go/ssa"
1110
"tinygo.org/x/go-llvm"
@@ -105,359 +104,3 @@ func typeHasPointers(t llvm.Type) bool {
105104
return false
106105
}
107106
}
108-
109-
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
110-
// stores to stack slots that are scannable by the GC.
111-
func (c *Compiler) makeGCStackSlots() bool {
112-
// Check whether there are allocations at all.
113-
alloc := c.mod.NamedFunction("runtime.alloc")
114-
if alloc.IsNil() {
115-
// Nothing to. Make sure all remaining bits and pieces for stack
116-
// chains are neutralized.
117-
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
118-
call.EraseFromParentAsInstruction()
119-
}
120-
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
121-
if !stackChainStart.IsNil() {
122-
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
123-
stackChainStart.SetGlobalConstant(true)
124-
}
125-
return false
126-
}
127-
128-
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
129-
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
130-
return false // nothing to do
131-
}
132-
133-
// Look at *all* functions to see whether they are free of function pointer
134-
// calls.
135-
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
136-
// faster when written in C++ (to avoid the CGo overhead).
137-
funcsWithFPCall := map[llvm.Value]struct{}{}
138-
n := 0
139-
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
140-
n++
141-
if _, ok := funcsWithFPCall[fn]; ok {
142-
continue // already found
143-
}
144-
done := false
145-
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
146-
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
147-
if call.IsACallInst().IsNil() {
148-
continue // only looking at calls
149-
}
150-
called := call.CalledValue()
151-
if !called.IsAFunction().IsNil() {
152-
continue // only looking for function pointers
153-
}
154-
funcsWithFPCall[fn] = struct{}{}
155-
markParentFunctions(funcsWithFPCall, fn)
156-
done = true
157-
}
158-
}
159-
}
160-
161-
// Determine which functions need stack objects. Many leaf functions don't
162-
// need it: it only causes overhead for them.
163-
// Actually, in one test it was only able to eliminate stack object from 12%
164-
// of functions that had a call to runtime.trackPointer (8 out of 68
165-
// functions), so this optimization is not as big as it may seem.
166-
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
167-
168-
// Work from runtime.alloc and trace all parents to check which functions do
169-
// a heap allocation (and thus which functions do not).
170-
markParentFunctions(allocatingFunctions, alloc)
171-
172-
// Also trace all functions that call a function pointer.
173-
for fn := range funcsWithFPCall {
174-
// Assume that functions that call a function pointer do a heap
175-
// allocation as a conservative guess because the called function might
176-
// do a heap allocation.
177-
allocatingFunctions[fn] = struct{}{}
178-
markParentFunctions(allocatingFunctions, fn)
179-
}
180-
181-
// Collect some variables used below in the loop.
182-
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
183-
if stackChainStart.IsNil() {
184-
// This may be reached in a weird scenario where we call runtime.alloc but the garbage collector is unreachable.
185-
// This can be accomplished by allocating 0 bytes.
186-
// There is no point in tracking anything.
187-
for _, use := range getUses(trackPointer) {
188-
use.EraseFromParentAsInstruction()
189-
}
190-
return false
191-
}
192-
stackChainStartType := stackChainStart.Type().ElementType()
193-
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
194-
195-
// Iterate until runtime.trackPointer has no uses left.
196-
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
197-
// Pick the first use of runtime.trackPointer.
198-
call := use.User()
199-
if call.IsACallInst().IsNil() {
200-
panic("expected runtime.trackPointer use to be a call")
201-
}
202-
203-
// Pick the parent function.
204-
fn := call.InstructionParent().Parent()
205-
206-
if _, ok := allocatingFunctions[fn]; !ok {
207-
// This function nor any of the functions it calls (recursively)
208-
// allocate anything from the heap, so it will not trigger a garbage
209-
// collection cycle. Thus, it does not need to track local pointer
210-
// values.
211-
// This is a useful optimization but not as big as you might guess,
212-
// as described above (it avoids stack objects for ~12% of
213-
// functions).
214-
call.EraseFromParentAsInstruction()
215-
continue
216-
}
217-
218-
// Find all calls to runtime.trackPointer in this function.
219-
var calls []llvm.Value
220-
var returns []llvm.Value
221-
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
222-
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
223-
switch inst.InstructionOpcode() {
224-
case llvm.Call:
225-
if inst.CalledValue() == trackPointer {
226-
calls = append(calls, inst)
227-
}
228-
case llvm.Ret:
229-
returns = append(returns, inst)
230-
}
231-
}
232-
}
233-
234-
// Determine what to do with each call.
235-
var allocas, pointers []llvm.Value
236-
for _, call := range calls {
237-
ptr := call.Operand(0)
238-
call.EraseFromParentAsInstruction()
239-
if ptr.IsAInstruction().IsNil() {
240-
continue
241-
}
242-
243-
// Some trivial optimizations.
244-
if ptr.IsAInstruction().IsNil() {
245-
continue
246-
}
247-
switch ptr.InstructionOpcode() {
248-
case llvm.PHI, llvm.GetElementPtr:
249-
// These values do not create new values: the values already
250-
// existed locally in this function so must have been tracked
251-
// already.
252-
continue
253-
case llvm.ExtractValue, llvm.BitCast:
254-
// These instructions do not create new values, but their
255-
// original value may not be tracked. So keep tracking them for
256-
// now.
257-
// With more analysis, it should be possible to optimize a
258-
// significant chunk of these away.
259-
case llvm.Call, llvm.Load, llvm.IntToPtr:
260-
// These create new values so must be stored locally. But
261-
// perhaps some of these can be fused when they actually refer
262-
// to the same value.
263-
default:
264-
// Ambiguous. These instructions are uncommon, but perhaps could
265-
// be optimized if needed.
266-
}
267-
268-
if !ptr.IsAAllocaInst().IsNil() {
269-
if typeHasPointers(ptr.Type().ElementType()) {
270-
allocas = append(allocas, ptr)
271-
}
272-
} else {
273-
pointers = append(pointers, ptr)
274-
}
275-
}
276-
277-
if len(allocas) == 0 && len(pointers) == 0 {
278-
// This function does not need to keep track of stack pointers.
279-
continue
280-
}
281-
282-
// Determine the type of the required stack slot.
283-
fields := []llvm.Type{
284-
stackChainStartType, // Pointer to parent frame.
285-
c.uintptrType, // Number of elements in this frame.
286-
}
287-
for _, alloca := range allocas {
288-
fields = append(fields, alloca.Type().ElementType())
289-
}
290-
for _, ptr := range pointers {
291-
fields = append(fields, ptr.Type())
292-
}
293-
stackObjectType := c.ctx.StructType(fields, false)
294-
295-
// Create the stack object at the function entry.
296-
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
297-
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
298-
initialStackObject := llvm.ConstNull(stackObjectType)
299-
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
300-
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
301-
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
302-
c.builder.CreateStore(initialStackObject, stackObject)
303-
304-
// Update stack start.
305-
parent := c.builder.CreateLoad(stackChainStart, "")
306-
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
307-
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
308-
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
309-
}, "")
310-
c.builder.CreateStore(parent, gep)
311-
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
312-
c.builder.CreateStore(stackObjectCast, stackChainStart)
313-
314-
// Replace all independent allocas with GEPs in the stack object.
315-
for i, alloca := range allocas {
316-
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
317-
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
318-
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
319-
}, "")
320-
alloca.ReplaceAllUsesWith(gep)
321-
alloca.EraseFromParentAsInstruction()
322-
}
323-
324-
// Do a store to the stack object after each new pointer that is created.
325-
for i, ptr := range pointers {
326-
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
327-
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
328-
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
329-
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
330-
}, "")
331-
c.builder.CreateStore(ptr, gep)
332-
}
333-
334-
// Make sure this stack object is popped from the linked list of stack
335-
// objects at return.
336-
for _, ret := range returns {
337-
c.builder.SetInsertPointBefore(ret)
338-
c.builder.CreateStore(parent, stackChainStart)
339-
}
340-
}
341-
342-
return true
343-
}
344-
345-
func (c *Compiler) addGlobalsBitmap() bool {
346-
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
347-
return false // nothing to do: no GC in use
348-
}
349-
350-
var trackedGlobals []llvm.Value
351-
var trackedGlobalTypes []llvm.Type
352-
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
353-
if global.IsDeclaration() {
354-
continue
355-
}
356-
typ := global.Type().ElementType()
357-
ptrs := c.getPointerBitmap(typ, global.Name())
358-
if ptrs.BitLen() == 0 {
359-
continue
360-
}
361-
trackedGlobals = append(trackedGlobals, global)
362-
trackedGlobalTypes = append(trackedGlobalTypes, typ)
363-
}
364-
365-
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
366-
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
367-
globalsBundle.SetLinkage(llvm.InternalLinkage)
368-
globalsBundle.SetUnnamedAddr(true)
369-
initializer := llvm.Undef(globalsBundleType)
370-
for i, global := range trackedGlobals {
371-
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
372-
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
373-
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
374-
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
375-
})
376-
global.ReplaceAllUsesWith(gep)
377-
global.EraseFromParentAsGlobal()
378-
}
379-
globalsBundle.SetInitializer(initializer)
380-
381-
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
382-
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
383-
384-
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
385-
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
386-
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
387-
388-
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
389-
bitmapValues := make([]llvm.Value, len(bitmapBytes))
390-
for i, b := range bitmapBytes {
391-
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
392-
}
393-
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
394-
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
395-
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
396-
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
397-
bitmapNew.SetInitializer(bitmapArray)
398-
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
399-
400-
return true // the IR was changed
401-
}
402-
403-
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
404-
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
405-
switch typ.TypeKind() {
406-
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
407-
return big.NewInt(0)
408-
case llvm.PointerTypeKind:
409-
return big.NewInt(1)
410-
case llvm.StructTypeKind:
411-
ptrs := big.NewInt(0)
412-
for i, subtyp := range typ.StructElementTypes() {
413-
subptrs := c.getPointerBitmap(subtyp, name)
414-
if subptrs.BitLen() == 0 {
415-
continue
416-
}
417-
offset := c.targetData.ElementOffset(typ, i)
418-
if offset%uint64(alignment) != 0 {
419-
panic("precise GC: global contains unaligned pointer: " + name)
420-
}
421-
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
422-
ptrs.Or(ptrs, subptrs)
423-
}
424-
return ptrs
425-
case llvm.ArrayTypeKind:
426-
subtyp := typ.ElementType()
427-
subptrs := c.getPointerBitmap(subtyp, name)
428-
ptrs := big.NewInt(0)
429-
if subptrs.BitLen() == 0 {
430-
return ptrs
431-
}
432-
elementSize := c.targetData.TypeAllocSize(subtyp)
433-
for i := 0; i < typ.ArrayLength(); i++ {
434-
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
435-
ptrs.Or(ptrs, subptrs)
436-
}
437-
return ptrs
438-
default:
439-
panic("unknown type kind of global: " + name)
440-
}
441-
}
442-
443-
// markParentFunctions traverses all parent function calls (recursively) and
444-
// adds them to the set of marked functions. It only considers function calls:
445-
// any other uses of such a function is ignored.
446-
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
447-
worklist := []llvm.Value{fn}
448-
for len(worklist) != 0 {
449-
fn := worklist[len(worklist)-1]
450-
worklist = worklist[:len(worklist)-1]
451-
for _, use := range getUses(fn) {
452-
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
453-
// Not the parent function.
454-
continue
455-
}
456-
parent := use.InstructionParent().Parent()
457-
if _, ok := marked[parent]; !ok {
458-
marked[parent] = struct{}{}
459-
worklist = append(worklist, parent)
460-
}
461-
}
462-
}
463-
}

compiler/optimizer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
137137
builder.Populate(modPasses)
138138
modPasses.Run(c.mod)
139139

140-
hasGCPass := c.addGlobalsBitmap()
141-
hasGCPass = c.makeGCStackSlots() || hasGCPass
140+
hasGCPass := transform.AddGlobalsBitmap(c.mod)
141+
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
142142
if hasGCPass {
143143
if err := c.Verify(); err != nil {
144144
return errors.New("GC pass caused a verification failure")

0 commit comments

Comments
 (0)