github.com/aykevl/tinygo@v0.5.0/interp/interp.go (about) 1 // Package interp interprets Go package initializers as much as possible. This 2 // avoid running them at runtime, improving code size and making other 3 // optimizations possible. 4 package interp 5 6 // This file provides the overarching Eval object with associated (utility) 7 // methods. 8 9 import ( 10 "errors" 11 "strings" 12 13 "tinygo.org/x/go-llvm" 14 ) 15 16 type Eval struct { 17 Mod llvm.Module 18 TargetData llvm.TargetData 19 Debug bool 20 builder llvm.Builder 21 dibuilder *llvm.DIBuilder 22 dirtyGlobals map[llvm.Value]struct{} 23 sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results 24 } 25 26 // Run evaluates the function with the given name and then eliminates all 27 // callers. 28 func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error { 29 if debug { 30 println("\ncompile-time evaluation:") 31 } 32 33 name := "runtime.initAll" 34 e := &Eval{ 35 Mod: mod, 36 TargetData: targetData, 37 Debug: debug, 38 dirtyGlobals: map[llvm.Value]struct{}{}, 39 } 40 e.builder = mod.Context().NewBuilder() 41 e.dibuilder = llvm.NewDIBuilder(mod) 42 43 initAll := mod.NamedFunction(name) 44 bb := initAll.EntryBasicBlock() 45 // Create a dummy alloca in the entry block that we can set the insert point 46 // to. This is necessary because otherwise we might be removing the 47 // instruction (init call) that we are removing after successful 48 // interpretation. 49 e.builder.SetInsertPointBefore(bb.FirstInstruction()) 50 dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy") 51 e.builder.SetInsertPointBefore(dummy) 52 e.builder.SetInstDebugLocation(bb.FirstInstruction()) 53 var initCalls []llvm.Value 54 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 55 if inst == dummy { 56 continue 57 } 58 if !inst.IsAReturnInst().IsNil() { 59 break // ret void 60 } 61 if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() { 62 return errors.New("expected all instructions in " + name + " to be direct calls") 63 } 64 initCalls = append(initCalls, inst) 65 } 66 67 // Do this in a separate step to avoid corrupting the iterator above. 68 undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0)) 69 for _, call := range initCalls { 70 initName := call.CalledValue().Name() 71 if !strings.HasSuffix(initName, ".init") { 72 return errors.New("expected all instructions in " + name + " to be *.init() calls") 73 } 74 pkgName := initName[:len(initName)-5] 75 fn := call.CalledValue() 76 call.EraseFromParentAsInstruction() 77 _, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName) 78 if err == ErrUnreachable { 79 break 80 } 81 if err != nil { 82 return err 83 } 84 } 85 86 return nil 87 } 88 89 func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) { 90 return e.function(fn, params, pkgName, "") 91 } 92 93 func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) { 94 fr := frame{ 95 Eval: e, 96 fn: fn, 97 pkgName: pkgName, 98 locals: make(map[llvm.Value]Value), 99 } 100 for i, param := range fn.Params() { 101 fr.locals[param] = params[i] 102 } 103 104 bb := fn.EntryBasicBlock() 105 var lastBB llvm.BasicBlock 106 for { 107 retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent) 108 if outgoing == nil { 109 // returned something (a value or void, or an error) 110 return retval, err 111 } 112 if len(outgoing) > 1 { 113 panic("unimplemented: multiple outgoing blocks") 114 } 115 next := outgoing[0] 116 if next.IsABasicBlock().IsNil() { 117 panic("did not switch to a basic block") 118 } 119 lastBB = bb 120 bb = next.AsBasicBlock() 121 } 122 } 123 124 // getValue determines what kind of LLVM value it gets and returns the 125 // appropriate Value type. 126 func (e *Eval) getValue(v llvm.Value) Value { 127 return &LocalValue{e, v} 128 } 129 130 // markDirty marks the passed-in LLVM value dirty, recursively. For example, 131 // when it encounters a constant GEP on a global, it marks the global dirty. 132 func (e *Eval) markDirty(v llvm.Value) { 133 if !v.IsAGlobalVariable().IsNil() { 134 if v.IsGlobalConstant() { 135 return 136 } 137 if _, ok := e.dirtyGlobals[v]; !ok { 138 e.dirtyGlobals[v] = struct{}{} 139 e.sideEffectFuncs = nil // re-calculate all side effects 140 } 141 } else if v.IsConstant() { 142 if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() { 143 // looks like a constant getelementptr of a global. 144 // TODO: find a way to make sure it really is: v.Opcode() returns 0. 145 e.markDirty(v.Operand(0)) 146 return 147 } 148 return // nothing to mark 149 } else if !v.IsAGetElementPtrInst().IsNil() { 150 panic("interp: todo: GEP") 151 } else { 152 // Not constant and not a global or GEP so doesn't have to be marked 153 // non-constant. 154 } 155 }