diff --git a/go/ssa/interp/external.go b/go/ssa/interp/external.go index 2c4dcc76..735a14d9 100644 --- a/go/ssa/interp/external.go +++ b/go/ssa/interp/external.go @@ -8,16 +8,13 @@ package interp // external or because they use "unsafe" or "reflect" operations. import ( - "go/types" + "bytes" "math" "os" "runtime" "strings" - "sync/atomic" "time" - "unsafe" - - "golang.org/x/tools/go/ssa" + "unicode/utf8" ) type externalFn func(fr *frame, args []value) value @@ -32,144 +29,81 @@ var externals = make(map[string]externalFn) func init() { // That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd]. for k, v := range map[string]externalFn{ - "(*sync.Pool).Get": ext۰sync۰Pool۰Get, - "(*sync.Pool).Put": ext۰nop, - "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, - "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, - "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, - "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, - "(reflect.Value).Field": ext۰reflect۰Value۰Field, - "(reflect.Value).Float": ext۰reflect۰Value۰Float, - "(reflect.Value).Index": ext۰reflect۰Value۰Index, - "(reflect.Value).Int": ext۰reflect۰Value۰Int, - "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, - "(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil, - "(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid, - "(reflect.Value).Kind": ext۰reflect۰Value۰Kind, - "(reflect.Value).Len": ext۰reflect۰Value۰Len, - "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, - "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, - "(reflect.Value).NumField": ext۰reflect۰Value۰NumField, - "(reflect.Value).NumMethod": ext۰reflect۰Value۰NumMethod, - "(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer, - "(reflect.Value).Set": ext۰reflect۰Value۰Set, - "(reflect.Value).String": ext۰reflect۰Value۰String, - "(reflect.Value).Type": ext۰reflect۰Value۰Type, - "(reflect.Value).Uint": ext۰reflect۰Value۰Uint, - "(reflect.error).Error": ext۰reflect۰error۰Error, - "(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits, - "(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, - "(reflect.rtype).Field": ext۰reflect۰rtype۰Field, - "(reflect.rtype).In": ext۰reflect۰rtype۰In, - "(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind, - "(reflect.rtype).NumField": ext۰reflect۰rtype۰NumField, - "(reflect.rtype).NumIn": ext۰reflect۰rtype۰NumIn, - "(reflect.rtype).NumMethod": ext۰reflect۰rtype۰NumMethod, - "(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut, - "(reflect.rtype).Out": ext۰reflect۰rtype۰Out, - "(reflect.rtype).Size": ext۰reflect۰rtype۰Size, - "(reflect.rtype).String": ext۰reflect۰rtype۰String, - "bytes.init": ext۰nop, // avoid asm dependency - "bytes.Equal": ext۰bytes۰Equal, - "bytes.IndexByte": ext۰bytes۰IndexByte, - "hash/crc32.haveSSE42": ext۰crc32۰haveSSE42, - "internal/cpu.cpuid": ext۰cpu۰cpuid, - "internal/syscall/unix.syscall_fcntl": ext۰syscall۰unix۰syscall_fcntl, - "math.Abs": ext۰math۰Abs, - "math.Exp": ext۰math۰Exp, - "math.Float32bits": ext۰math۰Float32bits, - "math.Float32frombits": ext۰math۰Float32frombits, - "math.Float64bits": ext۰math۰Float64bits, - "math.Float64frombits": ext۰math۰Float64frombits, - "math.Ldexp": ext۰math۰Ldexp, - "math.Log": ext۰math۰Log, - "math.Min": ext۰math۰Min, - "math.hasSSE4": ext۰math۰hasSSE4, - "math.hasVectorFacility": ext۰math۰hasVectorFacility, - "os.runtime_args": ext۰os۰runtime_args, - "os.runtime_beforeExit": ext۰nop, - "os/signal.init": ext۰nop, - "reflect.New": ext۰reflect۰New, - "reflect.SliceOf": ext۰reflect۰SliceOf, - "reflect.TypeOf": ext۰reflect۰TypeOf, - "reflect.ValueOf": ext۰reflect۰ValueOf, - "reflect.Zero": ext۰reflect۰Zero, - "reflect.init": ext۰reflect۰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, - "runtime.Goexit": ext۰runtime۰Goexit, - "runtime.Gosched": ext۰runtime۰Gosched, - "runtime.init": ext۰nop, - "runtime.KeepAlive": ext۰nop, - "runtime.NumCPU": ext۰runtime۰NumCPU, - "runtime.NumGoroutine": ext۰runtime۰NumGoroutine, - "runtime.ReadMemStats": ext۰runtime۰ReadMemStats, - "runtime.SetFinalizer": ext۰nop, // ignore - "(*runtime.Func).Entry": ext۰runtime۰Func۰Entry, - "(*runtime.Func).FileLine": ext۰runtime۰Func۰FileLine, - "(*runtime.Func).Name": ext۰runtime۰Func۰Name, - "runtime.environ": ext۰runtime۰environ, - "runtime.getgoroot": ext۰runtime۰getgoroot, - "strings.init": ext۰nop, // avoid asm dependency - "strings.Count": ext۰strings۰Count, - "strings.Index": ext۰strings۰Index, - "strings.IndexByte": ext۰strings۰IndexByte, - "sync.runtime_Semacquire": ext۰nop, // unimplementable - "sync.runtime_Semrelease": ext۰nop, // unimplementable - "sync.runtime_Syncsemcheck": ext۰nop, // unimplementable - "sync.runtime_notifyListCheck": ext۰nop, - "sync.runtime_registerPoolCleanup": ext۰nop, - "sync/atomic.AddInt32": ext۰atomic۰AddInt32, - "sync/atomic.AddUint32": ext۰atomic۰AddUint32, - "sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32, - "sync/atomic.CompareAndSwapUint32": ext۰atomic۰CompareAndSwapUint32, - "sync/atomic.LoadInt32": ext۰atomic۰LoadInt32, - "sync/atomic.LoadUint32": ext۰atomic۰LoadUint32, - "sync/atomic.StoreInt32": ext۰atomic۰StoreInt32, - "sync/atomic.StoreUint32": ext۰atomic۰StoreUint32, - "sync/atomic.AddInt64": ext۰atomic۰AddInt64, - "sync/atomic.AddUint64": ext۰atomic۰AddUint64, - "sync/atomic.CompareAndSwapInt64": ext۰atomic۰CompareAndSwapInt64, - "sync/atomic.CompareAndSwapUint64": ext۰atomic۰CompareAndSwapUint64, - "sync/atomic.LoadInt64": ext۰atomic۰LoadInt64, - "sync/atomic.LoadUint64": ext۰atomic۰LoadUint64, - "sync/atomic.StoreInt64": ext۰atomic۰StoreInt64, - "sync/atomic.StoreUint64": ext۰atomic۰StoreUint64, - "(*sync/atomic.Value).Load": ext۰atomic۰ValueLoad, - "(*sync/atomic.Value).Store": ext۰atomic۰ValueStore, - "testing.MainStart": ext۰testing۰MainStart, - "time.Sleep": ext۰time۰Sleep, - "time.now": ext۰time۰now, + "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, + "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, + "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, + "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, + "(reflect.Value).Field": ext۰reflect۰Value۰Field, + "(reflect.Value).Float": ext۰reflect۰Value۰Float, + "(reflect.Value).Index": ext۰reflect۰Value۰Index, + "(reflect.Value).Int": ext۰reflect۰Value۰Int, + "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, + "(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil, + "(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid, + "(reflect.Value).Kind": ext۰reflect۰Value۰Kind, + "(reflect.Value).Len": ext۰reflect۰Value۰Len, + "(reflect.Value).MapIndex": ext۰reflect۰Value۰MapIndex, + "(reflect.Value).MapKeys": ext۰reflect۰Value۰MapKeys, + "(reflect.Value).NumField": ext۰reflect۰Value۰NumField, + "(reflect.Value).NumMethod": ext۰reflect۰Value۰NumMethod, + "(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer, + "(reflect.Value).Set": ext۰reflect۰Value۰Set, + "(reflect.Value).String": ext۰reflect۰Value۰String, + "(reflect.Value).Type": ext۰reflect۰Value۰Type, + "(reflect.Value).Uint": ext۰reflect۰Value۰Uint, + "(reflect.error).Error": ext۰reflect۰error۰Error, + "(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits, + "(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, + "(reflect.rtype).Field": ext۰reflect۰rtype۰Field, + "(reflect.rtype).In": ext۰reflect۰rtype۰In, + "(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind, + "(reflect.rtype).NumField": ext۰reflect۰rtype۰NumField, + "(reflect.rtype).NumIn": ext۰reflect۰rtype۰NumIn, + "(reflect.rtype).NumMethod": ext۰reflect۰rtype۰NumMethod, + "(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut, + "(reflect.rtype).Out": ext۰reflect۰rtype۰Out, + "(reflect.rtype).Size": ext۰reflect۰rtype۰Size, + "(reflect.rtype).String": ext۰reflect۰rtype۰String, + "bytes.Equal": ext۰bytes۰Equal, + "bytes.IndexByte": ext۰bytes۰IndexByte, + "fmt.Sprint": ext۰fmt۰Sprint, + "math.Abs": ext۰math۰Abs, + "math.Exp": ext۰math۰Exp, + "math.Float32bits": ext۰math۰Float32bits, + "math.Float32frombits": ext۰math۰Float32frombits, + "math.Float64bits": ext۰math۰Float64bits, + "math.Float64frombits": ext۰math۰Float64frombits, + "math.Inf": ext۰math۰Inf, + "math.IsNaN": ext۰math۰IsNaN, + "math.Ldexp": ext۰math۰Ldexp, + "math.Log": ext۰math۰Log, + "math.Min": ext۰math۰Min, + "math.NaN": ext۰math۰NaN, + "os.Exit": ext۰os۰Exit, + "os.Getenv": ext۰os۰Getenv, + "reflect.New": ext۰reflect۰New, + "reflect.SliceOf": ext۰reflect۰SliceOf, + "reflect.TypeOf": ext۰reflect۰TypeOf, + "reflect.ValueOf": ext۰reflect۰ValueOf, + "reflect.Zero": ext۰reflect۰Zero, + "runtime.Breakpoint": ext۰runtime۰Breakpoint, + "runtime.GC": ext۰runtime۰GC, + "runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS, + "runtime.GOROOT": ext۰runtime۰GOROOT, + "runtime.Goexit": ext۰runtime۰Goexit, + "runtime.Gosched": ext۰runtime۰Gosched, + "runtime.NumCPU": ext۰runtime۰NumCPU, + "strings.Count": ext۰strings۰Count, + "strings.Index": ext۰strings۰Index, + "strings.IndexByte": ext۰strings۰IndexByte, + "strings.Replace": ext۰strings۰Replace, + "time.Sleep": ext۰time۰Sleep, + "unicode/utf8.DecodeRuneInString": ext۰unicode۰utf8۰DecodeRuneInString, } { externals[k] = v } } -// wrapError returns an interpreted 'error' interface value for err. -func wrapError(err error) value { - if err == nil { - return iface{} - } - return iface{t: errorType, v: err.Error()} -} - -func ext۰nop(fr *frame, args []value) value { return nil } - -func ext۰sync۰Pool۰Get(fr *frame, args []value) value { - Pool := fr.i.prog.ImportedPackage("sync").Type("Pool").Object() - _, newIndex, _ := types.LookupFieldOrMethod(Pool.Type(), false, Pool.Pkg(), "New") - - if New := (*args[0].(*value)).(structure)[newIndex[0]]; New != nil { - return call(fr.i, fr, 0, New, nil) - } - return nil -} - func ext۰bytes۰Equal(fr *frame, args []value) value { // func Equal(a, b []byte) bool a := args[0].([]value) @@ -197,10 +131,6 @@ func ext۰bytes۰IndexByte(fr *frame, args []value) value { return -1 } -func ext۰crc32۰haveSSE42(fr *frame, args []value) value { - return false -} - func ext۰math۰Float64frombits(fr *frame, args []value) value { return math.Float64frombits(args[0].(uint64)) } @@ -229,12 +159,16 @@ func ext۰math۰Min(fr *frame, args []value) value { return math.Min(args[0].(float64), args[1].(float64)) } -func ext۰math۰hasSSE4(fr *frame, args []value) value { - return false +func ext۰math۰NaN(fr *frame, args []value) value { + return math.NaN() } -func ext۰math۰hasVectorFacility(fr *frame, args []value) value { - return false +func ext۰math۰IsNaN(fr *frame, args []value) value { + return math.IsNaN(args[0].(float64)) +} + +func ext۰math۰Inf(fr *frame, args []value) value { + return math.Inf(args[0].(int)) } func ext۰math۰Ldexp(fr *frame, args []value) value { @@ -245,94 +179,32 @@ func ext۰math۰Log(fr *frame, args []value) value { return math.Log(args[0].(float64)) } -func ext۰os۰runtime_args(fr *frame, args []value) value { - return fr.i.osArgs -} - func ext۰runtime۰Breakpoint(fr *frame, args []value) value { runtime.Breakpoint() return nil } -func ext۰runtime۰Caller(fr *frame, args []value) value { - // 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. - // (Required to interpret the log package's tests.) - 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 && i < len(pc) { - pc[i] = uintptr(unsafe.Pointer(fr.fn)) - i++ - fr = fr.caller - } - return i -} - -func ext۰runtime۰FuncForPC(fr *frame, args []value) value { - // 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۰environ(fr *frame, args []value) value { - // This function also implements syscall.runtime_envs. - return environ -} - -func ext۰runtime۰getgoroot(fr *frame, args []value) value { - return os.Getenv("GOROOT") -} - func ext۰strings۰Count(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.Count(args[0].(string), args[1].(string)) } func ext۰strings۰IndexByte(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.IndexByte(args[0].(string), args[1].(byte)) } func ext۰strings۰Index(fr *frame, args []value) value { - // Call compiled version to avoid asm dependency. return strings.Index(args[0].(string), args[1].(string)) } +func ext۰strings۰Replace(fr *frame, args []value) value { + // func Replace(s, old, new string, n int) string + s := args[0].(string) + new := args[1].(string) + old := args[2].(string) + n := args[3].(int) + return strings.Replace(s, old, new, n) +} + func ext۰runtime۰GOMAXPROCS(fr *frame, args []value) value { // Ignore args[0]; don't let the interpreted program // set the interpreter's GOMAXPROCS! @@ -345,6 +217,10 @@ func ext۰runtime۰Goexit(fr *frame, args []value) value { return nil } +func ext۰runtime۰GOROOT(fr *frame, args []value) value { + return runtime.GOROOT() +} + func ext۰runtime۰GC(fr *frame, args []value) value { runtime.GC() return nil @@ -359,187 +235,6 @@ func ext۰runtime۰NumCPU(fr *frame, args []value) value { return runtime.NumCPU() } -func ext۰runtime۰NumGoroutine(fr *frame, args []value) value { - return int(atomic.LoadInt32(&fr.i.goroutines)) -} - -func ext۰runtime۰ReadMemStats(fr *frame, args []value) value { - // TODO(adonovan): populate args[0].(Struct) - return nil -} - -func ext۰atomic۰LoadUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(uint32) -} - -func ext۰atomic۰StoreUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(uint32) - return nil -} - -func ext۰atomic۰LoadInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(int32) -} - -func ext۰atomic۰StoreInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(int32) - return nil -} - -func ext۰atomic۰CompareAndSwapInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(int32) == args[1].(int32) { - *p = args[2].(int32) - return true - } - return false -} - -func ext۰atomic۰CompareAndSwapUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(uint32) == args[1].(uint32) { - *p = args[2].(uint32) - return true - } - return false -} - -func ext۰atomic۰AddInt32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(int32) + args[1].(int32) - *p = newv - return newv -} - -func ext۰atomic۰AddUint32(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(uint32) + args[1].(uint32) - *p = newv - return newv -} - -func ext۰atomic۰LoadUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(uint64) -} - -func ext۰atomic۰StoreUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(uint64) - return nil -} - -func ext۰atomic۰LoadInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - return (*args[0].(*value)).(int64) -} - -func ext۰atomic۰StoreInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - *args[0].(*value) = args[1].(int64) - return nil -} - -func ext۰atomic۰CompareAndSwapInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(int64) == args[1].(int64) { - *p = args[2].(int64) - return true - } - return false -} - -func ext۰atomic۰CompareAndSwapUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - if (*p).(uint64) == args[1].(uint64) { - *p = args[2].(uint64) - return true - } - return false -} - -func ext۰atomic۰AddInt64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(int64) + args[1].(int64) - *p = newv - return newv -} - -func ext۰atomic۰AddUint64(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - p := args[0].(*value) - newv := (*p).(uint64) + args[1].(uint64) - *p = newv - return newv -} - -func ext۰atomic۰ValueLoad(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - // Receiver is *struct{v interface{}}. - return (*args[0].(*value)).(structure)[0] -} - -func ext۰atomic۰ValueStore(fr *frame, args []value) value { - // TODO(adonovan): fix: not atomic! - // Receiver is *struct{v interface{}}. - (*args[0].(*value)).(structure)[0] = args[1] - return nil -} - -func ext۰cpu۰cpuid(fr *frame, args []value) value { - return tuple{uint32(0), uint32(0), uint32(0), uint32(0)} -} - -func ext۰syscall۰unix۰syscall_fcntl(fr *frame, args []value) value { - return tuple{int(0), wrapError(nil)} -} - -// Pretend: type runtime.Func struct { entry *ssa.Function } - -func ext۰runtime۰Func۰FileLine(fr *frame, args []value) value { - // func (*runtime.Func) FileLine(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۰Func۰Name(fr *frame, args []value) value { - // func (*runtime.Func) Name() string - f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) - if f != nil { - return f.String() - } - return "" -} - -func ext۰runtime۰Func۰Entry(fr *frame, args []value) value { - // func (*runtime.Func) Entry() uintptr - f, _ := (*args[0].(*value)).(structure)[0].(*ssa.Function) - return uintptr(unsafe.Pointer(f)) -} - -func ext۰time۰now(fr *frame, args []value) value { - nano := time.Now().UnixNano() - return tuple{int64(nano / 1e9), int32(nano % 1e9), int64(0)} -} - func ext۰time۰Sleep(fr *frame, args []value) value { time.Sleep(time.Duration(args[0].(int64))) return nil @@ -554,9 +249,42 @@ func valueToBytes(v value) []byte { return b } -func ext۰testing۰MainStart(fr *frame, args []value) value { - // We no longer support interpretation of the "testing" package - // because it changes too often and uses low-level features that - // are a pain to emulate. - panic(`interpretation of the "testing" package is no longer supported`) +func ext۰os۰Getenv(fr *frame, args []value) value { + name := args[0].(string) + switch name { + case "GOSSAINTERP": + return "1" + case "GOARCH": + return "amd64" + case "GOOS": + return "linux" + } + return os.Getenv(name) +} + +func ext۰os۰Exit(fr *frame, args []value) value { + panic(exitPanic(args[0].(int))) +} + +func ext۰unicode۰utf8۰DecodeRuneInString(fr *frame, args []value) value { + r, n := utf8.DecodeRuneInString(args[0].(string)) + return tuple{r, n} +} + +// A fake function for turning an arbitrary value into a string. +// Handles only the cases needed by the tests. +// Uses same logic as 'print' built-in. +func ext۰fmt۰Sprint(fr *frame, args []value) value { + buf := new(bytes.Buffer) + wasStr := false + for i, arg := range args[0].([]value) { + x := arg.(iface).v + _, isStr := x.(string) + if i > 0 && !wasStr && !isStr { + buf.WriteByte(' ') + } + wasStr = isStr + buf.WriteString(toString(x)) + } + return buf.String() } diff --git a/go/ssa/interp/external_darwin.go b/go/ssa/interp/external_darwin.go deleted file mode 100644 index 47130093..00000000 --- a/go/ssa/interp/external_darwin.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 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. - -// +build darwin - -package interp - -import "syscall" - -func init() { - externals["syscall.Sysctl"] = ext۰syscall۰Sysctl - - fillStat = func(st *syscall.Stat_t, stat structure) { - stat[0] = st.Dev - stat[1] = st.Mode - stat[2] = st.Nlink - stat[3] = st.Ino - stat[4] = st.Uid - stat[5] = st.Gid - stat[6] = st.Rdev - // TODO(adonovan): fix: copy Timespecs. - // stat[8] = st.Atim - // stat[9] = st.Mtim - // stat[10] = st.Ctim - stat[12] = st.Size - stat[13] = st.Blocks - stat[14] = st.Blksize - } -} - -func ext۰syscall۰Sysctl(fr *frame, args []value) value { - r, err := syscall.Sysctl(args[0].(string)) - return tuple{r, wrapError(err)} -} diff --git a/go/ssa/interp/external_unix.go b/go/ssa/interp/external_unix.go deleted file mode 100644 index bfa39f68..00000000 --- a/go/ssa/interp/external_unix.go +++ /dev/null @@ -1,256 +0,0 @@ -// 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. - -// +build darwin linux - -package interp - -import "syscall" - -func init() { - for k, v := range map[string]externalFn{ - "os.Pipe": ext۰os۰Pipe, - "syscall.Close": ext۰syscall۰Close, - "syscall.Exit": ext۰syscall۰Exit, - "syscall.Fchown": ext۰syscall۰Fchown, - "syscall.Fstat": ext۰syscall۰Fstat, - "syscall.Ftruncate": ext۰syscall۰Ftruncate, - "syscall.Getpid": ext۰syscall۰Getpid, - "syscall.Getwd": ext۰syscall۰Getwd, - "syscall.Kill": ext۰syscall۰Kill, - "syscall.Link": ext۰syscall۰Link, - "syscall.Lstat": ext۰syscall۰Lstat, - "syscall.Mkdir": ext۰syscall۰Mkdir, - "syscall.Open": ext۰syscall۰Open, - "syscall.ParseDirent": ext۰syscall۰ParseDirent, - "syscall.RawSyscall": ext۰syscall۰RawSyscall, - "syscall.Read": ext۰syscall۰Read, - "syscall.ReadDirent": ext۰syscall۰ReadDirent, - "syscall.Readlink": ext۰syscall۰Readlink, - "syscall.Rmdir": ext۰syscall۰Rmdir, - "syscall.Seek": ext۰syscall۰Seek, - "syscall.Stat": ext۰syscall۰Stat, - "syscall.Symlink": ext۰syscall۰Symlink, - "syscall.Write": ext۰syscall۰Write, - "syscall.Unlink": ext۰syscall۰Unlink, - "syscall۰UtimesNano": ext۰syscall۰UtimesNano, - "syscall.setenv_c": ext۰nop, - "syscall.unsetenv_c": ext۰nop, - "syscall.runtime_envs": ext۰runtime۰environ, - } { - externals[k] = v - } - - syswrite = syscall.Write -} - -func ext۰os۰Pipe(fr *frame, args []value) value { - // func os.Pipe() (r *File, w *File, err error) - - // The portable POSIX pipe(2) call is good enough for our needs. - var p [2]int - if err := syscall.Pipe(p[:]); err != nil { - // TODO(adonovan): fix: return an *os.SyscallError. - return tuple{nil, nil, wrapError(err)} - } - - NewFile := fr.i.prog.ImportedPackage("os").Func("NewFile") - r := call(fr.i, fr, 0, NewFile, []value{uintptr(p[0]), "|0"}) - w := call(fr.i, fr, 0, NewFile, []value{uintptr(p[1]), "|1"}) - return tuple{r, w, wrapError(nil)} -} - -// overridden on darwin -var fillStat = func(st *syscall.Stat_t, stat structure) { - stat[0] = st.Dev - stat[1] = st.Ino - stat[2] = st.Nlink - stat[3] = st.Mode - stat[4] = st.Uid - stat[5] = st.Gid - stat[7] = st.Rdev - stat[8] = st.Size - stat[9] = st.Blksize - stat[10] = st.Blocks - // TODO(adonovan): fix: copy Timespecs. - // stat[11] = st.Atim - // stat[12] = st.Mtim - // stat[13] = st.Ctim -} - -func ext۰syscall۰Close(fr *frame, args []value) value { - // func Close(fd int) (err error) - return wrapError(syscall.Close(args[0].(int))) -} - -func ext۰syscall۰Exit(fr *frame, args []value) value { - panic(exitPanic(args[0].(int))) -} - -func ext۰syscall۰Fchown(fr *frame, args []value) value { - fd := args[0].(int) - uid := args[1].(int) - gid := args[2].(int) - return wrapError(syscall.Fchown(fd, uid, gid)) -} - -func ext۰syscall۰Fstat(fr *frame, args []value) value { - // func Fstat(fd int, stat *Stat_t) (err error) - fd := args[0].(int) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Fstat(fd, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Ftruncate(fr *frame, args []value) value { - fd := args[0].(int) - length := args[1].(int64) - return wrapError(syscall.Ftruncate(fd, length)) -} - -func ext۰syscall۰Getpid(fr *frame, args []value) value { - return syscall.Getpid() -} - -func ext۰syscall۰Getwd(fr *frame, args []value) value { - s, err := syscall.Getwd() - return tuple{s, wrapError(err)} -} - -func ext۰syscall۰Kill(fr *frame, args []value) value { - // func Kill(pid int, sig Signal) (err error) - return wrapError(syscall.Kill(args[0].(int), syscall.Signal(args[1].(int)))) -} - -func ext۰syscall۰Link(fr *frame, args []value) value { - path := args[0].(string) - link := args[1].(string) - return wrapError(syscall.Link(path, link)) -} - -func ext۰syscall۰Lstat(fr *frame, args []value) value { - // func Lstat(name string, stat *Stat_t) (err error) - name := args[0].(string) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Lstat(name, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Mkdir(fr *frame, args []value) value { - path := args[0].(string) - mode := args[1].(uint32) - return wrapError(syscall.Mkdir(path, mode)) -} - -func ext۰syscall۰Open(fr *frame, args []value) value { - // func Open(path string, mode int, perm uint32) (fd int, err error) { - path := args[0].(string) - mode := args[1].(int) - perm := args[2].(uint32) - fd, err := syscall.Open(path, mode, perm) - return tuple{fd, wrapError(err)} -} - -func ext۰syscall۰ParseDirent(fr *frame, args []value) value { - // func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) - max := args[1].(int) - var names []string - for _, iname := range args[2].([]value) { - names = append(names, iname.(string)) - } - consumed, count, newnames := syscall.ParseDirent(valueToBytes(args[0]), max, names) - var inewnames []value - for _, newname := range newnames { - inewnames = append(inewnames, newname) - } - return tuple{consumed, count, inewnames} -} - -func ext۰syscall۰RawSyscall(fr *frame, args []value) value { - return tuple{uintptr(0), uintptr(0), uintptr(syscall.ENOSYS)} -} - -func ext۰syscall۰Read(fr *frame, args []value) value { - // func Read(fd int, p []byte) (n int, err error) - fd := args[0].(int) - p := args[1].([]value) - b := make([]byte, len(p)) - n, err := syscall.Read(fd, b) - for i := 0; i < n; i++ { - p[i] = b[i] - } - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰ReadDirent(fr *frame, args []value) value { - // func ReadDirent(fd int, buf []byte) (n int, err error) - fd := args[0].(int) - p := args[1].([]value) - b := make([]byte, len(p)) - n, err := syscall.ReadDirent(fd, b) - for i := 0; i < n; i++ { - p[i] = b[i] - } - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰Readlink(fr *frame, args []value) value { - path := args[0].(string) - buf := valueToBytes(args[1]) - n, err := syscall.Readlink(path, buf) - return tuple{n, wrapError(err)} -} - -func ext۰syscall۰Rmdir(fr *frame, args []value) value { - return wrapError(syscall.Rmdir(args[0].(string))) -} - -func ext۰syscall۰Seek(fr *frame, args []value) value { - fd := args[0].(int) - offset := args[1].(int64) - whence := args[2].(int) - new, err := syscall.Seek(fd, offset, whence) - return tuple{new, wrapError(err)} -} - -func ext۰syscall۰Stat(fr *frame, args []value) value { - // func Stat(name string, stat *Stat_t) (err error) - name := args[0].(string) - stat := (*args[1].(*value)).(structure) - - var st syscall.Stat_t - err := syscall.Stat(name, &st) - fillStat(&st, stat) - return wrapError(err) -} - -func ext۰syscall۰Symlink(fr *frame, args []value) value { - path := args[0].(string) - link := args[1].(string) - return wrapError(syscall.Symlink(path, link)) -} - -func ext۰syscall۰Unlink(fr *frame, args []value) value { - return wrapError(syscall.Unlink(args[0].(string))) -} - -func ext۰syscall۰UtimesNano(fr *frame, args []value) value { - path := args[0].(string) - var ts [2]syscall.Timespec - err := syscall.UtimesNano(path, ts[:]) - // TODO(adonovan): copy the Timespecs into args[1] - return wrapError(err) -} - -func ext۰syscall۰Write(fr *frame, args []value) value { - // func Write(fd int, p []byte) (n int, err error) - n, err := write(args[0].(int), valueToBytes(args[1])) - return tuple{n, wrapError(err)} -} diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go index c0929609..76b7ca3a 100644 --- a/go/ssa/interp/interp.go +++ b/go/ssa/interp/interp.go @@ -630,30 +630,6 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) { panic("no global variable: " + pkg.Pkg.Path() + "." + name) } -var environ []value - -func init() { - for _, s := range os.Environ() { - environ = append(environ, s) - } - environ = append(environ, "GOSSAINTERP=1") - environ = append(environ, "GOARCH="+runtime.GOARCH) -} - -// deleteBodies delete the bodies of all standalone functions except the -// specified ones. A missing intrinsic leads to a clear runtime error. -func deleteBodies(pkg *ssa.Package, except ...string) { - keep := make(map[string]bool) - for _, e := range except { - keep[e] = true - } - for _, mem := range pkg.Members { - if fn, ok := mem.(*ssa.Function); ok && !keep[fn.Name()] { - fn.Blocks = nil - } - } -} - // 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 @@ -665,11 +641,6 @@ func deleteBodies(pkg *ssa.Package, except ...string) { // The SSA program must include the "runtime" package. // func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) { - if syswrite == nil { - fmt.Fprintln(os.Stderr, "Interpret: unsupported platform.") - return 1 - } - i := &interpreter{ prog: mainpkg.Prog, globals: make(map[ssa.Value]*value), @@ -699,20 +670,6 @@ func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename stri i.globals[v] = &cell } } - - // Ad-hoc initialization for magic system variables. - switch pkg.Pkg.Path() { - case "syscall": - setGlobal(i, pkg, "envs", environ) - - case "reflect": - deleteBodies(pkg, "DeepEqual", "deepValueEqual") - - case "runtime": - sz := sizes.Sizeof(pkg.Pkg.Scope().Lookup("MemStats").Type()) - setGlobal(i, pkg, "sizeof_C_MStats", uintptr(sz)) - deleteBodies(pkg, "GOROOT", "gogetenv") - } } // Top-level error handler. diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go index 07bb53c2..c1e30ec1 100644 --- a/go/ssa/interp/interp_test.go +++ b/go/ssa/interp/interp_test.go @@ -2,18 +2,27 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux darwin - package interp_test +// This test runs the SSA interpreter over sample Go programs. +// Because the interpreter requires intrinsics for assembly +// functions and many low-level runtime routines, it is inherently +// not robust to evolutionary change in the standard library. +// Therefore the test cases are restricted to programs that +// use a fake standard library in testdata/src containing a tiny +// subset of simple functions useful for writing assertions. +// +// We no longer attempt to interpret any real standard packages such as +// fmt or testing, as it proved too fragile. + import ( "bytes" "fmt" "go/build" "go/types" + "log" "os" "path/filepath" - "runtime" "strings" "testing" "time" @@ -28,12 +37,10 @@ import ( // filenames comprising the main package of a program. // They are ordered quickest-first, roughly. // -// TODO(adonovan): integrate into the $GOROOT/test driver scripts, -// golden file checking, etc. +// If a test in this list fails spuriously, remove it. var gorootTestTests = []string{ "235.go", "alias1.go", - "chancap.go", "func5.go", "func6.go", "func7.go", @@ -63,12 +70,10 @@ var gorootTestTests = []string{ "bigmap.go", "func.go", "reorder2.go", - "closure.go", "gc.go", "simassign.go", "iota.go", "nilptr2.go", - "goprint.go", // doesn't actually assert anything (cmpout) "utf.go", "method.go", "char_lit.go", @@ -86,52 +91,18 @@ var gorootTestTests = []string{ "convert.go", "convT2X.go", "switch.go", - "initialize.go", "ddd.go", "blank.go", // partly disabled - "map.go", "closedchan.go", "divide.go", "rename.go", - "const3.go", "nil.go", - "recover.go", // reflection parts disabled "recover1.go", "recover2.go", "recover3.go", "typeswitch1.go", "floatcmp.go", "crlf.go", // doesn't actually assert anything (runoutput) - // Slow tests follow. - "bom.go", // ~1.7s - "gc1.go", // ~1.7s - "cmplxdivide.go cmplxdivide1.go", // ~2.4s - - // Working, but not worth enabling: - // "append.go", // works, but slow (15s). - // "gc2.go", // works, but slow, and cheats on the memory check. - // "sigchld.go", // works, but only on POSIX. - // "peano.go", // works only up to n=9, and slow even then. - // "stack.go", // works, but too slow (~30s) by default. - // "solitaire.go", // works, but too slow (~30s). - // "const.go", // works but for but one bug: constant folder doesn't consider representations. - // "init1.go", // too slow (80s) and not that interesting. Cheats on ReadMemStats check too. - // "rotate.go rotate0.go", // emits source for a test - // "rotate.go rotate1.go", // emits source for a test - // "rotate.go rotate2.go", // emits source for a test - // "rotate.go rotate3.go", // emits source for a test - // "64bit.go", // emits source for a test - // "run.go", // test driver, not a test. - - // Broken. TODO(adonovan): fix. - // copy.go // very slow; but with N=4 quickly crashes, slice index out of range. - // nilptr.go // interp: V > uintptr not implemented. Slow test, lots of mem - // args.go // works, but requires specific os.Args from the driver. - // index.go // a template, not a real test. - // mallocfin.go // SetFinalizer not implemented. - - // TODO(adonovan): add tests from $GOROOT/test/* subtrees: - // bench chan bugs fixedbugs interface ken. } // These are files in go.tools/go/ssa/interp/testdata/. @@ -150,31 +121,21 @@ var testdataTests = []string{ "recover.go", "reflect.go", "static.go", - "callstack.go", } -type successPredicate func(exitcode int, output string) error - -func run(t *testing.T, dir, input string, success successPredicate) bool { - t.Skip("https://golang.org/issue/27292") - if runtime.GOOS == "darwin" { - t.Skip("skipping on darwin until https://golang.org/issue/23166 is fixed") - } - fmt.Printf("Input: %s\n", input) +func run(t *testing.T, input string) bool { + t.Logf("Input: %s\n", input) start := time.Now() - var inputs []string - for _, i := range strings.Split(input, " ") { - if strings.HasSuffix(i, ".go") { - i = dir + i - } - inputs = append(inputs, i) - } + ctx := build.Default // copy + ctx.GOROOT = "testdata" // fake goroot + ctx.GOOS = "linux" + ctx.GOARCH = "amd64" - var conf loader.Config - if _, err := conf.FromArgs(inputs, true); err != nil { - t.Errorf("FromArgs(%s) failed: %s", inputs, err) + conf := loader.Config{Build: &ctx} + if _, err := conf.FromArgs([]string{input}, true); err != nil { + t.Errorf("FromArgs(%s) failed: %s", input, err) return false } @@ -193,61 +154,43 @@ func run(t *testing.T, dir, input string, success successPredicate) bool { interp.CapturedOutput = nil }() - hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", strings.Join(inputs, " ")) + hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input) iprog, err := conf.Load() if err != nil { - t.Errorf("conf.Load(%s) failed: %s", inputs, err) + t.Errorf("conf.Load(%s) failed: %s", input, err) return false } prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog.Build() - // Find first main or test package among the initial packages. - var mainPkg *ssa.Package - for _, info := range iprog.InitialPackages() { - if info.Pkg.Path() == "runtime" { - continue // not an initial package - } - p := prog.Package(info.Pkg) - if p.Pkg.Name() == "main" && p.Func("main") != nil { - mainPkg = p - break - } - - mainPkg = prog.CreateTestMainPackage(p) - if mainPkg != nil { - break - } - } + mainPkg := prog.Package(iprog.Created[0].Pkg) if mainPkg == nil { - t.Fatalf("no main or test packages among initial packages: %s", inputs) + t.Fatalf("not a main package: %s", input) } - var out bytes.Buffer - interp.CapturedOutput = &out + interp.CapturedOutput = new(bytes.Buffer) - hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", strings.Join(inputs, " ")) - exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, inputs[0], []string{}) - - // The definition of success varies with each file. - if err := success(exitCode, out.String()); err != nil { - t.Errorf("interp.Interpret(%s) failed: %s", inputs, err) - return false + hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", input) + exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, input, []string{}) + if exitCode != 0 { + t.Fatalf("interpreting %s: exit code was %d", input, exitCode) + } + // $GOROOT/test tests use this convention: + if strings.Contains(interp.CapturedOutput.String(), "BUG") { + t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input) } hint = "" // call off the hounds if false { - fmt.Println(input, time.Since(start)) // test profiling + t.Log(input, time.Since(start)) // test profiling } return true } -const slash = string(os.PathSeparator) - func printFailures(failures []string) { if failures != nil { fmt.Println("The following tests failed:") @@ -257,26 +200,16 @@ func printFailures(failures []string) { } } -func success(exitcode int, output string) error { - if exitcode != 0 { - return fmt.Errorf("exit code was %d", exitcode) - } - if strings.Contains(output, "BUG") { - return fmt.Errorf("exited zero but output contained 'BUG'") - } - return nil -} - // TestTestdataFiles runs the interpreter on testdata/*.go. func TestTestdataFiles(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + var failures []string - start := time.Now() for _, input := range testdataTests { - if testing.Short() && time.Since(start) > 30*time.Second { - printFailures(failures) - t.Skipf("timeout - aborting test") - } - if !run(t, "testdata"+slash, input, success) { + if !run(t, filepath.Join(cwd, "testdata", input)) { failures = append(failures, input) } } @@ -285,34 +218,12 @@ func TestTestdataFiles(t *testing.T) { // TestGorootTest runs the interpreter on $GOROOT/test/*.go. func TestGorootTest(t *testing.T) { - if testing.Short() { - t.Skip() // too slow (~30s) - } - var failures []string for _, input := range gorootTestTests { - if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input, success) { + if !run(t, filepath.Join(build.Default.GOROOT, "test", input)) { failures = append(failures, input) } } printFailures(failures) } - -// CreateTestMainPackage should return nil if there were no tests. -func TestNullTestmainPackage(t *testing.T) { - var conf loader.Config - conf.CreateFromFilenames("", "testdata/b_test.go") - iprog, err := conf.Load() - if err != nil { - t.Fatalf("CreatePackages failed: %s", err) - } - prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) - mainPkg := prog.Package(iprog.Created[0].Pkg) - if mainPkg.Func("main") != nil { - t.Fatalf("unexpected main function") - } - if prog.CreateTestMainPackage(mainPkg) != nil { - t.Fatalf("CreateTestMainPackage returned non-nil") - } -} diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go index 334d5b7b..ca004d71 100644 --- a/go/ssa/interp/ops.go +++ b/go/ssa/interp/ops.go @@ -10,6 +10,7 @@ import ( "go/constant" "go/token" "go/types" + "os" "strings" "sync" "unsafe" @@ -918,21 +919,18 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { var CapturedOutput *bytes.Buffer var capturedOutputMu sync.Mutex -// write writes bytes b to the target program's file descriptor fd. +// write writes bytes b to the target program's standard output. // The print/println built-ins and the write() system call funnel // through here so they can be captured by the test driver. -func write(fd int, b []byte) (int, error) { - // TODO(adonovan): fix: on Windows, std{out,err} are not 1, 2. - if CapturedOutput != nil && (fd == 1 || fd == 2) { +func print(b []byte) (int, error) { + if CapturedOutput != nil { capturedOutputMu.Lock() CapturedOutput.Write(b) // ignore errors capturedOutputMu.Unlock() } - return syswrite(fd, b) + return os.Stdout.Write(b) } -var syswrite func(int, []byte) (int, error) // set on darwin/linux only - // callBuiltin interprets a call to builtin fn with arguments args, // returning its result. func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value) value { @@ -987,7 +985,7 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value if ln { buf.WriteRune('\n') } - write(1, buf.Bytes()) + print(buf.Bytes()) return nil case "len": diff --git a/go/ssa/interp/reflect.go b/go/ssa/interp/reflect.go index 2b828996..0a4465b0 100644 --- a/go/ssa/interp/reflect.go +++ b/go/ssa/interp/reflect.go @@ -68,11 +68,6 @@ func makeReflectType(rt rtype) value { return iface{rtypeType, rt} } -func ext۰reflect۰Init(fr *frame, args []value) value { - // Signature: func() - return nil -} - func ext۰reflect۰rtype۰Bits(fr *frame, args []value) value { // Signature: func (t reflect.rtype) int rt := args[0].(rtype).t diff --git a/go/ssa/interp/testdata/a_test.go b/go/ssa/interp/testdata/a_test.go deleted file mode 100644 index 844ec5cd..00000000 --- a/go/ssa/interp/testdata/a_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package a - -import "testing" - -func TestFoo(t *testing.T) { - t.Error("foo") -} - -func TestBar(t *testing.T) { - t.Error("bar") -} - -func BenchmarkWiz(b *testing.B) { - b.Error("wiz") -} - -// Don't test Examples since that testing package needs pipe(2) for that. diff --git a/go/ssa/interp/testdata/b_test.go b/go/ssa/interp/testdata/b_test.go deleted file mode 100644 index 4a30e96a..00000000 --- a/go/ssa/interp/testdata/b_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package b - -import "testing" - -func NotATest(t *testing.T) { - t.Error("foo") -} - -func NotABenchmark(b *testing.B) { - b.Error("wiz") -} diff --git a/go/ssa/interp/testdata/boundmeth.go b/go/ssa/interp/testdata/boundmeth.go index 255cc607..69937f9d 100644 --- a/go/ssa/interp/testdata/boundmeth.go +++ b/go/ssa/interp/testdata/boundmeth.go @@ -2,7 +2,10 @@ package main -import "fmt" +import ( + "errors" + "fmt" +) func assert(b bool) { if !b { @@ -105,7 +108,7 @@ func regress1(x error) func() string { // Regression test for b/7269: // taking the value of an interface method performs a nil check. func nilInterfaceMethodValue() { - err := fmt.Errorf("ok") + err := errors.New("ok") f := err.Error if got := f(); got != "ok" { panic(got) @@ -119,7 +122,7 @@ func nilInterfaceMethodValue() { defer func() { r := fmt.Sprint(recover()) // runtime panic string varies across toolchains - if r != "runtime error: interface conversion: interface is nil, not error" && + if r != "interface conversion: interface is nil, not error" && r != "runtime error: invalid memory address or nil pointer dereference" { panic("want runtime panic from nil interface method value, got " + r) } diff --git a/go/ssa/interp/testdata/c_test.go b/go/ssa/interp/testdata/c_test.go deleted file mode 100644 index ad80b910..00000000 --- a/go/ssa/interp/testdata/c_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package c_test - -import ( - "os" - "testing" -) - -func TestC(t *testing.T) { - println("TestC") -} - -func TestMain(m *testing.M) { - println("TestMain start") - code := m.Run() - println("TestMain end") - os.Exit(code) -} diff --git a/go/ssa/interp/testdata/callstack.go b/go/ssa/interp/testdata/callstack.go deleted file mode 100644 index 56f3b281..00000000 --- a/go/ssa/interp/testdata/callstack.go +++ /dev/null @@ -1,52 +0,0 @@ -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, "$") || 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) - } -} diff --git a/go/ssa/interp/testdata/coverage.go b/go/ssa/interp/testdata/coverage.go index 37ef95c6..00c7165c 100644 --- a/go/ssa/interp/testdata/coverage.go +++ b/go/ssa/interp/testdata/coverage.go @@ -9,6 +9,7 @@ package main import ( "fmt" "reflect" + "strings" ) func init() { @@ -171,7 +172,7 @@ func main() { // fmt. const message = "Hello, World!" - if fmt.Sprintf("%s, %s!", "Hello", "World") != message { + if fmt.Sprint("Hello", ", ", "World", "!") != message { panic("oops") } @@ -425,7 +426,7 @@ func init() { // But two slices cannot be compared, even if one is nil. defer func() { r := fmt.Sprint(recover()) - if r != "runtime error: comparing uncomparable type []string" { + if !(strings.Contains(r, "compar") && strings.Contains(r, "[]string")) { panic("want panic from slice comparison, got " + r) } }() @@ -508,7 +509,7 @@ func init() { r := fmt.Sprint(recover()) // Exact error varies by toolchain: if r != "runtime error: value method (main.T).f called using nil *main.T pointer" && - r != "value method main.T.f called using nil *T pointer" { + r != "value method (main.T).f called using nil *main.T pointer" { panic("want panic from call with nil receiver, got " + r) } }() diff --git a/go/ssa/interp/testdata/range.go b/go/ssa/interp/testdata/range.go index da8a421e..a1063366 100644 --- a/go/ssa/interp/testdata/range.go +++ b/go/ssa/interp/testdata/range.go @@ -23,7 +23,7 @@ func init() { } s := "" for _, r := range runes { - s = fmt.Sprintf("%s%c", s, r) + s += string(r) } if s != "Hello, 世界" { panic(s) diff --git a/go/ssa/interp/testdata/src/errors/errors.go b/go/ssa/interp/testdata/src/errors/errors.go new file mode 100644 index 00000000..2377563c --- /dev/null +++ b/go/ssa/interp/testdata/src/errors/errors.go @@ -0,0 +1,7 @@ +package errors + +func New(text string) error { return errorString{text} } + +type errorString struct{ s string } + +func (e errorString) Error() string { return e.s } diff --git a/go/ssa/interp/testdata/src/fmt/fmt.go b/go/ssa/interp/testdata/src/fmt/fmt.go new file mode 100644 index 00000000..2185eb70 --- /dev/null +++ b/go/ssa/interp/testdata/src/fmt/fmt.go @@ -0,0 +1,26 @@ +package fmt + +func Sprint(args ...interface{}) string + +func Print(args ...interface{}) { + for i, arg := range args { + if i > 0 { + print(" ") + } + print(Sprint(arg)) + } +} + +func Println(args ...interface{}) { + Print(args...) + println() +} + +// formatting is too complex to fake + +func Printf(args ...interface{}) string { + panic("Printf is not supported") +} +func Sprintf(format string, args ...interface{}) string { + panic("Sprintf is not supported") +} diff --git a/go/ssa/interp/testdata/src/math/math.go b/go/ssa/interp/testdata/src/math/math.go new file mode 100644 index 00000000..f51e5f57 --- /dev/null +++ b/go/ssa/interp/testdata/src/math/math.go @@ -0,0 +1,13 @@ +package math + +func NaN() float64 + +func Inf(int) float64 + +func IsNaN(float64) bool + +func Float64bits(float64) uint64 + +func Signbit(x float64) bool { + return Float64bits(x)&(1<<63) != 0 +} diff --git a/go/ssa/interp/testdata/src/os/os.go b/go/ssa/interp/testdata/src/os/os.go new file mode 100644 index 00000000..555ef549 --- /dev/null +++ b/go/ssa/interp/testdata/src/os/os.go @@ -0,0 +1,5 @@ +package os + +func Getenv(string) string + +func Exit(int) diff --git a/go/ssa/interp/testdata/src/reflect/reflect.go b/go/ssa/interp/testdata/src/reflect/reflect.go new file mode 100644 index 00000000..f6c4e279 --- /dev/null +++ b/go/ssa/interp/testdata/src/reflect/reflect.go @@ -0,0 +1,16 @@ +package reflect + +type Type interface { + String() string +} + +type Value struct { +} + +func (Value) String() string + +func SliceOf(Type) Type + +func TypeOf(interface{}) Type + +func ValueOf(interface{}) Value diff --git a/go/ssa/interp/testdata/src/runtime/runtime.go b/go/ssa/interp/testdata/src/runtime/runtime.go new file mode 100644 index 00000000..c60c7fc2 --- /dev/null +++ b/go/ssa/interp/testdata/src/runtime/runtime.go @@ -0,0 +1,22 @@ +package runtime + +// An errorString represents a runtime error described by a single string. +type errorString string + +func (e errorString) RuntimeError() {} + +func (e errorString) Error() string { + return "runtime error: " + string(e) +} + +func Breakpoint() + +type Error interface { + error + RuntimeError() +} + +const GOOS = "linux" +const GOARCH = "amd64" + +func GC() diff --git a/go/ssa/interp/testdata/src/strings/strings.go b/go/ssa/interp/testdata/src/strings/strings.go new file mode 100644 index 00000000..dd86dcf4 --- /dev/null +++ b/go/ssa/interp/testdata/src/strings/strings.go @@ -0,0 +1,9 @@ +package strings + +func Replace(s, old, new string, n int) string + +func Index(haystack, needle string) int + +func Contains(haystack, needle string) bool { + return Index(haystack, needle) >= 0 +} diff --git a/go/ssa/interp/testdata/src/time/time.go b/go/ssa/interp/testdata/src/time/time.go new file mode 100644 index 00000000..66aa9ab1 --- /dev/null +++ b/go/ssa/interp/testdata/src/time/time.go @@ -0,0 +1,5 @@ +package time + +type Duration int64 + +func Sleep(Duration) diff --git a/go/ssa/interp/testdata/src/unicode/utf8/utf8.go b/go/ssa/interp/testdata/src/unicode/utf8/utf8.go new file mode 100644 index 00000000..0e44f7cb --- /dev/null +++ b/go/ssa/interp/testdata/src/unicode/utf8/utf8.go @@ -0,0 +1,9 @@ +package utf8 + +func DecodeRuneInString(string) (rune, int) + +func DecodeRune(b []byte) (rune, int) { + return DecodeRuneInString(string(b)) +} + +const RuneError = '\uFFFD' diff --git a/go/ssa/interp/testdata/src/unsafe/unsafe.go b/go/ssa/interp/testdata/src/unsafe/unsafe.go new file mode 100644 index 00000000..2d727856 --- /dev/null +++ b/go/ssa/interp/testdata/src/unsafe/unsafe.go @@ -0,0 +1 @@ +package unsafe