2013-08-19 13:38:30 -06:00
|
|
|
// Package ssa/interp defines an interpreter for the SSA
|
2013-05-17 14:20:39 -06:00
|
|
|
// representation of Go programs.
|
|
|
|
//
|
|
|
|
// This interpreter is provided as an adjunct for testing the SSA
|
|
|
|
// construction algorithm. Its purpose is to provide a minimal
|
|
|
|
// metacircular implementation of the dynamic semantics of each SSA
|
|
|
|
// instruction. It is not, and will never be, a production-quality Go
|
|
|
|
// interpreter.
|
|
|
|
//
|
|
|
|
// The following is a partial list of Go features that are currently
|
|
|
|
// unsupported or incomplete in the interpreter.
|
|
|
|
//
|
|
|
|
// * Unsafe operations, including all uses of unsafe.Pointer, are
|
|
|
|
// impossible to support given the "boxed" value representation we
|
|
|
|
// have chosen.
|
|
|
|
//
|
|
|
|
// * The reflect package is only partially implemented.
|
|
|
|
//
|
|
|
|
// * "sync/atomic" operations are not currently atomic due to the
|
|
|
|
// "boxed" value representation: it is not possible to read, modify
|
|
|
|
// and write an interface value atomically. As a consequence, Mutexes
|
|
|
|
// are currently broken. TODO(adonovan): provide a metacircular
|
|
|
|
// implementation of Mutex avoiding the broken atomic primitives.
|
|
|
|
//
|
|
|
|
// * recover is only partially implemented. Also, the interpreter
|
|
|
|
// makes no attempt to distinguish target panics from interpreter
|
|
|
|
// crashes.
|
|
|
|
//
|
|
|
|
// * map iteration is asymptotically inefficient.
|
|
|
|
//
|
|
|
|
// * the equivalence relation for structs doesn't skip over blank
|
|
|
|
// fields.
|
|
|
|
//
|
|
|
|
// * the sizes of the int, uint and uintptr types in the target
|
|
|
|
// program are assumed to be the same as those of the interpreter
|
|
|
|
// itself.
|
|
|
|
//
|
|
|
|
// * all values occupy space, even those of types defined by the spec
|
|
|
|
// to have zero size, e.g. struct{}. This can cause asymptotic
|
|
|
|
// performance degradation.
|
|
|
|
//
|
|
|
|
// * os.Exit is implemented using panic, causing deferred functions to
|
|
|
|
// run.
|
|
|
|
package interp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
"runtime"
|
|
|
|
|
|
|
|
"code.google.com/p/go.tools/go/types"
|
|
|
|
"code.google.com/p/go.tools/ssa"
|
|
|
|
)
|
|
|
|
|
|
|
|
type status int
|
|
|
|
|
|
|
|
const (
|
|
|
|
stRunning status = iota
|
|
|
|
stComplete
|
|
|
|
stPanic
|
|
|
|
)
|
|
|
|
|
|
|
|
type continuation int
|
|
|
|
|
|
|
|
const (
|
|
|
|
kNext continuation = iota
|
|
|
|
kReturn
|
|
|
|
kJump
|
|
|
|
)
|
|
|
|
|
|
|
|
// Mode is a bitmask of options affecting the interpreter.
|
|
|
|
type Mode uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
DisableRecover Mode = 1 << iota // Disable recover() in target programs; show interpreter crash instead.
|
|
|
|
EnableTracing // Print a trace of all instructions as they are interpreted.
|
|
|
|
)
|
|
|
|
|
2013-07-30 12:28:14 -06:00
|
|
|
type methodSet map[string]*ssa.Function
|
|
|
|
|
2013-05-17 14:20:39 -06:00
|
|
|
// State shared between all interpreted goroutines.
|
|
|
|
type interpreter struct {
|
|
|
|
prog *ssa.Program // the SSA program
|
|
|
|
globals map[ssa.Value]*value // addresses of global variables (immutable)
|
|
|
|
mode Mode // interpreter options
|
|
|
|
reflectPackage *ssa.Package // the fake reflect package
|
2013-07-30 12:28:14 -06:00
|
|
|
errorMethods methodSet // the method set of reflect.error, which implements the error interface.
|
|
|
|
rtypeMethods methodSet // the method set of rtype, which implements the reflect.Type interface.
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type frame struct {
|
|
|
|
i *interpreter
|
|
|
|
caller *frame
|
|
|
|
fn *ssa.Function
|
|
|
|
block, prevBlock *ssa.BasicBlock
|
|
|
|
env map[ssa.Value]value // dynamic values of SSA variables
|
|
|
|
locals []value
|
|
|
|
defers []func()
|
|
|
|
result value
|
|
|
|
status status
|
|
|
|
panic interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fr *frame) get(key ssa.Value) value {
|
|
|
|
switch key := key.(type) {
|
|
|
|
case nil:
|
|
|
|
// Hack; simplifies handling of optional attributes
|
|
|
|
// such as ssa.Slice.{Low,High}.
|
|
|
|
return nil
|
|
|
|
case *ssa.Function, *ssa.Builtin:
|
|
|
|
return key
|
2013-07-16 11:50:08 -06:00
|
|
|
case *ssa.Const:
|
|
|
|
return constValue(key)
|
2013-05-17 14:20:39 -06:00
|
|
|
case *ssa.Global:
|
|
|
|
if r, ok := fr.i.globals[key]; ok {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if r, ok := fr.env[key]; ok {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("get: no value for %T: %v", key, key.Name()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fr *frame) rundefers() {
|
|
|
|
for i := range fr.defers {
|
|
|
|
if fr.i.mode&EnableTracing != 0 {
|
|
|
|
fmt.Fprintln(os.Stderr, "Invoking deferred function", i)
|
|
|
|
}
|
|
|
|
fr.defers[len(fr.defers)-1-i]()
|
|
|
|
}
|
|
|
|
fr.defers = fr.defers[:0]
|
|
|
|
}
|
|
|
|
|
2013-07-26 09:22:34 -06:00
|
|
|
// lookupMethod returns the method set for type typ, which may be one
|
2013-05-17 14:20:39 -06:00
|
|
|
// of the interpreter's fake types.
|
2013-07-26 09:22:34 -06:00
|
|
|
func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Function {
|
2013-05-17 14:20:39 -06:00
|
|
|
switch typ {
|
|
|
|
case rtypeType:
|
2013-07-26 09:22:34 -06:00
|
|
|
return i.rtypeMethods[meth.Id()]
|
2013-05-17 14:20:39 -06:00
|
|
|
case errorType:
|
2013-07-26 09:22:34 -06:00
|
|
|
return i.errorMethods[meth.Id()]
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
2013-07-30 14:36:58 -06:00
|
|
|
return i.prog.Method(typ.MethodSet().Lookup(meth.Pkg(), meth.Name()))
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// visitInstr interprets a single ssa.Instruction within the activation
|
|
|
|
// record frame. It returns a continuation value indicating where to
|
|
|
|
// read the next instruction from.
|
|
|
|
func visitInstr(fr *frame, instr ssa.Instruction) continuation {
|
|
|
|
switch instr := instr.(type) {
|
go.tools/ssa: add debug information for all ast.Idents.
This CL adds three new functions to determine the SSA Value
for a given syntactic var, func or const object:
Program.{Const,Func,Var}Value.
Since constants and functions are immutable, the first
two only need a types.Object; but each distinct
reference to a var may return a distinct Value, so the third
requires an ast.Ident parameter too.
Debug information for local vars is encoded in the
instruction stream in the form of DebugRef instructions,
which are a no-op but relate their operand to a particular
ident in the AST. The beauty of this approach is that it
naturally stays consistent during optimisation passes
(e.g. lifting) without additional bookkeeping.
DebugRef instructions are only generated if the DebugMode
builder flag is set; I plan to make the policy more fine-
grained (per function).
DebugRef instructions are inserted for:
- expr(Ident) for rvalue idents
- address.store() for idents that update an lvalue
- address.address() for idents that take address of lvalue
(this new method replaces all uses of lval.(address).addr)
- expr() for all constant expressions
- local ValueSpecs with implicit zero initialization (no RHS)
(this case doesn't call store() or address())
To ensure we don't forget to emit debug info for uses of Idents,
we must use the lvalue mechanism consistently. (Previously,
many simple cases had effectively inlined these functions.)
Similarly setCallFunc no longer inlines expr(Ident).
Also:
- Program.Value() has been inlined & specialized.
- Program.Package() has moved nearer the new lookup functions.
- refactoring: funcSyntax has lost paramFields, resultFields;
gained funcType, which provides access to both.
- add package-level constants to Package.values map.
- opt: don't call localValueSpec for constants.
(The resulting code is always optimised away.)
There are a number of comments asking whether Literals
should have positions. Will address in a follow-up.
Added tests of all interesting cases.
R=gri
CC=golang-dev
https://golang.org/cl/11259044
2013-07-15 11:56:46 -06:00
|
|
|
case *ssa.DebugRef:
|
|
|
|
// no-op
|
|
|
|
|
2013-05-17 14:20:39 -06:00
|
|
|
case *ssa.UnOp:
|
|
|
|
fr.env[instr] = unop(instr, fr.get(instr.X))
|
|
|
|
|
|
|
|
case *ssa.BinOp:
|
|
|
|
fr.env[instr] = binop(instr.Op, fr.get(instr.X), fr.get(instr.Y))
|
|
|
|
|
|
|
|
case *ssa.Call:
|
|
|
|
fn, args := prepareCall(fr, &instr.Call)
|
2013-05-17 15:02:47 -06:00
|
|
|
fr.env[instr] = call(fr.i, fr, instr.Pos(), fn, args)
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
case *ssa.ChangeInterface:
|
2013-07-26 19:49:27 -06:00
|
|
|
fr.env[instr] = fr.get(instr.X)
|
2013-05-17 14:20:39 -06:00
|
|
|
|
2013-05-17 15:02:47 -06:00
|
|
|
case *ssa.ChangeType:
|
|
|
|
fr.env[instr] = fr.get(instr.X) // (can't fail)
|
|
|
|
|
|
|
|
case *ssa.Convert:
|
|
|
|
fr.env[instr] = conv(instr.Type(), instr.X.Type(), fr.get(instr.X))
|
|
|
|
|
2013-05-17 14:20:39 -06:00
|
|
|
case *ssa.MakeInterface:
|
|
|
|
fr.env[instr] = iface{t: instr.X.Type(), v: fr.get(instr.X)}
|
|
|
|
|
|
|
|
case *ssa.Extract:
|
|
|
|
fr.env[instr] = fr.get(instr.Tuple).(tuple)[instr.Index]
|
|
|
|
|
|
|
|
case *ssa.Slice:
|
|
|
|
fr.env[instr] = slice(fr.get(instr.X), fr.get(instr.Low), fr.get(instr.High))
|
|
|
|
|
|
|
|
case *ssa.Ret:
|
|
|
|
switch len(instr.Results) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
fr.result = fr.get(instr.Results[0])
|
|
|
|
default:
|
|
|
|
var res []value
|
|
|
|
for _, r := range instr.Results {
|
|
|
|
res = append(res, fr.get(r))
|
|
|
|
}
|
|
|
|
fr.result = tuple(res)
|
|
|
|
}
|
|
|
|
return kReturn
|
|
|
|
|
|
|
|
case *ssa.RunDefers:
|
|
|
|
fr.rundefers()
|
|
|
|
|
|
|
|
case *ssa.Panic:
|
|
|
|
panic(targetPanic{fr.get(instr.X)})
|
|
|
|
|
|
|
|
case *ssa.Send:
|
|
|
|
fr.get(instr.Chan).(chan value) <- copyVal(fr.get(instr.X))
|
|
|
|
|
|
|
|
case *ssa.Store:
|
|
|
|
*fr.get(instr.Addr).(*value) = copyVal(fr.get(instr.Val))
|
|
|
|
|
|
|
|
case *ssa.If:
|
|
|
|
succ := 1
|
|
|
|
if fr.get(instr.Cond).(bool) {
|
|
|
|
succ = 0
|
|
|
|
}
|
|
|
|
fr.prevBlock, fr.block = fr.block, fr.block.Succs[succ]
|
|
|
|
return kJump
|
|
|
|
|
|
|
|
case *ssa.Jump:
|
|
|
|
fr.prevBlock, fr.block = fr.block, fr.block.Succs[0]
|
|
|
|
return kJump
|
|
|
|
|
|
|
|
case *ssa.Defer:
|
|
|
|
fn, args := prepareCall(fr, &instr.Call)
|
2013-06-24 12:15:13 -06:00
|
|
|
fr.defers = append(fr.defers, func() { call(fr.i, fr, instr.Pos(), fn, args) })
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
case *ssa.Go:
|
|
|
|
fn, args := prepareCall(fr, &instr.Call)
|
2013-05-17 15:02:47 -06:00
|
|
|
go call(fr.i, nil, instr.Pos(), fn, args)
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
case *ssa.MakeChan:
|
|
|
|
fr.env[instr] = make(chan value, asInt(fr.get(instr.Size)))
|
|
|
|
|
|
|
|
case *ssa.Alloc:
|
|
|
|
var addr *value
|
|
|
|
if instr.Heap {
|
|
|
|
// new
|
|
|
|
addr = new(value)
|
|
|
|
fr.env[instr] = addr
|
|
|
|
} else {
|
|
|
|
// local
|
|
|
|
addr = fr.env[instr].(*value)
|
|
|
|
}
|
2013-07-12 22:09:33 -06:00
|
|
|
*addr = zero(deref(instr.Type()))
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
case *ssa.MakeSlice:
|
|
|
|
slice := make([]value, asInt(fr.get(instr.Cap)))
|
2013-05-17 15:02:47 -06:00
|
|
|
tElt := instr.Type().Underlying().(*types.Slice).Elem()
|
2013-05-17 14:20:39 -06:00
|
|
|
for i := range slice {
|
|
|
|
slice[i] = zero(tElt)
|
|
|
|
}
|
|
|
|
fr.env[instr] = slice[:asInt(fr.get(instr.Len))]
|
|
|
|
|
|
|
|
case *ssa.MakeMap:
|
|
|
|
reserve := 0
|
|
|
|
if instr.Reserve != nil {
|
|
|
|
reserve = asInt(fr.get(instr.Reserve))
|
|
|
|
}
|
2013-05-17 15:02:47 -06:00
|
|
|
fr.env[instr] = makeMap(instr.Type().Underlying().(*types.Map).Key(), reserve)
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
case *ssa.Range:
|
|
|
|
fr.env[instr] = rangeIter(fr.get(instr.X), instr.X.Type())
|
|
|
|
|
|
|
|
case *ssa.Next:
|
|
|
|
fr.env[instr] = fr.get(instr.Iter).(iter).next()
|
|
|
|
|
|
|
|
case *ssa.FieldAddr:
|
|
|
|
x := fr.get(instr.X)
|
|
|
|
fr.env[instr] = &(*x.(*value)).(structure)[instr.Field]
|
|
|
|
|
|
|
|
case *ssa.Field:
|
|
|
|
fr.env[instr] = copyVal(fr.get(instr.X).(structure)[instr.Field])
|
|
|
|
|
|
|
|
case *ssa.IndexAddr:
|
|
|
|
x := fr.get(instr.X)
|
|
|
|
idx := fr.get(instr.Index)
|
|
|
|
switch x := x.(type) {
|
|
|
|
case []value:
|
|
|
|
fr.env[instr] = &x[asInt(idx)]
|
|
|
|
case *value: // *array
|
|
|
|
fr.env[instr] = &(*x).(array)[asInt(idx)]
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unexpected x type in IndexAddr: %T", x))
|
|
|
|
}
|
|
|
|
|
|
|
|
case *ssa.Index:
|
|
|
|
fr.env[instr] = copyVal(fr.get(instr.X).(array)[asInt(fr.get(instr.Index))])
|
|
|
|
|
|
|
|
case *ssa.Lookup:
|
|
|
|
fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index))
|
|
|
|
|
|
|
|
case *ssa.MapUpdate:
|
|
|
|
m := fr.get(instr.Map)
|
|
|
|
key := fr.get(instr.Key)
|
|
|
|
v := fr.get(instr.Value)
|
|
|
|
switch m := m.(type) {
|
|
|
|
case map[value]value:
|
|
|
|
m[key] = v
|
|
|
|
case *hashmap:
|
|
|
|
m.insert(key.(hashable), v)
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("illegal map type: %T", m))
|
|
|
|
}
|
|
|
|
|
|
|
|
case *ssa.TypeAssert:
|
|
|
|
fr.env[instr] = typeAssert(fr.i, instr, fr.get(instr.X).(iface))
|
|
|
|
|
|
|
|
case *ssa.MakeClosure:
|
|
|
|
var bindings []value
|
|
|
|
for _, binding := range instr.Bindings {
|
|
|
|
bindings = append(bindings, fr.get(binding))
|
|
|
|
}
|
|
|
|
fr.env[instr] = &closure{instr.Fn.(*ssa.Function), bindings}
|
|
|
|
|
|
|
|
case *ssa.Phi:
|
2013-05-30 07:59:17 -06:00
|
|
|
for i, pred := range instr.Block().Preds {
|
2013-05-17 14:20:39 -06:00
|
|
|
if fr.prevBlock == pred {
|
|
|
|
fr.env[instr] = fr.get(instr.Edges[i])
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case *ssa.Select:
|
|
|
|
var cases []reflect.SelectCase
|
|
|
|
if !instr.Blocking {
|
|
|
|
cases = append(cases, reflect.SelectCase{
|
|
|
|
Dir: reflect.SelectDefault,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, state := range instr.States {
|
|
|
|
var dir reflect.SelectDir
|
|
|
|
if state.Dir == ast.RECV {
|
|
|
|
dir = reflect.SelectRecv
|
|
|
|
} else {
|
|
|
|
dir = reflect.SelectSend
|
|
|
|
}
|
|
|
|
var send reflect.Value
|
|
|
|
if state.Send != nil {
|
|
|
|
send = reflect.ValueOf(fr.get(state.Send))
|
|
|
|
}
|
|
|
|
cases = append(cases, reflect.SelectCase{
|
|
|
|
Dir: dir,
|
|
|
|
Chan: reflect.ValueOf(fr.get(state.Chan)),
|
|
|
|
Send: send,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
chosen, recv, recvOk := reflect.Select(cases)
|
|
|
|
if !instr.Blocking {
|
|
|
|
chosen-- // default case should have index -1.
|
|
|
|
}
|
2013-06-24 12:15:13 -06:00
|
|
|
r := tuple{chosen, recvOk}
|
|
|
|
for i, st := range instr.States {
|
|
|
|
if st.Dir == ast.RECV {
|
|
|
|
var v value
|
|
|
|
if i == chosen && recvOk {
|
|
|
|
// No need to copy since send makes an unaliased copy.
|
|
|
|
v = recv.Interface().(value)
|
|
|
|
} else {
|
|
|
|
v = zero(st.Chan.Type().Underlying().(*types.Chan).Elem())
|
|
|
|
}
|
|
|
|
r = append(r, v)
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
}
|
2013-06-24 12:15:13 -06:00
|
|
|
fr.env[instr] = r
|
2013-05-17 14:20:39 -06:00
|
|
|
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unexpected instruction: %T", instr))
|
|
|
|
}
|
|
|
|
|
|
|
|
// if val, ok := instr.(ssa.Value); ok {
|
|
|
|
// fmt.Println(toString(fr.env[val])) // debugging
|
|
|
|
// }
|
|
|
|
|
|
|
|
return kNext
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepareCall determines the function value and argument values for a
|
2013-06-13 12:43:35 -06:00
|
|
|
// function call in a Call, Go or Defer instruction, performing
|
2013-05-17 14:20:39 -06:00
|
|
|
// interface method lookup if needed.
|
|
|
|
//
|
|
|
|
func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
2013-07-26 12:06:26 -06:00
|
|
|
v := fr.get(call.Value)
|
|
|
|
if call.Method == nil {
|
2013-05-17 14:20:39 -06:00
|
|
|
// Function call.
|
2013-07-26 12:06:26 -06:00
|
|
|
fn = v
|
2013-05-17 14:20:39 -06:00
|
|
|
} else {
|
|
|
|
// Interface method invocation.
|
2013-07-26 12:06:26 -06:00
|
|
|
recv := v.(iface)
|
2013-05-17 14:20:39 -06:00
|
|
|
if recv.t == nil {
|
|
|
|
panic("method invoked on nil interface")
|
|
|
|
}
|
2013-07-26 09:22:34 -06:00
|
|
|
fn = lookupMethod(fr.i, recv.t, call.Method)
|
2013-06-13 12:43:35 -06:00
|
|
|
if fn == nil {
|
2013-05-17 14:20:39 -06:00
|
|
|
// Unreachable in well-typed programs.
|
2013-07-26 09:22:34 -06:00
|
|
|
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
2013-06-13 12:43:35 -06:00
|
|
|
args = append(args, copyVal(recv.v))
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
for _, arg := range call.Args {
|
|
|
|
args = append(args, fr.get(arg))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// call interprets a call to a function (function, builtin or closure)
|
|
|
|
// fn with arguments args, returning its result.
|
|
|
|
// callpos is the position of the callsite.
|
|
|
|
//
|
|
|
|
func call(i *interpreter, caller *frame, callpos token.Pos, fn value, args []value) value {
|
|
|
|
switch fn := fn.(type) {
|
|
|
|
case *ssa.Function:
|
|
|
|
if fn == nil {
|
|
|
|
panic("call of nil function") // nil of func type
|
|
|
|
}
|
|
|
|
return callSSA(i, caller, callpos, fn, args, nil)
|
|
|
|
case *closure:
|
|
|
|
return callSSA(i, caller, callpos, fn.Fn, args, fn.Env)
|
|
|
|
case *ssa.Builtin:
|
|
|
|
return callBuiltin(caller, callpos, fn, args)
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("cannot call %T", fn))
|
|
|
|
}
|
|
|
|
|
|
|
|
func loc(fset *token.FileSet, pos token.Pos) string {
|
|
|
|
if pos == token.NoPos {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return " at " + fset.Position(pos).String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// callSSA interprets a call to function fn with arguments args,
|
|
|
|
// and lexical environment env, returning its result.
|
|
|
|
// callpos is the position of the callsite.
|
|
|
|
//
|
|
|
|
func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value {
|
|
|
|
if i.mode&EnableTracing != 0 {
|
2013-07-01 13:24:50 -06:00
|
|
|
fset := fn.Prog.Fset
|
2013-05-17 14:20:39 -06:00
|
|
|
// TODO(adonovan): fix: loc() lies for external functions.
|
2013-06-26 10:38:08 -06:00
|
|
|
fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn, loc(fset, fn.Pos()))
|
2013-05-17 14:20:39 -06:00
|
|
|
suffix := ""
|
|
|
|
if caller != nil {
|
2013-06-26 10:38:08 -06:00
|
|
|
suffix = ", resuming " + caller.fn.String() + loc(fset, callpos)
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
2013-06-26 10:38:08 -06:00
|
|
|
defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn, suffix)
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
if fn.Enclosing == nil {
|
2013-06-26 10:38:08 -06:00
|
|
|
name := fn.String()
|
2013-05-17 14:20:39 -06:00
|
|
|
if ext := externals[name]; ext != nil {
|
|
|
|
if i.mode&EnableTracing != 0 {
|
|
|
|
fmt.Fprintln(os.Stderr, "\t(external)")
|
|
|
|
}
|
|
|
|
return ext(fn, args)
|
|
|
|
}
|
|
|
|
if fn.Blocks == nil {
|
|
|
|
panic("no code for function: " + name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fr := &frame{
|
|
|
|
i: i,
|
|
|
|
caller: caller, // currently unused; for unwinding.
|
|
|
|
fn: fn,
|
|
|
|
env: make(map[ssa.Value]value),
|
|
|
|
block: fn.Blocks[0],
|
|
|
|
locals: make([]value, len(fn.Locals)),
|
|
|
|
}
|
|
|
|
for i, l := range fn.Locals {
|
2013-07-12 22:09:33 -06:00
|
|
|
fr.locals[i] = zero(deref(l.Type()))
|
2013-05-17 14:20:39 -06:00
|
|
|
fr.env[l] = &fr.locals[i]
|
|
|
|
}
|
|
|
|
for i, p := range fn.Params {
|
|
|
|
fr.env[p] = args[i]
|
|
|
|
}
|
|
|
|
for i, fv := range fn.FreeVars {
|
|
|
|
fr.env[fv] = env[i]
|
|
|
|
}
|
|
|
|
var instr ssa.Instruction
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if fr.status != stComplete {
|
|
|
|
if fr.i.mode&DisableRecover != 0 {
|
|
|
|
return // let interpreter crash
|
|
|
|
}
|
2013-07-01 13:17:36 -06:00
|
|
|
fr.status = stPanic
|
|
|
|
fr.panic = recover()
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
fr.rundefers()
|
|
|
|
// Destroy the locals to avoid accidental use after return.
|
|
|
|
for i := range fn.Locals {
|
|
|
|
fr.locals[i] = bad{}
|
|
|
|
}
|
|
|
|
if fr.status == stPanic {
|
|
|
|
panic(fr.panic) // panic stack is not entirely clean
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
if i.mode&EnableTracing != 0 {
|
|
|
|
fmt.Fprintf(os.Stderr, ".%s:\n", fr.block)
|
|
|
|
}
|
|
|
|
block:
|
|
|
|
for _, instr = range fr.block.Instrs {
|
|
|
|
if i.mode&EnableTracing != 0 {
|
|
|
|
if v, ok := instr.(ssa.Value); ok {
|
|
|
|
fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(os.Stderr, "\t", instr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch visitInstr(fr, instr) {
|
|
|
|
case kReturn:
|
|
|
|
fr.status = stComplete
|
|
|
|
return fr.result
|
|
|
|
case kNext:
|
|
|
|
// no-op
|
|
|
|
case kJump:
|
|
|
|
break block
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
|
|
|
|
// setGlobal sets the value of a system-initialized global variable.
|
|
|
|
func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
|
|
|
|
if g, ok := i.globals[pkg.Var(name)]; ok {
|
|
|
|
*g = v
|
|
|
|
return
|
|
|
|
}
|
2013-07-01 13:24:50 -06:00
|
|
|
panic("no global variable: " + pkg.Object.Path() + "." + name)
|
2013-05-17 14:20:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Interpret interprets the Go program whose main package is mainpkg.
|
|
|
|
// mode specifies various interpreter options. filename and args are
|
|
|
|
// the initial values of os.Args for the target program.
|
|
|
|
//
|
|
|
|
// Interpret returns the exit code of the program: 2 for panic (like
|
|
|
|
// gc does), or the argument to os.Exit for normal termination.
|
|
|
|
//
|
|
|
|
func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) (exitCode int) {
|
|
|
|
i := &interpreter{
|
|
|
|
prog: mainpkg.Prog,
|
|
|
|
globals: make(map[ssa.Value]*value),
|
|
|
|
mode: mode,
|
|
|
|
}
|
|
|
|
initReflect(i)
|
|
|
|
|
2013-07-01 13:24:50 -06:00
|
|
|
for importPath, pkg := range i.prog.PackagesByPath {
|
2013-05-17 14:20:39 -06:00
|
|
|
// Initialize global storage.
|
|
|
|
for _, m := range pkg.Members {
|
|
|
|
switch v := m.(type) {
|
|
|
|
case *ssa.Global:
|
2013-07-12 22:09:33 -06:00
|
|
|
cell := zero(deref(v.Type()))
|
2013-05-17 14:20:39 -06:00
|
|
|
i.globals[v] = &cell
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ad-hoc initialization for magic system variables.
|
|
|
|
switch importPath {
|
|
|
|
case "syscall":
|
|
|
|
var envs []value
|
|
|
|
for _, s := range os.Environ() {
|
|
|
|
envs = append(envs, s)
|
|
|
|
}
|
|
|
|
envs = append(envs, "GOSSAINTERP=1")
|
|
|
|
setGlobal(i, pkg, "envs", envs)
|
|
|
|
|
|
|
|
case "runtime":
|
|
|
|
// TODO(gri): expose go/types.sizeof so we can
|
|
|
|
// avoid this fragile magic number;
|
|
|
|
// unsafe.Sizeof(memStats) won't work since gc
|
|
|
|
// and go/types have different sizeof
|
|
|
|
// functions.
|
|
|
|
setGlobal(i, pkg, "sizeof_C_MStats", uintptr(3696))
|
|
|
|
|
|
|
|
case "os":
|
|
|
|
Args := []value{filename}
|
|
|
|
for _, s := range args {
|
|
|
|
Args = append(Args, s)
|
|
|
|
}
|
|
|
|
setGlobal(i, pkg, "Args", Args)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Top-level error handler.
|
|
|
|
exitCode = 2
|
|
|
|
defer func() {
|
|
|
|
if exitCode != 2 || i.mode&DisableRecover != 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch p := recover().(type) {
|
|
|
|
case exitPanic:
|
|
|
|
exitCode = int(p)
|
|
|
|
return
|
|
|
|
case targetPanic:
|
|
|
|
fmt.Fprintln(os.Stderr, "panic:", toString(p.v))
|
|
|
|
case runtime.Error:
|
|
|
|
fmt.Fprintln(os.Stderr, "panic:", p.Error())
|
|
|
|
case string:
|
|
|
|
fmt.Fprintln(os.Stderr, "panic:", p)
|
|
|
|
default:
|
|
|
|
fmt.Fprintf(os.Stderr, "panic: unexpected type: %T\n", p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(adonovan): dump panicking interpreter goroutine?
|
|
|
|
// buf := make([]byte, 0x10000)
|
|
|
|
// runtime.Stack(buf, false)
|
|
|
|
// fmt.Fprintln(os.Stderr, string(buf))
|
|
|
|
// (Or dump panicking target goroutine?)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Run!
|
2013-07-10 16:37:52 -06:00
|
|
|
call(i, nil, token.NoPos, mainpkg.Func("init"), nil)
|
2013-05-17 14:20:39 -06:00
|
|
|
if mainFn := mainpkg.Func("main"); mainFn != nil {
|
|
|
|
call(i, nil, token.NoPos, mainFn, nil)
|
|
|
|
exitCode = 0
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(os.Stderr, "No main function.")
|
|
|
|
exitCode = 1
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2013-07-12 22:09:33 -06:00
|
|
|
|
|
|
|
// deref returns a pointer's element type; otherwise it returns typ.
|
|
|
|
// TODO(adonovan): Import from ssa?
|
|
|
|
func deref(typ types.Type) types.Type {
|
|
|
|
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
|
|
|
return p.Elem()
|
|
|
|
}
|
|
|
|
return typ
|
|
|
|
}
|