github.com/aykevl/tinygo@v0.5.0/interp/scan.go (about) 1 package interp 2 3 import ( 4 "tinygo.org/x/go-llvm" 5 ) 6 7 type sideEffectSeverity int 8 9 const ( 10 sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions) 11 sideEffectNone // no side effects at all (pure) 12 sideEffectLimited // has side effects, but the effects are known 13 sideEffectAll // has unknown side effects 14 ) 15 16 // sideEffectResult contains the scan results after scanning a function for side 17 // effects (recursively). 18 type sideEffectResult struct { 19 severity sideEffectSeverity 20 mentionsGlobals map[llvm.Value]struct{} 21 } 22 23 // hasSideEffects scans this function and all descendants, recursively. It 24 // returns whether this function has side effects and if it does, which globals 25 // it mentions anywhere in this function or any called functions. 26 func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult { 27 switch fn.Name() { 28 case "runtime.alloc": 29 // Cannot be scanned but can be interpreted. 30 return &sideEffectResult{severity: sideEffectNone} 31 case "runtime.nanotime": 32 // Fixed value at compile time. 33 return &sideEffectResult{severity: sideEffectNone} 34 case "runtime._panic": 35 return &sideEffectResult{severity: sideEffectLimited} 36 case "runtime.interfaceImplements": 37 return &sideEffectResult{severity: sideEffectNone} 38 } 39 if e.sideEffectFuncs == nil { 40 e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult) 41 } 42 if se, ok := e.sideEffectFuncs[fn]; ok { 43 return se 44 } 45 result := &sideEffectResult{ 46 severity: sideEffectInProgress, 47 mentionsGlobals: map[llvm.Value]struct{}{}, 48 } 49 e.sideEffectFuncs[fn] = result 50 dirtyLocals := map[llvm.Value]struct{}{} 51 for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { 52 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 53 if inst.IsAInstruction().IsNil() { 54 panic("not an instruction") 55 } 56 57 // Check for any globals mentioned anywhere in the function. Assume 58 // any mentioned globals may be read from or written to when 59 // executed, thus must be marked dirty with a call. 60 for i := 0; i < inst.OperandsCount(); i++ { 61 operand := inst.Operand(i) 62 if !operand.IsAGlobalVariable().IsNil() { 63 result.mentionsGlobals[operand] = struct{}{} 64 } 65 } 66 67 switch inst.InstructionOpcode() { 68 case llvm.IndirectBr, llvm.Invoke: 69 // Not emitted by the compiler. 70 panic("unknown instructions") 71 case llvm.Call: 72 child := inst.CalledValue() 73 if !child.IsAInlineAsm().IsNil() { 74 // Inline assembly. This most likely has side effects. 75 // Assume they're only limited side effects, similar to 76 // external function calls. 77 result.updateSeverity(sideEffectLimited) 78 continue 79 } 80 if child.IsAFunction().IsNil() { 81 // Indirect call? 82 // In any case, we can't know anything here about what it 83 // affects exactly so mark this function as invoking all 84 // possible side effects. 85 result.updateSeverity(sideEffectAll) 86 continue 87 } 88 if child.IsDeclaration() { 89 // External function call. Assume only limited side effects 90 // (no affected globals, etc.). 91 if e.hasLocalSideEffects(dirtyLocals, inst) { 92 result.updateSeverity(sideEffectLimited) 93 } 94 continue 95 } 96 childSideEffects := e.hasSideEffects(child) 97 switch childSideEffects.severity { 98 case sideEffectInProgress, sideEffectNone: 99 // no side effects or recursive function - continue scanning 100 case sideEffectLimited: 101 // The return value may be problematic. 102 if e.hasLocalSideEffects(dirtyLocals, inst) { 103 result.updateSeverity(sideEffectLimited) 104 } 105 case sideEffectAll: 106 result.updateSeverity(sideEffectAll) 107 default: 108 panic("unreachable") 109 } 110 case llvm.Load, llvm.Store: 111 if inst.IsVolatile() { 112 result.updateSeverity(sideEffectLimited) 113 } 114 case llvm.IntToPtr: 115 // Pointer casts are not yet supported. 116 result.updateSeverity(sideEffectLimited) 117 default: 118 // Ignore most instructions. 119 // Check this list for completeness: 120 // https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode 121 } 122 } 123 } 124 125 if result.severity == sideEffectInProgress { 126 // No side effect was reported for this function. 127 result.severity = sideEffectNone 128 } 129 return result 130 } 131 132 // hasLocalSideEffects checks whether the given instruction flows into a branch 133 // or return instruction, in which case the whole function must be marked as 134 // having side effects and be called at runtime. 135 func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool { 136 if _, ok := dirtyLocals[inst]; ok { 137 // It is already known that this local is dirty. 138 return true 139 } 140 141 for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() { 142 user := use.User() 143 if user.IsAInstruction().IsNil() { 144 panic("user not an instruction") 145 } 146 switch user.InstructionOpcode() { 147 case llvm.Br, llvm.Switch: 148 // A branch on a dirty value makes this function dirty: it cannot be 149 // interpreted at compile time so has to be run at runtime. It is 150 // marked as having side effects for this reason. 151 return true 152 case llvm.Ret: 153 // This function returns a dirty value so it is itself marked as 154 // dirty to make sure it is called at runtime. 155 return true 156 case llvm.Store: 157 ptr := user.Operand(1) 158 if !ptr.IsAGlobalVariable().IsNil() { 159 // Store to a global variable. 160 // Already handled in (*Eval).hasSideEffects. 161 continue 162 } 163 // But a store might also store to an alloca, in which case all uses 164 // of the alloca (possibly indirect through a GEP, bitcast, etc.) 165 // must be marked dirty. 166 panic("todo: store") 167 default: 168 // All instructions that take 0 or more operands (1 or more if it 169 // was a use) and produce a result. 170 // For a list: 171 // https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode 172 dirtyLocals[user] = struct{}{} 173 if e.hasLocalSideEffects(dirtyLocals, user) { 174 return true 175 } 176 } 177 } 178 179 // No side effects found. 180 return false 181 } 182 183 // updateSeverity sets r.severity to the max of r.severity and severity, 184 // conservatively assuming the worst severity. 185 func (r *sideEffectResult) updateSeverity(severity sideEffectSeverity) { 186 if severity > r.severity { 187 r.severity = severity 188 } 189 } 190 191 // updateSeverity updates the severity with the severity of the child severity, 192 // like in a function call. This means it also copies the mentioned globals. 193 func (r *sideEffectResult) update(child *sideEffectResult) { 194 r.updateSeverity(child.severity) 195 for global := range child.mentionsGlobals { 196 r.mentionsGlobals[global] = struct{}{} 197 } 198 }