package interp
import (
"errors"
"fmt"
"math"
"os"
"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 ++
if time . Since ( r . start ) > time . Minute {
// Running for more than a minute. This should never happen.
return nil , mem , r . errorAt ( fn . blocks [ 0 ] . instructions [ 0 ] , fmt . Errorf ( "interp: running for more than a minute, timing out (executed calls: %d)" , r . callsExecuted ) )
}
// Parameters are considered a kind of local values.
for i , param := range params {
locals [ i ] = param
}
// 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
for instIndex := 0 ; instIndex < len ( bb . instructions ) ; instIndex ++ {
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 {
if localVal := locals [ fn . locals [ v . value ] ] ; localVal == nil {
return nil , mem , r . errorAt ( inst , errors . New ( "interp: local not defined" ) )
} else {
operands = append ( operands , localVal )
if _ , ok := localVal . ( localValue ) ; ok {
isRuntimeInst = true
}
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 :
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" )
}
break // continue with next block
case llvm . PHI :
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" )
}
locals [ inst . localIndex ] = result
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" :
// 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.
err := r . runAtRuntime ( fn , inst , locals , & mem , indent )
if err != nil {
return nil , mem , err
}
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 ( )
// Create the object.
alloc := object {
globalName : r . pkgName + "$alloc" ,
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.
typecodeID := operands [ 1 ] . toLLVMValue ( inst . llvmInst . Operand ( 0 ) . Type ( ) , & mem ) . 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 := operands [ 2 ] . toLLVMValue ( inst . llvmInst . Operand ( 1 ) . Type ( ) , & mem )
actualTypePtrToInt := operands [ 1 ] . toLLVMValue ( inst . llvmInst . Operand ( 0 ) . Type ( ) , & mem )
actualType := actualTypePtrToInt . Operand ( 0 )
if actualType . Name ( ) + "$id" == assertedType . Name ( ) {
locals [ inst . localIndex ] = literalValue { uint8 ( 1 ) }
} else {
locals [ inst . localIndex ] = literalValue { uint8 ( 0 ) }
}
case callFn . name == "runtime.interfaceImplements" :
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 ( )
interfaceMethodSetPtr , err := operands [ 2 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
interfaceMethodSet := mem . get ( interfaceMethodSetPtr . index ( ) ) . llvmGlobal . Initializer ( )
// 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 i := 0 ; i < interfaceMethodSet . Type ( ) . ArrayLength ( ) ; i ++ {
name := llvm . ConstExtractValue ( interfaceMethodSet , [ ] uint32 { uint32 ( i ) } ) . Name ( )
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 callFn . name == "runtime.interfaceMethod" :
// This builtin returns the function (which may be a thunk) to
// invoke a method on an interface. It does not call the method.
if r . debug {
fmt . Fprintln ( os . Stderr , indent + "interface method:" , operands [ 1 : ] )
}
// Load the first param, which is the type code (ptrtoint of the
// type code global).
typecodeID := operands [ 1 ] . toLLVMValue ( inst . llvmInst . Operand ( 0 ) . Type ( ) , & mem ) . 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.
signature := inst . llvmInst . Operand ( 2 )
// 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: " + signature . Name ( ) ) )
}
locals [ inst . localIndex ] = r . getValue ( method )
case callFn . name == "runtime.hashmapMake" :
// Create a new map.
hashmapPointerType := inst . llvmInst . Type ( )
keySize := uint32 ( operands [ 1 ] . Uint ( ) )
valueSize := uint32 ( operands [ 2 ] . Uint ( ) )
m := newMapValue ( r , hashmapPointerType , keySize , valueSize )
alloc := object {
llvmType : hashmapPointerType ,
globalName : r . pkgName + "$map" ,
buffer : m ,
size : m . len ( r ) ,
}
index := len ( r . objects )
r . objects = append ( r . objects , alloc )
// Create a pointer to this map. Maps are reference types, so
// are implemented as pointers.
ptr := newPointerValue ( r , index , 0 )
if r . debug {
fmt . Fprintln ( os . Stderr , indent + "runtime.hashmapMake:" , keySize , valueSize , "->" , ptr )
}
locals [ inst . localIndex ] = ptr
case callFn . name == "runtime.hashmapBinarySet" :
// Do a mapassign operation with a binary key (that is, without
// a string key).
if r . debug {
fmt . Fprintln ( os . Stderr , indent + "runtime.hashmapBinarySet:" , operands [ 1 : ] )
}
mapPtr , err := operands [ 1 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
m := mem . getWritable ( mapPtr . index ( ) ) . buffer . ( * mapValue )
keyPtr , err := operands [ 2 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
valuePtr , err := operands [ 3 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
err = m . putBinary ( & mem , keyPtr , valuePtr )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
case callFn . name == "runtime.hashmapStringSet" :
// Do a mapassign operation with a string key.
if r . debug {
fmt . Fprintln ( os . Stderr , indent + "runtime.hashmapBinarySet:" , operands [ 1 : ] )
}
mapPtr , err := operands [ 1 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
m := mem . getWritable ( mapPtr . index ( ) ) . buffer . ( * mapValue )
stringPtr , err := operands [ 2 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
stringLen := operands [ 3 ] . Uint ( )
valuePtr , err := operands [ 4 ] . asPointer ( r )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
err = m . putString ( & mem , stringPtr , stringLen , valuePtr )
if err != nil {
return nil , mem , r . errorAt ( inst , err )
}
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 := range argStrings {
argStrings [ i ] = operands [ i + 1 ] . 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 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 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 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 )
}
mem . store ( val , ptr )
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
var gepOperands [ ] 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.
// The field number is encoded by flipping all the bits.
gepOperands = append ( gepOperands , ^ elementSize )
offset += index
} else {
// This is a normal GEP, probably an array index.
gepOperands = append ( gepOperands , 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 ( ) {
operand = locals [ fn . locals [ operand ] ] . toLLVMValue ( operand . Type ( ) , mem )
}
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 {
mem . markExternalStore ( arg )
}
}
result = r . builder . CreateCall ( llvmFn , args , inst . name )
case llvm . Load :
mem . markExternalLoad ( operands [ 0 ] )
result = r . builder . CreateLoad ( operands [ 0 ] , inst . name )
if inst . llvmInst . IsVolatile ( ) {
result . SetVolatile ( true )
}
case llvm . Store :
mem . markExternalStore ( operands [ 1 ] )
result = r . builder . CreateStore ( operands [ 0 ] , operands [ 1 ] )
if inst . llvmInst . IsVolatile ( ) {
result . SetVolatile ( true )
}
case llvm . BitCast :
result = r . builder . CreateBitCast ( operands [ 0 ] , inst . llvmInst . Type ( ) , inst . name )
case llvm . ExtractValue :
indices := inst . llvmInst . Indices ( )
if len ( indices ) != 1 {
panic ( "expected exactly one index" )
}
result = r . builder . CreateExtractValue ( operands [ 0 ] , int ( indices [ 0 ] ) , inst . name )
case llvm . InsertValue :
indices := inst . llvmInst . Indices ( )
if len ( indices ) != 1 {
panic ( "expected exactly one index" )
}
result = r . builder . CreateInsertValue ( operands [ 0 ] , operands [ 1 ] , int ( indices [ 0 ] ) , inst . name )
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?"
}
}