github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/interp/interp.go (about) 1 // Package interp is a partial evaluator of code run at package init time. See 2 // the README in this package for details. 3 package interp 4 5 import ( 6 "fmt" 7 "os" 8 "strings" 9 "time" 10 11 "tinygo.org/x/go-llvm" 12 ) 13 14 // Enable extra checks, which should be disabled by default. 15 // This may help track down bugs by adding a few more sanity checks. 16 const checks = true 17 18 // runner contains all state related to one interp run. 19 type runner struct { 20 mod llvm.Module 21 targetData llvm.TargetData 22 builder llvm.Builder 23 pointerSize uint32 // cached pointer size from the TargetData 24 dataPtrType llvm.Type // often used type so created in advance 25 uintptrType llvm.Type // equivalent to uintptr in Go 26 maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result 27 debug bool // log debug messages 28 pkgName string // package name of the currently executing package 29 functionCache map[llvm.Value]*function // cache of compiled functions 30 objects []object // slice of objects in memory 31 globals map[llvm.Value]int // map from global to index in objects slice 32 start time.Time 33 timeout time.Duration 34 callsExecuted uint64 35 } 36 37 func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { 38 r := runner{ 39 mod: mod, 40 targetData: llvm.NewTargetData(mod.DataLayout()), 41 debug: debug, 42 functionCache: make(map[llvm.Value]*function), 43 objects: []object{{}}, 44 globals: make(map[llvm.Value]int), 45 start: time.Now(), 46 timeout: timeout, 47 } 48 r.pointerSize = uint32(r.targetData.PointerSize()) 49 r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0) 50 r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8) 51 r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case) 52 return &r 53 } 54 55 // Dispose deallocates all alloated LLVM resources. 56 func (r *runner) dispose() { 57 r.targetData.Dispose() 58 r.targetData = llvm.TargetData{} 59 } 60 61 // Run evaluates runtime.initAll function as much as possible at compile time. 62 // Set debug to true if it should print output while running. 63 func Run(mod llvm.Module, timeout time.Duration, debug bool) error { 64 r := newRunner(mod, timeout, debug) 65 defer r.dispose() 66 67 initAll := mod.NamedFunction("runtime.initAll") 68 bb := initAll.EntryBasicBlock() 69 70 // Create a builder, to insert instructions that could not be evaluated at 71 // compile time. 72 r.builder = mod.Context().NewBuilder() 73 defer r.builder.Dispose() 74 75 // Create a dummy alloca in the entry block that we can set the insert point 76 // to. This is necessary because otherwise we might be removing the 77 // instruction (init call) that we are removing after successful 78 // interpretation. 79 r.builder.SetInsertPointBefore(bb.FirstInstruction()) 80 dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy") 81 r.builder.SetInsertPointBefore(dummy) 82 defer dummy.EraseFromParentAsInstruction() 83 84 // Get a list if init calls. A runtime.initAll might look something like this: 85 // func initAll() { 86 // unsafe.init() 87 // machine.init() 88 // runtime.init() 89 // } 90 // This function gets a list of these call instructions. 91 var initCalls []llvm.Value 92 for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { 93 if inst == dummy { 94 continue 95 } 96 if !inst.IsAReturnInst().IsNil() { 97 break // ret void 98 } 99 if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() { 100 return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls") 101 } 102 initCalls = append(initCalls, inst) 103 } 104 105 // Run initializers for each package. Once the package initializer is 106 // finished, the call to the package initializer can be removed. 107 for _, call := range initCalls { 108 initName := call.CalledValue().Name() 109 if !strings.HasSuffix(initName, ".init") { 110 return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls") 111 } 112 r.pkgName = initName[:len(initName)-len(".init")] 113 fn := call.CalledValue() 114 if r.debug { 115 fmt.Fprintln(os.Stderr, "call:", fn.Name()) 116 } 117 _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") 118 call.EraseFromParentAsInstruction() 119 if callErr != nil { 120 if isRecoverableError(callErr.Err) { 121 if r.debug { 122 fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) 123 } 124 // Remove instructions that were created as part of interpreting 125 // the package. 126 mem.revert() 127 // Create a call to the package initializer (which was 128 // previously deleted). 129 i8undef := llvm.Undef(r.dataPtrType) 130 r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "") 131 // Make sure that any globals touched by the package 132 // initializer, won't be accessed by later package initializers. 133 err := r.markExternalLoad(fn) 134 if err != nil { 135 return fmt.Errorf("failed to interpret package %s: %w", r.pkgName, err) 136 } 137 continue 138 } 139 return callErr 140 } 141 for index, obj := range mem.objects { 142 r.objects[index] = obj 143 } 144 } 145 r.pkgName = "" 146 147 // Update all global variables in the LLVM module. 148 mem := memoryView{r: r} 149 for i, obj := range r.objects { 150 if obj.llvmGlobal.IsNil() { 151 continue 152 } 153 if obj.buffer == nil { 154 continue 155 } 156 if obj.constant { 157 continue // constant buffers can't have been modified 158 } 159 initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem) 160 if err == errInvalidPtrToIntSize { 161 // This can happen when a previous interp run did not have the 162 // correct LLVM type for a global and made something up. In that 163 // case, some fields could be written out as a series of (null) 164 // bytes even though they actually contain a pointer value. 165 // As a fallback, use asRawValue to get something of the correct 166 // memory layout. 167 initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem) 168 if err != nil { 169 return err 170 } 171 initializerType := initializer.Type() 172 newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp") 173 newGlobal.SetInitializer(initializer) 174 newGlobal.SetLinkage(obj.llvmGlobal.Linkage()) 175 newGlobal.SetAlignment(obj.llvmGlobal.Alignment()) 176 // TODO: copy debug info, unnamed_addr, ... 177 obj.llvmGlobal.ReplaceAllUsesWith(newGlobal) 178 name := obj.llvmGlobal.Name() 179 obj.llvmGlobal.EraseFromParentAsGlobal() 180 newGlobal.SetName(name) 181 182 // Update interp-internal references. 183 delete(r.globals, obj.llvmGlobal) 184 obj.llvmGlobal = newGlobal 185 r.globals[newGlobal] = i 186 r.objects[i] = obj 187 continue 188 } 189 if err != nil { 190 return err 191 } 192 if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() { 193 panic("initializer type mismatch") 194 } 195 obj.llvmGlobal.SetInitializer(initializer) 196 } 197 198 return nil 199 } 200 201 // RunFunc evaluates a single package initializer at compile time. 202 // Set debug to true if it should print output while running. 203 func RunFunc(fn llvm.Value, timeout time.Duration, debug bool) error { 204 // Create and initialize *runner object. 205 mod := fn.GlobalParent() 206 r := newRunner(mod, timeout, debug) 207 defer r.dispose() 208 initName := fn.Name() 209 if !strings.HasSuffix(initName, ".init") { 210 return errorAt(fn, "interp: unexpected function name (expected *.init)") 211 } 212 r.pkgName = initName[:len(initName)-len(".init")] 213 214 // Create new function with the interp result. 215 newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.GlobalValueType()) 216 newFn.SetLinkage(fn.Linkage()) 217 newFn.SetVisibility(fn.Visibility()) 218 entry := mod.Context().AddBasicBlock(newFn, "entry") 219 220 // Create a builder, to insert instructions that could not be evaluated at 221 // compile time. 222 r.builder = mod.Context().NewBuilder() 223 defer r.builder.Dispose() 224 r.builder.SetInsertPointAtEnd(entry) 225 226 // Copy debug information. 227 subprogram := fn.Subprogram() 228 if !subprogram.IsNil() { 229 newFn.SetSubprogram(subprogram) 230 r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{}) 231 } 232 233 // Run the initializer, filling the .init.tmp function. 234 if r.debug { 235 fmt.Fprintln(os.Stderr, "interp:", fn.Name()) 236 } 237 _, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ") 238 if callErr != nil { 239 if isRecoverableError(callErr.Err) { 240 // Could not finish, but could recover from it. 241 if r.debug { 242 fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) 243 } 244 newFn.EraseFromParentAsFunction() 245 return nil 246 } 247 return callErr 248 } 249 for index, obj := range pkgMem.objects { 250 r.objects[index] = obj 251 } 252 253 // Update globals with values determined while running the initializer above. 254 mem := memoryView{r: r} 255 for _, obj := range r.objects { 256 if obj.llvmGlobal.IsNil() { 257 continue 258 } 259 if obj.buffer == nil { 260 continue 261 } 262 if obj.constant { 263 continue // constant, so can't have been modified 264 } 265 initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.GlobalValueType(), &mem) 266 if err != nil { 267 return err 268 } 269 if checks && initializer.Type() != obj.llvmGlobal.GlobalValueType() { 270 panic("initializer type mismatch") 271 } 272 obj.llvmGlobal.SetInitializer(initializer) 273 } 274 275 // Finalize: remove the old init function and replace it with the new 276 // (.init.tmp) function. 277 r.builder.CreateRetVoid() 278 fnName := fn.Name() 279 fn.ReplaceAllUsesWith(newFn) 280 fn.EraseFromParentAsFunction() 281 newFn.SetName(fnName) 282 283 return nil 284 } 285 286 // getFunction returns the compiled version of the given LLVM function. It 287 // compiles the function if necessary and caches the result. 288 func (r *runner) getFunction(llvmFn llvm.Value) *function { 289 if fn, ok := r.functionCache[llvmFn]; ok { 290 return fn 291 } 292 fn := r.compileFunction(llvmFn) 293 r.functionCache[llvmFn] = fn 294 return fn 295 } 296 297 // markExternalLoad marks the given llvmValue as being loaded externally. This 298 // is primarily used to mark package initializers that could not be run at 299 // compile time. As an example, a package initialize might store to a global 300 // variable. Another package initializer might read from the same global 301 // variable. By marking this function as being run at runtime, that load 302 // instruction will need to be run at runtime instead of at compile time. 303 func (r *runner) markExternalLoad(llvmValue llvm.Value) error { 304 mem := memoryView{r: r} 305 err := mem.markExternalLoad(llvmValue) 306 if err != nil { 307 return err 308 } 309 for index, obj := range mem.objects { 310 if obj.marked > r.objects[index].marked { 311 r.objects[index].marked = obj.marked 312 } 313 } 314 return nil 315 }