
This change triggers a revert whenever a basic block runs instructions at runtime twice. As a result, a loop body with runtime-only instructions will no longer be unrolled. This should help some extreme cases where loops can be expanded into hundreds or thousands of instructions.
1086 строки
37 КиБ
Go
1086 строки
37 КиБ
Go
package interp
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent string) (value, memoryView, *Error) {
|
|
mem := memoryView{r: r, parent: parentMem}
|
|
locals := make([]value, len(fn.locals))
|
|
r.callsExecuted++
|
|
|
|
t0 := time.Since(r.start)
|
|
|
|
// Parameters are considered a kind of local values.
|
|
for i, param := range params {
|
|
locals[i] = param
|
|
}
|
|
|
|
// Track what blocks have run instructions at runtime.
|
|
// This is used to prevent unrolling.
|
|
var runtimeBlocks map[int]struct{}
|
|
|
|
// Start with the first basic block and the first instruction.
|
|
// Branch instructions may modify both bb and instIndex when branching.
|
|
bb := fn.blocks[0]
|
|
currentBB := 0
|
|
lastBB := -1 // last basic block is undefined, only defined after a branch
|
|
var operands []value
|
|
startRTInsts := len(mem.instructions)
|
|
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
|
|
if instIndex == 0 {
|
|
// This is the start of a new basic block.
|
|
if len(mem.instructions) != startRTInsts {
|
|
if _, ok := runtimeBlocks[lastBB]; ok {
|
|
// This loop has been unrolled.
|
|
// Avoid doing this, as it can result in a large amount of extra machine code.
|
|
// This currently uses the branch from the last block, as there is no available information to give a better location.
|
|
lastBBInsts := fn.blocks[lastBB].instructions
|
|
return nil, mem, r.errorAt(lastBBInsts[len(lastBBInsts)-1], errLoopUnrolled)
|
|
}
|
|
|
|
// Flag the last block as having run stuff at runtime.
|
|
if runtimeBlocks == nil {
|
|
runtimeBlocks = make(map[int]struct{})
|
|
}
|
|
runtimeBlocks[lastBB] = struct{}{}
|
|
|
|
// Reset the block-start runtime instructions counter.
|
|
startRTInsts = len(mem.instructions)
|
|
}
|
|
|
|
// There may be PHI nodes that need to be resolved. Resolve all PHI
|
|
// nodes before continuing with regular instructions.
|
|
// PHI nodes need to be treated specially because they can have a
|
|
// mutual dependency:
|
|
// for.loop:
|
|
// %a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
|
|
// %b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
|
|
// If these PHI nodes are processed like a regular instruction, %a
|
|
// and %b are both 3 on the second iteration of the loop because %b
|
|
// loads the value of %a from the second iteration, while it should
|
|
// load the value from the previous iteration. The correct behavior
|
|
// is that these two values swap each others place on each
|
|
// iteration.
|
|
var phiValues []value
|
|
var phiIndices []int
|
|
for _, inst := range bb.phiNodes {
|
|
var result value
|
|
for i := 0; i < len(inst.operands); i += 2 {
|
|
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
|
|
incoming := inst.operands[i+1]
|
|
if local, ok := incoming.(localValue); ok {
|
|
result = locals[fn.locals[local.value]]
|
|
} else {
|
|
result = incoming
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
|
|
}
|
|
if result == nil {
|
|
panic("could not find PHI input")
|
|
}
|
|
phiValues = append(phiValues, result)
|
|
phiIndices = append(phiIndices, inst.localIndex)
|
|
}
|
|
for i, value := range phiValues {
|
|
locals[phiIndices[i]] = value
|
|
}
|
|
}
|
|
|
|
inst := bb.instructions[instIndex]
|
|
operands = operands[:0]
|
|
isRuntimeInst := false
|
|
if inst.opcode != llvm.PHI {
|
|
for _, v := range inst.operands {
|
|
if v, ok := v.(localValue); ok {
|
|
index, ok := fn.locals[v.value]
|
|
if !ok {
|
|
// This is a localValue that is not local to the
|
|
// function. An example would be an inline assembly call
|
|
// operand.
|
|
isRuntimeInst = true
|
|
break
|
|
}
|
|
localVal := locals[index]
|
|
if localVal == nil {
|
|
// Trying to read a function-local value before it is
|
|
// set.
|
|
return nil, mem, r.errorAt(inst, errors.New("interp: local not defined"))
|
|
} else {
|
|
operands = append(operands, localVal)
|
|
if _, ok := localVal.(localValue); ok {
|
|
// The function-local value is still just a
|
|
// localValue (which can't be interpreted at compile
|
|
// time). Not sure whether this ever happens in
|
|
// practice.
|
|
isRuntimeInst = true
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
operands = append(operands, v)
|
|
}
|
|
}
|
|
if isRuntimeInst {
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
switch inst.opcode {
|
|
case llvm.Ret:
|
|
const maxInterpSeconds = 180
|
|
if t0 > maxInterpSeconds*time.Second {
|
|
// Running for more than maxInterpSeconds seconds. This should never happen, but does.
|
|
// See github.com/tinygo-org/tinygo/issues/2124
|
|
return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than %d seconds, timing out (executed calls: %d)", maxInterpSeconds, r.callsExecuted))
|
|
}
|
|
|
|
if len(operands) != 0 {
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"ret", operands[0])
|
|
}
|
|
// Return instruction has a value to return.
|
|
return operands[0], mem, nil
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"ret")
|
|
}
|
|
// Return instruction doesn't return anything, it's just 'ret void'.
|
|
return nil, mem, nil
|
|
case llvm.Br:
|
|
switch len(operands) {
|
|
case 1:
|
|
// Unconditional branch: [nextBB]
|
|
lastBB = currentBB
|
|
currentBB = int(operands[0].(literalValue).value.(uint32))
|
|
bb = fn.blocks[currentBB]
|
|
instIndex = -1 // start at 0 the next cycle
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
|
}
|
|
case 3:
|
|
// Conditional branch: [cond, thenBB, elseBB]
|
|
lastBB = currentBB
|
|
switch operands[0].Uint() {
|
|
case 1: // true -> thenBB
|
|
currentBB = int(operands[1].(literalValue).value.(uint32))
|
|
case 0: // false -> elseBB
|
|
currentBB = int(operands[2].(literalValue).value.(uint32))
|
|
default:
|
|
panic("bool should be 0 or 1")
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
|
}
|
|
bb = fn.blocks[currentBB]
|
|
instIndex = -1 // start at 0 the next cycle
|
|
default:
|
|
panic("unknown operands length")
|
|
}
|
|
case llvm.Switch:
|
|
// Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...]
|
|
value := operands[0].Uint()
|
|
targetLabel := operands[1].Uint() // default label
|
|
// Do a lazy switch by iterating over all cases.
|
|
for i := 2; i < len(operands); i += 2 {
|
|
if value == operands[i].Uint() {
|
|
targetLabel = operands[i+1].Uint()
|
|
break
|
|
}
|
|
}
|
|
lastBB = currentBB
|
|
currentBB = int(targetLabel)
|
|
bb = fn.blocks[currentBB]
|
|
instIndex = -1 // start at 0 the next cycle
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"switch", operands, "->", currentBB)
|
|
}
|
|
case llvm.Select:
|
|
// Select is much like a ternary operator: it picks a result from
|
|
// the second and third operand based on the boolean first operand.
|
|
var result value
|
|
switch operands[0].Uint() {
|
|
case 1:
|
|
result = operands[1]
|
|
case 0:
|
|
result = operands[2]
|
|
default:
|
|
panic("boolean must be 0 or 1")
|
|
}
|
|
locals[inst.localIndex] = result
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"select", operands, "->", result)
|
|
}
|
|
case llvm.Call:
|
|
// A call instruction can either be a regular call or a runtime intrinsic.
|
|
fnPtr, err := operands[0].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
callFn := r.getFunction(fnPtr.llvmValue(&mem))
|
|
switch {
|
|
case callFn.name == "runtime.trackPointer":
|
|
// Allocas and such are created as globals, so don't need a
|
|
// runtime.trackPointer.
|
|
// Unless the object is allocated at runtime for example, in
|
|
// which case this call won't even get to this point but will
|
|
// already be emitted in initAll.
|
|
continue
|
|
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" ||
|
|
callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current":
|
|
// These functions should be run at runtime. Specifically:
|
|
// * Print and panic functions are best emitted directly without
|
|
// interpreting them, otherwise we get a ton of putchar (etc.)
|
|
// calls.
|
|
// * runtime.hashmapGet tries to access the map value directly.
|
|
// This is not possible as the map value is treated as a special
|
|
// kind of object in this package.
|
|
// * os.runtime_args reads globals that are initialized outside
|
|
// the view of the interp package so it always needs to be run
|
|
// at runtime.
|
|
// * internal/task.start, internal/task.Current: start and read shcheduler state,
|
|
// which is modified elsewhere.
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
case callFn.name == "internal/task.Pause":
|
|
// Task scheduling isn't possible at compile time.
|
|
return nil, mem, r.errorAt(inst, errUnsupportedRuntimeInst)
|
|
case callFn.name == "runtime.nanotime" && r.pkgName == "time":
|
|
// The time package contains a call to runtime.nanotime.
|
|
// This appears to be to work around a limitation in Windows
|
|
// Server 2008:
|
|
// > Monotonic times are reported as offsets from startNano.
|
|
// > We initialize startNano to runtimeNano() - 1 so that on systems where
|
|
// > monotonic time resolution is fairly low (e.g. Windows 2008
|
|
// > which appears to have a default resolution of 15ms),
|
|
// > we avoid ever reporting a monotonic time of 0.
|
|
// > (Callers may want to use 0 as "time not set".)
|
|
// Simply let runtime.nanotime return 0 in this case, which
|
|
// should be fine and avoids a call to runtime.nanotime. It
|
|
// means that monotonic time in the time package is counted from
|
|
// time.Time{}.Sub(1), which should be fine.
|
|
locals[inst.localIndex] = literalValue{uint64(0)}
|
|
case callFn.name == "runtime.alloc":
|
|
// Allocate heap memory. At compile time, this is instead done
|
|
// by creating a global variable.
|
|
|
|
// Get the requested memory size to be allocated.
|
|
size := operands[1].Uint()
|
|
|
|
// Get the object layout, if it is available.
|
|
llvmLayoutType := r.getLLVMTypeFromLayout(operands[2])
|
|
|
|
// Create the object.
|
|
alloc := object{
|
|
globalName: r.pkgName + "$alloc",
|
|
llvmLayoutType: llvmLayoutType,
|
|
buffer: newRawValue(uint32(size)),
|
|
size: uint32(size),
|
|
}
|
|
index := len(r.objects)
|
|
r.objects = append(r.objects, alloc)
|
|
|
|
// And create a pointer to this object, for working with it (so
|
|
// that stores to it copy it, etc).
|
|
ptr := newPointerValue(r, index, 0)
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr)
|
|
}
|
|
locals[inst.localIndex] = ptr
|
|
case callFn.name == "runtime.sliceCopy":
|
|
// sliceCopy implements the built-in copy function for slices.
|
|
// It is implemented here so that it can be used even if the
|
|
// runtime implementation is not available. Doing it this way
|
|
// may also be faster.
|
|
// Code:
|
|
// func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int {
|
|
// n := srcLen
|
|
// if n > dstLen {
|
|
// n = dstLen
|
|
// }
|
|
// memmove(dst, src, n*elemSize)
|
|
// return int(n)
|
|
// }
|
|
dstLen := operands[3].Uint()
|
|
srcLen := operands[4].Uint()
|
|
elemSize := operands[5].Uint()
|
|
n := srcLen
|
|
if n > dstLen {
|
|
n = dstLen
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n)
|
|
}
|
|
if n != 0 {
|
|
// Only try to copy bytes when there are any bytes to copy.
|
|
// This is not just an optimization. If one of the slices
|
|
// (or both) are nil, the asPointer method call will fail
|
|
// even though copying a nil slice is allowed.
|
|
dst, err := operands[1].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
src, err := operands[2].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
nBytes := uint32(n * elemSize)
|
|
dstObj := mem.getWritable(dst.index())
|
|
dstBuf := dstObj.buffer.asRawValue(r)
|
|
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
|
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
|
dstObj.buffer = dstBuf
|
|
mem.put(dst.index(), dstObj)
|
|
}
|
|
switch inst.llvmInst.Type().IntTypeWidth() {
|
|
case 16:
|
|
locals[inst.localIndex] = literalValue{uint16(n)}
|
|
case 32:
|
|
locals[inst.localIndex] = literalValue{uint32(n)}
|
|
case 64:
|
|
locals[inst.localIndex] = literalValue{uint64(n)}
|
|
default:
|
|
panic("unknown integer type width")
|
|
}
|
|
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0i8.p0i8.") || strings.HasPrefix(callFn.name, "llvm.memmove.p0i8.p0i8."):
|
|
// Copy a block of memory from one pointer to another.
|
|
dst, err := operands[1].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
src, err := operands[2].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
nBytes := uint32(operands[3].Uint())
|
|
dstObj := mem.getWritable(dst.index())
|
|
dstBuf := dstObj.buffer.asRawValue(r)
|
|
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
|
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
|
dstObj.buffer = dstBuf
|
|
mem.put(dst.index(), dstObj)
|
|
case callFn.name == "(reflect.rawType).elem":
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:])
|
|
}
|
|
// Extract the type code global from the first parameter.
|
|
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
typecodeID := typecodeIDPtrToInt.Operand(0)
|
|
|
|
// Get the type class.
|
|
// See also: getClassAndValueFromTypeCode in transform/reflect.go.
|
|
typecodeName := typecodeID.Name()
|
|
const prefix = "reflect/types.type:"
|
|
if !strings.HasPrefix(typecodeName, prefix) {
|
|
panic("unexpected typecode name: " + typecodeName)
|
|
}
|
|
id := typecodeName[len(prefix):]
|
|
class := id[:strings.IndexByte(id, ':')]
|
|
value := id[len(class)+1:]
|
|
if class == "named" {
|
|
// Get the underlying type.
|
|
class = value[:strings.IndexByte(value, ':')]
|
|
value = value[len(class)+1:]
|
|
}
|
|
|
|
// Elem() is only valid for certain type classes.
|
|
switch class {
|
|
case "chan", "pointer", "slice", "array":
|
|
elementType := llvm.ConstExtractValue(typecodeID.Initializer(), []uint32{0})
|
|
uintptrType := r.mod.Context().IntType(int(mem.r.pointerSize) * 8)
|
|
locals[inst.localIndex] = r.getValue(llvm.ConstPtrToInt(elementType, uintptrType))
|
|
default:
|
|
return nil, mem, r.errorAt(inst, fmt.Errorf("(reflect.Type).Elem() called on %s type", class))
|
|
}
|
|
case callFn.name == "runtime.typeAssert":
|
|
// This function must be implemented manually as it is normally
|
|
// implemented by the interface lowering pass.
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
|
|
}
|
|
assertedType, err := operands[2].toLLVMValue(inst.llvmInst.Operand(1).Type(), &mem)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
if !actualTypePtrToInt.IsAConstantInt().IsNil() && actualTypePtrToInt.ZExtValue() == 0 {
|
|
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
break
|
|
}
|
|
actualType := actualTypePtrToInt.Operand(0)
|
|
if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") {
|
|
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
} else {
|
|
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
}
|
|
case strings.HasSuffix(callFn.name, ".$typeassert"):
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
|
|
}
|
|
|
|
// Load various values for the interface implements check below.
|
|
typecodePtr, err := operands[1].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
|
llvmFn := inst.llvmInst.CalledValue()
|
|
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
|
methodSetString := methodSetAttr.GetStringValue()
|
|
|
|
// Make a set of all the methods on the concrete type, for
|
|
// easier checking in the next step.
|
|
concreteTypeMethods := map[string]struct{}{}
|
|
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
|
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
|
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
|
concreteTypeMethods[name] = struct{}{}
|
|
}
|
|
|
|
// Check whether all interface methods are also in the list
|
|
// of defined methods calculated above. This is the interface
|
|
// assert itself.
|
|
assertOk := uint8(1) // i1 true
|
|
for _, name := range strings.Split(methodSetString, "; ") {
|
|
if _, ok := concreteTypeMethods[name]; !ok {
|
|
// There is a method on the interface that is not
|
|
// implemented by the type. The assertion will fail.
|
|
assertOk = 0 // i1 false
|
|
break
|
|
}
|
|
}
|
|
// If assertOk is still 1, the assertion succeeded.
|
|
locals[inst.localIndex] = literalValue{assertOk}
|
|
case strings.HasSuffix(callFn.name, "$invoke"):
|
|
// This thunk is the interface method dispatcher: it is called
|
|
// with all regular parameters and a type code. It will then
|
|
// call the concrete method for it.
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:])
|
|
}
|
|
|
|
// Load the type code of the interface value.
|
|
typecodeIDBitCast, err := operands[len(operands)-2].toLLVMValue(inst.llvmInst.Operand(len(operands)-3).Type(), &mem)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
typecodeID := typecodeIDBitCast.Operand(0).Initializer()
|
|
|
|
// Load the method set, which is part of the typecodeID object.
|
|
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer()
|
|
|
|
// We don't need to load the interface method set.
|
|
|
|
// Load the signature of the to-be-called function.
|
|
llvmFn := inst.llvmInst.CalledValue()
|
|
invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
|
|
invokeName := invokeAttr.GetStringValue()
|
|
signature := r.mod.NamedGlobal(invokeName)
|
|
|
|
// Iterate through all methods, looking for the one method that
|
|
// should be returned.
|
|
numMethods := methodSet.Type().ArrayLength()
|
|
var method llvm.Value
|
|
for i := 0; i < numMethods; i++ {
|
|
methodSignature := llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 0})
|
|
if methodSignature == signature {
|
|
method = llvm.ConstExtractValue(methodSet, []uint32{uint32(i), 1}).Operand(0)
|
|
}
|
|
}
|
|
if method.IsNil() {
|
|
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName))
|
|
}
|
|
|
|
// Change the to-be-called function to the underlying method to
|
|
// be called and fall through to the default case.
|
|
callFn = r.getFunction(method)
|
|
fallthrough
|
|
default:
|
|
if len(callFn.blocks) == 0 {
|
|
// Call to a function declaration without a definition
|
|
// available.
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
// Call a function with a definition available. Run it as usual,
|
|
// possibly trying to recover from it if it failed to execute.
|
|
if r.debug {
|
|
argStrings := make([]string, len(operands)-1)
|
|
for i, v := range operands[1:] {
|
|
argStrings[i] = v.String()
|
|
}
|
|
fmt.Fprintln(os.Stderr, indent+"call:", callFn.name+"("+strings.Join(argStrings, ", ")+")")
|
|
}
|
|
retval, callMem, callErr := r.run(callFn, operands[1:], &mem, indent+" ")
|
|
if callErr != nil {
|
|
if isRecoverableError(callErr.Err) {
|
|
// This error can be recovered by doing the call at
|
|
// runtime instead of at compile time. But we need to
|
|
// revert any changes made by the call first.
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"!! revert because of error:", callErr.Err)
|
|
}
|
|
callMem.revert()
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
// Add to the traceback, so that error handling code can see
|
|
// how this function got called.
|
|
callErr.Traceback = append(callErr.Traceback, ErrorLine{
|
|
Pos: getPosition(inst.llvmInst),
|
|
Inst: inst.llvmInst,
|
|
})
|
|
return nil, mem, callErr
|
|
}
|
|
locals[inst.localIndex] = retval
|
|
mem.extend(callMem)
|
|
}
|
|
case llvm.Load:
|
|
// Load instruction, loading some data from the topmost memory view.
|
|
ptr, err := operands[0].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
size := operands[1].(literalValue).value.(uint64)
|
|
if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalStore(ptr) {
|
|
// If there could be an external store (for example, because a
|
|
// pointer to the object was passed to a function that could not
|
|
// be interpreted at compile time) then the load must be done at
|
|
// runtime.
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
result := mem.load(ptr, uint32(size))
|
|
if result == nil {
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
|
|
}
|
|
locals[inst.localIndex] = result
|
|
case llvm.Store:
|
|
// Store instruction. Create a new object in the memory view and
|
|
// store to that, to make it possible to roll back this store.
|
|
ptr, err := operands[1].asPointer(r)
|
|
if err != nil {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalLoadOrStore(ptr) {
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
val := operands[0]
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
|
|
}
|
|
ok := mem.store(val, ptr)
|
|
if !ok {
|
|
// Could not store the value, do it at runtime.
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
}
|
|
case llvm.Alloca:
|
|
// Alloca normally allocates some stack memory. In the interpreter,
|
|
// it allocates a global instead.
|
|
// This can likely be optimized, as all it really needs is an alloca
|
|
// in the initAll function and creating a global is wasteful for
|
|
// this purpose.
|
|
|
|
// Create the new object.
|
|
size := operands[0].(literalValue).value.(uint64)
|
|
alloca := object{
|
|
llvmType: inst.llvmInst.Type(),
|
|
globalName: r.pkgName + "$alloca",
|
|
buffer: newRawValue(uint32(size)),
|
|
size: uint32(size),
|
|
}
|
|
index := len(r.objects)
|
|
r.objects = append(r.objects, alloca)
|
|
|
|
// Create a pointer to this object (an alloca produces a pointer).
|
|
ptr := newPointerValue(r, index, 0)
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"alloca:", operands, "->", ptr)
|
|
}
|
|
locals[inst.localIndex] = ptr
|
|
case llvm.GetElementPtr:
|
|
// GetElementPtr does pointer arithmetic, changing the offset of the
|
|
// pointer into the underlying object.
|
|
var offset uint64
|
|
for i := 2; i < len(operands); i += 2 {
|
|
index := operands[i].Uint()
|
|
elementSize := operands[i+1].Uint()
|
|
if int64(elementSize) < 0 {
|
|
// This is a struct field.
|
|
offset += index
|
|
} else {
|
|
// This is a normal GEP, probably an array index.
|
|
offset += elementSize * index
|
|
}
|
|
}
|
|
ptr, err := operands[0].asPointer(r)
|
|
if err != nil {
|
|
if err != errIntegerAsPointer {
|
|
return nil, mem, r.errorAt(inst, err)
|
|
}
|
|
// GEP on fixed pointer value (for example, memory-mapped I/O).
|
|
ptrValue := operands[0].Uint() + offset
|
|
switch operands[0].len(r) {
|
|
case 8:
|
|
locals[inst.localIndex] = literalValue{uint64(ptrValue)}
|
|
case 4:
|
|
locals[inst.localIndex] = literalValue{uint32(ptrValue)}
|
|
case 2:
|
|
locals[inst.localIndex] = literalValue{uint16(ptrValue)}
|
|
default:
|
|
panic("pointer operand is not of a known pointer size")
|
|
}
|
|
continue
|
|
}
|
|
ptr = ptr.addOffset(uint32(offset))
|
|
locals[inst.localIndex] = ptr
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
|
|
}
|
|
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
|
|
// Various bitcast-like instructions that all keep the same bits
|
|
// while changing the LLVM type.
|
|
// Because interp doesn't preserve the type, these operations are
|
|
// identity operations.
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", operands[0])
|
|
}
|
|
locals[inst.localIndex] = operands[0]
|
|
case llvm.ExtractValue:
|
|
agg := operands[0].asRawValue(r)
|
|
offset := operands[1].(literalValue).value.(uint64)
|
|
size := operands[2].(literalValue).value.(uint64)
|
|
elt := rawValue{
|
|
buf: agg.buf[offset : offset+size],
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"extractvalue:", operands, "->", elt)
|
|
}
|
|
locals[inst.localIndex] = elt
|
|
case llvm.InsertValue:
|
|
agg := operands[0].asRawValue(r)
|
|
elt := operands[1].asRawValue(r)
|
|
offset := int(operands[2].(literalValue).value.(uint64))
|
|
newagg := newRawValue(uint32(len(agg.buf)))
|
|
copy(newagg.buf, agg.buf)
|
|
copy(newagg.buf[offset:], elt.buf)
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"insertvalue:", operands, "->", newagg)
|
|
}
|
|
locals[inst.localIndex] = newagg
|
|
case llvm.ICmp:
|
|
predicate := llvm.IntPredicate(operands[2].(literalValue).value.(uint8))
|
|
var result bool
|
|
lhs := operands[0]
|
|
rhs := operands[1]
|
|
switch predicate {
|
|
case llvm.IntEQ, llvm.IntNE:
|
|
lhsPointer, lhsErr := lhs.asPointer(r)
|
|
rhsPointer, rhsErr := rhs.asPointer(r)
|
|
if (lhsErr == nil) != (rhsErr == nil) {
|
|
// Fast path: only one is a pointer, so they can't be equal.
|
|
result = false
|
|
} else if lhsErr == nil {
|
|
// Both must be nil, so both are pointers.
|
|
// Compare them directly.
|
|
result = lhsPointer.equal(rhsPointer)
|
|
} else {
|
|
// Fall back to generic comparison.
|
|
result = lhs.asRawValue(r).equal(rhs.asRawValue(r))
|
|
}
|
|
if predicate == llvm.IntNE {
|
|
result = !result
|
|
}
|
|
case llvm.IntUGT:
|
|
result = lhs.Uint() > rhs.Uint()
|
|
case llvm.IntUGE:
|
|
result = lhs.Uint() >= rhs.Uint()
|
|
case llvm.IntULT:
|
|
result = lhs.Uint() < rhs.Uint()
|
|
case llvm.IntULE:
|
|
result = lhs.Uint() <= rhs.Uint()
|
|
case llvm.IntSGT:
|
|
result = lhs.Int() > rhs.Int()
|
|
case llvm.IntSGE:
|
|
result = lhs.Int() >= rhs.Int()
|
|
case llvm.IntSLT:
|
|
result = lhs.Int() < rhs.Int()
|
|
case llvm.IntSLE:
|
|
result = lhs.Int() <= rhs.Int()
|
|
default:
|
|
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported icmp"))
|
|
}
|
|
if result {
|
|
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
} else {
|
|
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"icmp:", operands[0], intPredicateString(predicate), operands[1], "->", result)
|
|
}
|
|
case llvm.FCmp:
|
|
predicate := llvm.FloatPredicate(operands[2].(literalValue).value.(uint8))
|
|
var result bool
|
|
var lhs, rhs float64
|
|
switch operands[0].len(r) {
|
|
case 8:
|
|
lhs = math.Float64frombits(operands[0].Uint())
|
|
rhs = math.Float64frombits(operands[1].Uint())
|
|
case 4:
|
|
lhs = float64(math.Float32frombits(uint32(operands[0].Uint())))
|
|
rhs = float64(math.Float32frombits(uint32(operands[1].Uint())))
|
|
default:
|
|
panic("unknown float type")
|
|
}
|
|
switch predicate {
|
|
case llvm.FloatOEQ:
|
|
result = lhs == rhs
|
|
case llvm.FloatUNE:
|
|
result = lhs != rhs
|
|
case llvm.FloatOGT:
|
|
result = lhs > rhs
|
|
case llvm.FloatOGE:
|
|
result = lhs >= rhs
|
|
case llvm.FloatOLT:
|
|
result = lhs < rhs
|
|
case llvm.FloatOLE:
|
|
result = lhs <= rhs
|
|
default:
|
|
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported fcmp"))
|
|
}
|
|
if result {
|
|
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
} else {
|
|
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+"fcmp:", operands[0], predicate, operands[1], "->", result)
|
|
}
|
|
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
|
|
// Integer binary operations.
|
|
lhs := operands[0]
|
|
rhs := operands[1]
|
|
lhsPtr, err := lhs.asPointer(r)
|
|
if err == nil {
|
|
// The lhs is a pointer. This sometimes happens for particular
|
|
// pointer tricks.
|
|
switch inst.opcode {
|
|
case llvm.Add:
|
|
// This likely means this is part of a
|
|
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
|
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
|
|
locals[inst.localIndex] = lhsPtr
|
|
continue
|
|
case llvm.Xor:
|
|
if rhs.Uint() == 0 {
|
|
// Special workaround for strings.noescape, see
|
|
// src/strings/builder.go in the Go source tree. This is
|
|
// the identity operator, so we can return the input.
|
|
locals[inst.localIndex] = lhs
|
|
continue
|
|
}
|
|
default:
|
|
// Catch-all for weird operations that should just be done
|
|
// at runtime.
|
|
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
if err != nil {
|
|
return nil, mem, err
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
var result uint64
|
|
switch inst.opcode {
|
|
case llvm.Add:
|
|
result = lhs.Uint() + rhs.Uint()
|
|
case llvm.Sub:
|
|
result = lhs.Uint() - rhs.Uint()
|
|
case llvm.Mul:
|
|
result = lhs.Uint() * rhs.Uint()
|
|
case llvm.UDiv:
|
|
result = lhs.Uint() / rhs.Uint()
|
|
case llvm.SDiv:
|
|
result = uint64(lhs.Int() / rhs.Int())
|
|
case llvm.URem:
|
|
result = lhs.Uint() % rhs.Uint()
|
|
case llvm.SRem:
|
|
result = uint64(lhs.Int() % rhs.Int())
|
|
case llvm.Shl:
|
|
result = lhs.Uint() << rhs.Uint()
|
|
case llvm.LShr:
|
|
result = lhs.Uint() >> rhs.Uint()
|
|
case llvm.AShr:
|
|
result = uint64(lhs.Int() >> rhs.Uint())
|
|
case llvm.And:
|
|
result = lhs.Uint() & rhs.Uint()
|
|
case llvm.Or:
|
|
result = lhs.Uint() | rhs.Uint()
|
|
case llvm.Xor:
|
|
result = lhs.Uint() ^ rhs.Uint()
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
switch lhs.len(r) {
|
|
case 8:
|
|
locals[inst.localIndex] = literalValue{result}
|
|
case 4:
|
|
locals[inst.localIndex] = literalValue{uint32(result)}
|
|
case 2:
|
|
locals[inst.localIndex] = literalValue{uint16(result)}
|
|
case 1:
|
|
locals[inst.localIndex] = literalValue{uint8(result)}
|
|
default:
|
|
panic("unknown integer size")
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
|
|
}
|
|
case llvm.SExt, llvm.ZExt, llvm.Trunc:
|
|
// Change the size of an integer to a larger or smaller bit width.
|
|
// We make use of the fact that the Uint() function already
|
|
// zero-extends the value and that Int() already sign-extends the
|
|
// value, so we only need to truncate it to the appropriate bit
|
|
// width. This means we can implement sext, zext and trunc in the
|
|
// same way, by first {zero,sign}extending all the way up to uint64
|
|
// and then truncating it as necessary.
|
|
var value uint64
|
|
if inst.opcode == llvm.SExt {
|
|
value = uint64(operands[0].Int())
|
|
} else {
|
|
value = operands[0].Uint()
|
|
}
|
|
bitwidth := operands[1].Uint()
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
|
}
|
|
switch bitwidth {
|
|
case 64:
|
|
locals[inst.localIndex] = literalValue{value}
|
|
case 32:
|
|
locals[inst.localIndex] = literalValue{uint32(value)}
|
|
case 16:
|
|
locals[inst.localIndex] = literalValue{uint16(value)}
|
|
case 8:
|
|
locals[inst.localIndex] = literalValue{uint8(value)}
|
|
default:
|
|
panic("unknown integer size in sext/zext/trunc")
|
|
}
|
|
case llvm.SIToFP, llvm.UIToFP:
|
|
var value float64
|
|
switch inst.opcode {
|
|
case llvm.SIToFP:
|
|
value = float64(operands[0].Int())
|
|
case llvm.UIToFP:
|
|
value = float64(operands[0].Uint())
|
|
}
|
|
bitwidth := operands[1].Uint()
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
|
}
|
|
switch bitwidth {
|
|
case 64:
|
|
locals[inst.localIndex] = literalValue{math.Float64bits(value)}
|
|
case 32:
|
|
locals[inst.localIndex] = literalValue{math.Float32bits(float32(value))}
|
|
default:
|
|
panic("unknown integer size in sitofp/uitofp")
|
|
}
|
|
default:
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+inst.String())
|
|
}
|
|
return nil, mem, r.errorAt(inst, errUnsupportedInst)
|
|
}
|
|
}
|
|
return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator"))
|
|
}
|
|
|
|
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
|
|
numOperands := inst.llvmInst.OperandsCount()
|
|
operands := make([]llvm.Value, numOperands)
|
|
for i := 0; i < numOperands; i++ {
|
|
operand := inst.llvmInst.Operand(i)
|
|
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
|
var err error
|
|
operand, err = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
|
|
if err != nil {
|
|
return r.errorAt(inst, err)
|
|
}
|
|
}
|
|
operands[i] = operand
|
|
}
|
|
if r.debug {
|
|
fmt.Fprintln(os.Stderr, indent+inst.String())
|
|
}
|
|
var result llvm.Value
|
|
switch inst.opcode {
|
|
case llvm.Call:
|
|
llvmFn := operands[len(operands)-1]
|
|
args := operands[:len(operands)-1]
|
|
for _, arg := range args {
|
|
if arg.Type().TypeKind() == llvm.PointerTypeKind {
|
|
err := mem.markExternalStore(arg)
|
|
if err != nil {
|
|
return r.errorAt(inst, err)
|
|
}
|
|
}
|
|
}
|
|
result = r.builder.CreateCall(llvmFn, args, inst.name)
|
|
case llvm.Load:
|
|
err := mem.markExternalLoad(operands[0])
|
|
if err != nil {
|
|
return r.errorAt(inst, err)
|
|
}
|
|
result = r.builder.CreateLoad(operands[0], inst.name)
|
|
if inst.llvmInst.IsVolatile() {
|
|
result.SetVolatile(true)
|
|
}
|
|
if ordering := inst.llvmInst.Ordering(); ordering != llvm.AtomicOrderingNotAtomic {
|
|
result.SetOrdering(ordering)
|
|
}
|
|
case llvm.Store:
|
|
err := mem.markExternalStore(operands[1])
|
|
if err != nil {
|
|
return r.errorAt(inst, err)
|
|
}
|
|
result = r.builder.CreateStore(operands[0], operands[1])
|
|
if inst.llvmInst.IsVolatile() {
|
|
result.SetVolatile(true)
|
|
}
|
|
if ordering := inst.llvmInst.Ordering(); ordering != llvm.AtomicOrderingNotAtomic {
|
|
result.SetOrdering(ordering)
|
|
}
|
|
case llvm.BitCast:
|
|
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
|
|
case llvm.ExtractValue:
|
|
indices := inst.llvmInst.Indices()
|
|
// Note: the Go LLVM API doesn't support multiple indices, so simulate
|
|
// this operation with some extra extractvalue instructions. Hopefully
|
|
// this is optimized to a single instruction.
|
|
agg := operands[0]
|
|
for i := 0; i < len(indices)-1; i++ {
|
|
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
|
|
mem.instructions = append(mem.instructions, agg)
|
|
}
|
|
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name)
|
|
case llvm.InsertValue:
|
|
indices := inst.llvmInst.Indices()
|
|
// Similar to extractvalue, we're working around a limitation in the Go
|
|
// LLVM API here by splitting the insertvalue into multiple instructions
|
|
// if there is more than one operand.
|
|
agg := operands[0]
|
|
aggregates := []llvm.Value{agg}
|
|
for i := 0; i < len(indices)-1; i++ {
|
|
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
|
|
aggregates = append(aggregates, agg)
|
|
mem.instructions = append(mem.instructions, agg)
|
|
}
|
|
result = operands[1]
|
|
for i := len(indices) - 1; i >= 0; i-- {
|
|
agg := aggregates[i]
|
|
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
|
|
if i != 0 { // don't add last result to mem.instructions as it will be done at the end already
|
|
mem.instructions = append(mem.instructions, result)
|
|
}
|
|
}
|
|
|
|
case llvm.Add:
|
|
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
|
|
case llvm.Sub:
|
|
result = r.builder.CreateSub(operands[0], operands[1], inst.name)
|
|
case llvm.Mul:
|
|
result = r.builder.CreateMul(operands[0], operands[1], inst.name)
|
|
case llvm.UDiv:
|
|
result = r.builder.CreateUDiv(operands[0], operands[1], inst.name)
|
|
case llvm.SDiv:
|
|
result = r.builder.CreateSDiv(operands[0], operands[1], inst.name)
|
|
case llvm.URem:
|
|
result = r.builder.CreateURem(operands[0], operands[1], inst.name)
|
|
case llvm.SRem:
|
|
result = r.builder.CreateSRem(operands[0], operands[1], inst.name)
|
|
case llvm.ZExt:
|
|
result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name)
|
|
default:
|
|
return r.errorAt(inst, errUnsupportedRuntimeInst)
|
|
}
|
|
locals[inst.localIndex] = localValue{result}
|
|
mem.instructions = append(mem.instructions, result)
|
|
return nil
|
|
}
|
|
|
|
func intPredicateString(predicate llvm.IntPredicate) string {
|
|
switch predicate {
|
|
case llvm.IntEQ:
|
|
return "eq"
|
|
case llvm.IntNE:
|
|
return "ne"
|
|
case llvm.IntUGT:
|
|
return "ugt"
|
|
case llvm.IntUGE:
|
|
return "uge"
|
|
case llvm.IntULT:
|
|
return "ult"
|
|
case llvm.IntULE:
|
|
return "ule"
|
|
case llvm.IntSGT:
|
|
return "sgt"
|
|
case llvm.IntSGE:
|
|
return "sge"
|
|
case llvm.IntSLT:
|
|
return "slt"
|
|
case llvm.IntSLE:
|
|
return "sle"
|
|
default:
|
|
return "cmp?"
|
|
}
|
|
}
|