mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.9 KiB
147 lines
4.9 KiB
// Package interp is a partial evaluator of code run at package init time. See
|
|
// the README in this package for details.
|
|
package interp
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// Enable extra checks, which should be disabled by default.
|
|
// This may help track down bugs by adding a few more sanity checks.
|
|
const checks = true
|
|
|
|
// runner contains all state related to one interp run.
|
|
type runner struct {
|
|
mod llvm.Module
|
|
targetData llvm.TargetData
|
|
builder llvm.Builder
|
|
pointerSize uint32 // cached pointer size from the TargetData
|
|
i8ptrType llvm.Type // often used type so created in advance
|
|
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
|
|
debug bool // log debug messages
|
|
pkgName string // package name of the currently executing package
|
|
functionCache map[llvm.Value]*function // cache of compiled functions
|
|
objects []object // slice of objects in memory
|
|
globals map[llvm.Value]int // map from global to index in objects slice
|
|
start time.Time
|
|
callsExecuted uint64
|
|
}
|
|
|
|
// Run evaluates runtime.initAll function as much as possible at compile time.
|
|
// Set debug to true if it should print output while running.
|
|
func Run(mod llvm.Module, debug bool) error {
|
|
r := runner{
|
|
mod: mod,
|
|
targetData: llvm.NewTargetData(mod.DataLayout()),
|
|
debug: debug,
|
|
functionCache: make(map[llvm.Value]*function),
|
|
objects: []object{{}},
|
|
globals: make(map[llvm.Value]int),
|
|
start: time.Now(),
|
|
}
|
|
r.pointerSize = uint32(r.targetData.PointerSize())
|
|
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
|
|
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
|
|
|
|
initAll := mod.NamedFunction("runtime.initAll")
|
|
bb := initAll.EntryBasicBlock()
|
|
|
|
// Create a builder, to insert instructions that could not be evaluated at
|
|
// compile time.
|
|
r.builder = mod.Context().NewBuilder()
|
|
defer r.builder.Dispose()
|
|
|
|
// Create a dummy alloca in the entry block that we can set the insert point
|
|
// to. This is necessary because otherwise we might be removing the
|
|
// instruction (init call) that we are removing after successful
|
|
// interpretation.
|
|
r.builder.SetInsertPointBefore(bb.FirstInstruction())
|
|
dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
|
|
r.builder.SetInsertPointBefore(dummy)
|
|
defer dummy.EraseFromParentAsInstruction()
|
|
|
|
// Get a list if init calls. A runtime.initAll might look something like this:
|
|
// func initAll() {
|
|
// unsafe.init()
|
|
// machine.init()
|
|
// runtime.init()
|
|
// }
|
|
// This function gets a list of these call instructions.
|
|
var initCalls []llvm.Value
|
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
if inst == dummy {
|
|
continue
|
|
}
|
|
if !inst.IsAReturnInst().IsNil() {
|
|
break // ret void
|
|
}
|
|
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
|
return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
|
|
}
|
|
initCalls = append(initCalls, inst)
|
|
}
|
|
|
|
// Run initializers for each package. Once the package initializer is
|
|
// finished, the call to the package initializer can be removed.
|
|
for _, call := range initCalls {
|
|
initName := call.CalledValue().Name()
|
|
if !strings.HasSuffix(initName, ".init") {
|
|
return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
|
|
}
|
|
r.pkgName = initName[:len(initName)-len(".init")]
|
|
fn := call.CalledValue()
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, "call:", fn.Name())
|
|
}
|
|
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
|
if callErr != nil {
|
|
if isRecoverableError(callErr.Err) {
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
|
|
}
|
|
mem.revert()
|
|
continue
|
|
}
|
|
return callErr
|
|
}
|
|
call.EraseFromParentAsInstruction()
|
|
for index, obj := range mem.objects {
|
|
r.objects[index] = obj
|
|
}
|
|
}
|
|
r.pkgName = ""
|
|
|
|
// Update all global variables in the LLVM module.
|
|
mem := memoryView{r: &r}
|
|
for _, obj := range r.objects {
|
|
if obj.llvmGlobal.IsNil() {
|
|
continue
|
|
}
|
|
if obj.buffer == nil {
|
|
continue
|
|
}
|
|
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
|
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
|
panic("initializer type mismatch")
|
|
}
|
|
obj.llvmGlobal.SetInitializer(initializer)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getFunction returns the compiled version of the given LLVM function. It
|
|
// compiles the function if necessary and caches the result.
|
|
func (r *runner) getFunction(llvmFn llvm.Value) *function {
|
|
if fn, ok := r.functionCache[llvmFn]; ok {
|
|
return fn
|
|
}
|
|
fn := r.compileFunction(llvmFn)
|
|
r.functionCache[llvmFn] = fn
|
|
return fn
|
|
}
|
|
|