// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package ssa/interp defines an interpreter for the SSA // 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. // // * The "testing" package is no longer supported because it // depends on low-level details that change too often. // // * "sync/atomic" operations are not 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. // // * recover is only partially implemented. Also, the interpreter // makes no attempt to distinguish target panics from interpreter // crashes. // // * 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 "golang.org/x/tools/go/ssa/interp" import ( "fmt" "go/token" "go/types" "os" "reflect" "runtime" "sync/atomic" "golang.org/x/tools/go/ssa" ) 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. ) type methodSet map[string]*ssa.Function // State shared between all interpreted goroutines. type interpreter struct { osArgs []value // the value of os.Args 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 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. runtimeErrorString types.Type // the runtime.errorString type sizes types.Sizes // the effective type-sizing function goroutines int32 // atomically updated } type deferred struct { fn value args []value instr *ssa.Defer tail *deferred } 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 *deferred result value panicking bool 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 case *ssa.Const: return constValue(key) 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())) } // runDefer runs a deferred call d. // It always returns normally, but may set or clear fr.panic. // func (fr *frame) runDefer(d *deferred) { if fr.i.mode&EnableTracing != 0 { fmt.Fprintf(os.Stderr, "%s: invoking deferred function call\n", fr.i.prog.Fset.Position(d.instr.Pos())) } var ok bool defer func() { if !ok { // Deferred call created a new state of panic. fr.panicking = true fr.panic = recover() } }() call(fr.i, fr, d.instr.Pos(), d.fn, d.args) ok = true } // runDefers executes fr's deferred function calls in LIFO order. // // On entry, fr.panicking indicates a state of panic; if // true, fr.panic contains the panic value. // // On completion, if a deferred call started a panic, or if no // deferred call recovered from a previous state of panic, then // runDefers itself panics after the last deferred call has run. // // If there was no initial state of panic, or it was recovered from, // runDefers returns normally. // func (fr *frame) runDefers() { for d := fr.defers; d != nil; d = d.tail { fr.runDefer(d) } fr.defers = nil if fr.panicking { panic(fr.panic) // new panic, or still panicking } } // lookupMethod returns the method set for type typ, which may be one // of the interpreter's fake types. func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Function { switch typ { case rtypeType: return i.rtypeMethods[meth.Id()] case errorType: return i.errorMethods[meth.Id()] } return i.prog.LookupMethod(typ, meth.Pkg(), meth.Name()) } // 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) { case *ssa.DebugRef: // no-op case *ssa.UnOp: fr.env[instr] = unop(instr, fr.get(instr.X)) case *ssa.BinOp: fr.env[instr] = binop(instr.Op, instr.X.Type(), fr.get(instr.X), fr.get(instr.Y)) case *ssa.Call: fn, args := prepareCall(fr, &instr.Call) fr.env[instr] = call(fr.i, fr, instr.Pos(), fn, args) case *ssa.ChangeInterface: fr.env[instr] = fr.get(instr.X) 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)) 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), fr.get(instr.Max)) case *ssa.Return: 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) } fr.block = nil 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) <- fr.get(instr.X) case *ssa.Store: store(deref(instr.Addr.Type()), fr.get(instr.Addr).(*value), 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) fr.defers = &deferred{ fn: fn, args: args, instr: instr, tail: fr.defers, } case *ssa.Go: fn, args := prepareCall(fr, &instr.Call) atomic.AddInt32(&fr.i.goroutines, 1) go func() { call(fr.i, nil, instr.Pos(), fn, args) atomic.AddInt32(&fr.i.goroutines, -1) }() 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) } *addr = zero(deref(instr.Type())) case *ssa.MakeSlice: slice := make([]value, asInt(fr.get(instr.Cap))) tElt := instr.Type().Underlying().(*types.Slice).Elem() 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)) } fr.env[instr] = makeMap(instr.Type().Underlying().(*types.Map).Key(), reserve) 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: fr.env[instr] = &(*fr.get(instr.X).(*value)).(structure)[instr.Field] case *ssa.Field: fr.env[instr] = 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] = 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: for i, pred := range instr.Block().Preds { 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 == types.RecvOnly { 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. } r := tuple{chosen, recvOk} for i, st := range instr.States { if st.Dir == types.RecvOnly { 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) } } fr.env[instr] = r 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 // function call in a Call, Go or Defer instruction, performing // interface method lookup if needed. // func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) { v := fr.get(call.Value) if call.Method == nil { // Function call. fn = v } else { // Interface method invocation. recv := v.(iface) if recv.t == nil { panic("method invoked on nil interface") } if f := lookupMethod(fr.i, recv.t, call.Method); f == nil { // Unreachable in well-typed programs. panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method)) } else { fn = f } args = append(args, recv.v) } 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 { fset := fn.Prog.Fset // TODO(adonovan): fix: loc() lies for external functions. fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn, loc(fset, fn.Pos())) suffix := "" if caller != nil { suffix = ", resuming " + caller.fn.String() + loc(fset, callpos) } defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn, suffix) } fr := &frame{ i: i, caller: caller, // for panic/recover fn: fn, } if fn.Parent() == nil { name := fn.String() if ext := externals[name]; ext != nil { if i.mode&EnableTracing != 0 { fmt.Fprintln(os.Stderr, "\t(external)") } return ext(fr, args) } if fn.Blocks == nil { panic("no code for function: " + name) } } fr.env = make(map[ssa.Value]value) fr.block = fn.Blocks[0] fr.locals = make([]value, len(fn.Locals)) for i, l := range fn.Locals { fr.locals[i] = zero(deref(l.Type())) 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] } for fr.block != nil { runFrame(fr) } // Destroy the locals to avoid accidental use after return. for i := range fn.Locals { fr.locals[i] = bad{} } return fr.result } // runFrame executes SSA instructions starting at fr.block and // continuing until a return, a panic, or a recovered panic. // // After a panic, runFrame panics. // // After a normal return, fr.result contains the result of the call // and fr.block is nil. // // A recovered panic in a function without named return parameters // (NRPs) becomes a normal return of the zero value of the function's // result type. // // After a recovered panic in a function with NRPs, fr.result is // undefined and fr.block contains the block at which to resume // control. // func runFrame(fr *frame) { defer func() { if fr.block == nil { return // normal return } if fr.i.mode&DisableRecover != 0 { return // let interpreter crash } fr.panicking = true fr.panic = recover() if fr.i.mode&EnableTracing != 0 { fmt.Fprintf(os.Stderr, "Panicking: %T %v.\n", fr.panic, fr.panic) } fr.runDefers() fr.block = fr.fn.Recover }() for { if fr.i.mode&EnableTracing != 0 { fmt.Fprintf(os.Stderr, ".%s:\n", fr.block) } block: for _, instr := range fr.block.Instrs { if fr.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: return case kNext: // no-op case kJump: break block } } } } // doRecover implements the recover() built-in. func doRecover(caller *frame) value { // recover() must be exactly one level beneath the deferred // function (two levels beneath the panicking function) to // have any effect. Thus we ignore both "defer recover()" and // "defer f() -> g() -> recover()". if caller.i.mode&DisableRecover == 0 && caller != nil && !caller.panicking && caller.caller != nil && caller.caller.panicking { caller.caller.panicking = false p := caller.caller.panic caller.caller.panic = nil // TODO(adonovan): support runtime.Goexit. switch p := p.(type) { case targetPanic: // The target program explicitly called panic(). return p.v case runtime.Error: // The interpreter encountered a runtime error. return iface{caller.i.runtimeErrorString, p.Error()} case string: // The interpreter explicitly called panic(). return iface{caller.i.runtimeErrorString, p} default: panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p)) } } return iface{} } // 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 } panic("no global variable: " + pkg.Pkg.Path() + "." + name) } // 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. sizes is the // effective type-sizing function for this program. // // Interpret returns the exit code of the program: 2 for panic (like // gc does), or the argument to os.Exit for normal termination. // // The SSA program must include the "runtime" package. // func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { i := &interpreter{ prog: mainpkg.Prog, globals: make(map[ssa.Value]*value), mode: mode, sizes: sizes, goroutines: 1, } runtimePkg := i.prog.ImportedPackage("runtime") if runtimePkg == nil { panic("ssa.Program doesn't include runtime package") } i.runtimeErrorString = runtimePkg.Type("errorString").Object().Type() initReflect(i) i.osArgs = append(i.osArgs, filename) for _, arg := range args { i.osArgs = append(i.osArgs, arg) } for _, pkg := range i.prog.AllPackages() { // Initialize global storage. for _, m := range pkg.Members { switch v := m.(type) { case *ssa.Global: cell := zero(deref(v.Type())) i.globals[v] = &cell } } } // 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: %v\n", p, 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! call(i, nil, token.NoPos, mainpkg.Func("init"), nil) 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 } // 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 }