diff --git a/go/ssa/interp/external.go b/go/ssa/interp/external.go index 091780d0..28c81e64 100644 --- a/go/ssa/interp/external.go +++ b/go/ssa/interp/external.go @@ -13,6 +13,9 @@ import ( "runtime" "syscall" "time" + "unsafe" + + "code.google.com/p/go.tools/go/ssa" ) type externalFn func(fr *frame, args []value) value @@ -27,9 +30,6 @@ var externals map[string]externalFn func init() { // That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd]. externals = map[string]externalFn{ - "(*runtime.Func).Entry": ext۰runtime۰Func۰Entry, - "(*runtime.Func).FileLine": ext۰runtime۰Func۰FileLine, - "(*runtime.Func).Name": ext۰runtime۰Func۰Name, "(*sync.Pool).Get": ext۰sync۰Pool۰Get, "(*sync.Pool).Put": ext۰sync۰Pool۰Put, "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, @@ -82,6 +82,7 @@ func init() { "reflect.valueInterface": ext۰reflect۰valueInterface, "runtime.Breakpoint": ext۰runtime۰Breakpoint, "runtime.Caller": ext۰runtime۰Caller, + "runtime.Callers": ext۰runtime۰Callers, "runtime.FuncForPC": ext۰runtime۰FuncForPC, "runtime.GC": ext۰runtime۰GC, "runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS, @@ -89,10 +90,12 @@ func init() { "runtime.NumCPU": ext۰runtime۰NumCPU, "runtime.ReadMemStats": ext۰runtime۰ReadMemStats, "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, + "runtime.funcentry_go": ext۰runtime۰funcentry_go, + "runtime.funcline_go": ext۰runtime۰funcline_go, + "runtime.funcname_go": ext۰runtime۰funcname_go, "runtime.getgoroot": ext۰runtime۰getgoroot, "strings.IndexByte": ext۰strings۰IndexByte, "sync.runtime_Syncsemcheck": ext۰sync۰runtime_Syncsemcheck, - "sync.runtime_registerPool": ext۰sync۰runtime_registerPool, "sync/atomic.AddInt32": ext۰atomic۰AddInt32, "sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32, "sync/atomic.LoadInt32": ext۰atomic۰LoadInt32, @@ -126,18 +129,6 @@ func wrapError(err error) value { return iface{t: errorType, v: err.Error()} } -func ext۰runtime۰Func۰Entry(fr *frame, args []value) value { - return 0 -} - -func ext۰runtime۰Func۰FileLine(fr *frame, args []value) value { - return tuple{"unknown.go", -1} -} - -func ext۰runtime۰Func۰Name(fr *frame, args []value) value { - return "unknown" -} - func ext۰sync۰Pool۰Get(fr *frame, args []value) value { if New := (*args[0].(*value)).(structure)[4]; New != nil { return call(fr.i, fr, 0, New, nil) @@ -214,16 +205,57 @@ func ext۰runtime۰Breakpoint(fr *frame, args []value) value { } func ext۰runtime۰Caller(fr *frame, args []value) value { - // TODO(adonovan): actually inspect the stack. - return tuple{0, "somefile.go", 42, true} + // func Caller(skip int) (pc uintptr, file string, line int, ok bool) + skip := 1 + args[0].(int) + for i := 0; i < skip; i++ { + if fr != nil { + fr = fr.caller + } + } + var pc uintptr + var file string + var line int + var ok bool + if fr != nil { + fn := fr.fn + // TODO(adonovan): use pc/posn of current instruction, not start of fn. + pc = uintptr(unsafe.Pointer(fn)) + posn := fn.Prog.Fset.Position(fn.Pos()) + file = posn.Filename + line = posn.Line + ok = true + } + return tuple{pc, file, line, ok} +} + +func ext۰runtime۰Callers(fr *frame, args []value) value { + // Callers(skip int, pc []uintptr) int + skip := args[0].(int) + pc := args[1].([]value) + for i := 0; i < skip; i++ { + if fr != nil { + fr = fr.caller + } + } + i := 0 + for fr != nil { + pc[i] = uintptr(unsafe.Pointer(fr.fn)) + i++ + fr = fr.caller + } + return i } func ext۰runtime۰FuncForPC(fr *frame, args []value) value { - // TODO(adonovan): actually inspect the stack. - return (*value)(nil) - //tuple{0, "somefile.go", 42, true} - // - //func FuncForPC(pc uintptr) *Func + // FuncForPC(pc uintptr) *Func + pc := args[0].(uintptr) + var fn *ssa.Function + if pc != 0 { + fn = (*ssa.Function)(unsafe.Pointer(pc)) // indeed unsafe! + } + var Func value + Func = structure{fn} // a runtime.Func + return &Func } func ext۰runtime۰getgoroot(fr *frame, args []value) value { @@ -242,10 +274,6 @@ func ext۰strings۰IndexByte(fr *frame, args []value) value { return -1 } -func ext۰sync۰runtime_registerPool(fr *frame, args []value) value { - return nil -} - func ext۰sync۰runtime_Syncsemcheck(fr *frame, args []value) value { return nil } @@ -317,12 +345,32 @@ func ext۰runtime۰SetFinalizer(fr *frame, args []value) value { return nil // ignore } +func ext۰runtime۰funcline_go(fr *frame, args []value) value { + // func funcline_go(*Func, uintptr) (string, int) + f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) + pc := args[1].(uintptr) + _ = pc + if f != nil { + // TODO(adonovan): use position of current instruction, not fn. + posn := f.Prog.Fset.Position(f.Pos()) + return tuple{posn.Filename, posn.Line} + } + return tuple{"", 0} +} + func ext۰runtime۰funcname_go(fr *frame, args []value) value { - // TODO(adonovan): actually inspect the stack. - return (*value)(nil) - //tuple{0, "somefile.go", 42, true} - // - //func FuncForPC(pc uintptr) *Func + // func funcname_go(*Func) string + f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) + if f != nil { + return f.String() + } + return "" +} + +func ext۰runtime۰funcentry_go(fr *frame, args []value) value { + // func funcentry_go(*Func) uintptr + f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) + return uintptr(unsafe.Pointer(f)) } func ext۰time۰now(fr *frame, args []value) value { diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go index 1c521369..5ebb3a7e 100644 --- a/go/ssa/interp/interp.go +++ b/go/ssa/interp/interp.go @@ -82,6 +82,7 @@ type interpreter struct { 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 } type deferred struct { @@ -622,10 +623,6 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) { panic("no global variable: " + pkg.Object.Path() + "." + name) } -// _sizes is the effective type-sizing function. -// TODO(adonovan): avoid global state. -var _sizes types.Sizes - // 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 @@ -637,11 +634,11 @@ var _sizes types.Sizes // The SSA program must include the "runtime" package. // func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { - _sizes = sizes i := &interpreter{ prog: mainpkg.Prog, globals: make(map[ssa.Value]*value), mode: mode, + sizes: sizes, } runtimePkg := i.prog.ImportedPackage("runtime") if runtimePkg == nil { diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go index 359208e6..19ef001b 100644 --- a/go/ssa/interp/interp_test.go +++ b/go/ssa/interp/interp_test.go @@ -143,6 +143,7 @@ var testdataTests = []string{ "methprom.go", "mrvchain.go", "recover.go", + "callstack.go", } // These are files in $GOROOT/src/pkg/. diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go index cc9b2ce6..edb7a86b 100644 --- a/go/ssa/interp/reflect.go +++ b/go/ssa/interp/reflect.go @@ -81,7 +81,7 @@ func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value { if !ok { panic(fmt.Sprintf("reflect.Type.Bits(%T): non-basic type", rt)) } - return _sizes.Sizeof(basic) * 8 + return fr.i.sizes.Sizeof(basic) * 8 } func ext۰reflect۰rtype۰Elem(fr *frame, args []value) value { @@ -135,7 +135,7 @@ func ext۰reflect۰rtype۰Out(fr *frame, args []value) value { func ext۰reflect۰rtype۰Size(fr *frame, args []value) value { // Signature: func (t reflect.rtype) uintptr - return uint64(_sizes.Sizeof(args[0].(rtype).t)) + return uint64(fr.i.sizes.Sizeof(args[0].(rtype).t)) } func ext۰reflect۰rtype۰String(fr *frame, args []value) value { diff --git a/go/ssa/interp/testdata/callstack.go b/go/ssa/interp/testdata/callstack.go new file mode 100644 index 00000000..ea469843 --- /dev/null +++ b/go/ssa/interp/testdata/callstack.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "path" + "runtime" + "strings" +) + +var stack string + +func f() { + pc := make([]uintptr, 6) + pc = pc[:runtime.Callers(1, pc)] + for _, f := range pc { + Func := runtime.FuncForPC(f) + name := Func.Name() + if strings.Contains(name, "func") { + name = "func" // anon funcs vary across toolchains + } + file, line := Func.FileLine(0) + stack += fmt.Sprintf("%s at %s:%d\n", name, path.Base(file), line) + } +} + +func g() { f() } +func h() { g() } +func i() { func() { h() }() } + +// Hack: the 'func' and the call to Caller are on the same line, +// to paper over differences between toolchains. +// (The interpreter's location info isn't yet complete.) +func runtimeCaller0() (uintptr, string, int, bool) { return runtime.Caller(0) } + +func main() { + i() + if stack != `main.f at callstack.go:12 +main.g at callstack.go:26 +main.h at callstack.go:27 +func at callstack.go:28 +main.i at callstack.go:28 +main.main at callstack.go:35 +` { + panic("unexpected stack: " + stack) + } + + pc, file, line, _ := runtimeCaller0() + got := fmt.Sprintf("%s @ %s:%d", runtime.FuncForPC(pc).Name(), path.Base(file), line) + if got != "main.runtimeCaller0 @ callstack.go:33" { + panic("runtime.Caller: " + got) + } +}