1
0
mirror of https://github.com/golang/go synced 2024-09-25 07:20:12 -06:00

exp/ssa: a number of bug fixes.

ssadump:
- permit naming a package (not just *.go files) on command line.
- set BuildSerially flag when setting Log* flags
  (Q. should instead the logging functions take a lock?)

Builder:
- fixed bug when calling variadic function with zero '...'-params.
  Added regression test.

interp:
- more external functions:
   the 'error' interface
   bytes.{Equal,IndexByte}
   reflect.(Value).{Bool,NumOut,Out}
   syscall.{Close,Fstat,Read,Open,Stat,Lstat,Fstat,
     Getdents,ParseDirents,Getwd}
- permit comparisons between *Function and *closure.

With this CL, ssadump can now interpret ssadump itself (!),
loading, parsing, typing, SSA-building, and running
println("Hello, World!").  While a fmt-based equivalent still
lacks some external routines, e.g. math/big, I think there are
diminishing returns in expanding the interpreter (and
debugging it is starting to feel like "Inception").

I'm pretty confident this package is now good enough for wider use.

R=gri
CC=golang-dev
https://golang.org/cl/7392053
This commit is contained in:
Alan Donovan 2013-02-27 16:43:16 -05:00
parent d1d38c535d
commit 1c5e079600
10 changed files with 313 additions and 57 deletions

View File

@ -1077,7 +1077,7 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
case *types.Signature:
np := len(typ.Params)
if !c.HasEllipsis {
if typ.IsVariadic && len(args) > np-1 {
if typ.IsVariadic {
// case 2: ordinary call of variadic function.
vt = typ.Params[np-1].Type
args, varargs = args[:np-1], args[np-1:]
@ -1216,7 +1216,7 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
}
// Common code for varargs.
if len(varargs) > 0 { // case 2
if vt != nil { // case 2
at := &types.Array{
Elt: vt,
Len: int64(len(varargs)),

View File

@ -21,6 +21,7 @@ type externalFn func(fn *ssa.Function, args []value) value
// Key strings are from Function.FullName().
// That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd].
var externals = map[string]externalFn{
"(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,
@ -36,10 +37,15 @@ var externals = map[string]externalFn{
"(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer,
"(reflect.Value).String": ext۰reflect۰Value۰String,
"(reflect.Value).Type": ext۰reflect۰Value۰Type,
"(reflect.error).Error": ext۰reflect۰error۰Error,
"(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits,
"(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem,
"(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind,
"(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut,
"(reflect.rtype).Out": ext۰reflect۰rtype۰Out,
"(reflect.rtype).String": ext۰reflect۰rtype۰String,
"bytes.Equal": ext۰bytes۰Equal,
"bytes.IndexByte": ext۰bytes۰IndexByte,
"math.Float32bits": ext۰math۰Float32bits,
"math.Float32frombits": ext۰math۰Float32frombits,
"math.Float64bits": ext۰math۰Float64bits,
@ -61,14 +67,58 @@ var externals = map[string]externalFn{
"sync/atomic.LoadUint32": ext۰atomic۰LoadUint32,
"sync/atomic.StoreInt32": ext۰atomic۰StoreInt32,
"sync/atomic.StoreUint32": ext۰atomic۰StoreUint32,
"syscall.Close": ext۰syscall۰Close,
"syscall.Exit": ext۰syscall۰Exit,
"syscall.Fstat": ext۰syscall۰Fstat,
"syscall.Getdents": ext۰syscall۰Getdents,
"syscall.Getpid": ext۰syscall۰Getpid,
"syscall.Getwd": ext۰syscall۰Getwd,
"syscall.Kill": ext۰syscall۰Kill,
"syscall.Lstat": ext۰syscall۰Lstat,
"syscall.Open": ext۰syscall۰Open,
"syscall.ParseDirent": ext۰syscall۰ParseDirent,
"syscall.Read": ext۰syscall۰Read,
"syscall.Stat": ext۰syscall۰Stat,
"syscall.Write": ext۰syscall۰Write,
"time.Sleep": ext۰time۰Sleep,
"time.now": ext۰time۰now,
}
// 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۰bytes۰Equal(fn *ssa.Function, args []value) value {
// func Equal(a, b []byte) bool
a := args[0].([]value)
b := args[1].([]value)
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func ext۰bytes۰IndexByte(fn *ssa.Function, args []value) value {
// func IndexByte(s []byte, c byte) int
s := args[0].([]value)
c := args[1].(byte)
for i, b := range s {
if b.(byte) == c {
return i
}
}
return -1
}
func ext۰math۰Float64frombits(fn *ssa.Function, args []value) value {
return math.Float64frombits(args[0].(uint64))
}
@ -171,15 +221,17 @@ func ext۰syscall۰Exit(fn *ssa.Function, args []value) value {
panic(exitPanic(args[0].(int)))
}
func ext۰syscall۰Getwd(fn *ssa.Function, args []value) value {
s, err := syscall.Getwd()
return tuple{s, wrapError(err)}
}
func ext۰syscall۰Getpid(fn *ssa.Function, args []value) value {
// We could emulate syscall.Syscall but it's more effort.
return syscall.Getpid()
}
// The set of remaining native functions we need to implement (as needed):
// bytes/bytes.go:42:func Equal(a, b []byte) bool
// bytes/bytes_decl.go:8:func IndexByte(s []byte, c byte) int // asm_$GOARCH.s
// crypto/aes/cipher_asm.go:10:func hasAsm() bool
// crypto/aes/cipher_asm.go:11:func encryptBlockAsm(nr int, xk *uint32, dst, src *byte)
// crypto/aes/cipher_asm.go:12:func decryptBlockAsm(nr int, xk *uint32, dst, src *byte)
@ -283,10 +335,6 @@ func ext۰syscall۰Getpid(fn *ssa.Function, args []value) value {
// syscall/syscall_linux_amd64.go:60:func Gettimeofday(tv *Timeval) (err error)
// syscall/syscall_linux_amd64.go:61:func Time(t *Time_t) (tt Time_t, err error)
// syscall/syscall_linux_arm.go:28:func Seek(fd int, offset int64, whence int) (newoffset int64, err error)
// syscall/syscall_unix.go:23:func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
// syscall/syscall_unix.go:24:func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
// syscall/syscall_unix.go:25:func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
// syscall/syscall_unix.go:26:func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
// time/sleep.go:25:func startTimer(*runtimeTimer)
// time/sleep.go:26:func stopTimer(*runtimeTimer) bool
// time/time.go:758:func now() (sec int64, nsec int32)

View File

@ -9,18 +9,40 @@ import (
"syscall"
)
func ext۰syscall۰Close(fn *ssa.Function, args []value) value {
panic("syscall.Close not yet implemented")
}
func ext۰syscall۰Fstat(fn *ssa.Function, args []value) value {
panic("syscall.Fstat not yet implemented")
}
func ext۰syscall۰Getdents(fn *ssa.Function, args []value) value {
panic("syscall.Getdents not yet implemented")
}
func ext۰syscall۰Kill(fn *ssa.Function, args []value) value {
panic("syscall.Kill not yet implemented")
}
func ext۰syscall۰Lstat(fn *ssa.Function, args []value) value {
panic("syscall.Lstat not yet implemented")
}
func ext۰syscall۰Open(fn *ssa.Function, args []value) value {
panic("syscall.Open not yet implemented")
}
func ext۰syscall۰ParseDirent(fn *ssa.Function, args []value) value {
panic("syscall.ParseDirent not yet implemented")
}
func ext۰syscall۰Read(fn *ssa.Function, args []value) value {
panic("syscall.Read not yet implemented")
}
func ext۰syscall۰Stat(fn *ssa.Function, args []value) value {
panic("syscall.Stat not yet implemented")
}
func ext۰syscall۰Write(fn *ssa.Function, args []value) value {
// We could emulate syscall.Syscall but it's more effort.
p := args[1].([]value)
b := make([]byte, 0, len(p))
for i := range p {
b = append(b, p[i].(byte))
}
n, _ := syscall.Write(args[0].(int), b)
err := iface{} // TODO(adonovan): fix: adapt concrete err to interpreted iface.
return tuple{n, err}
n, err := syscall.Write(args[0].(int), b)
return tuple{n, wrapError(err)}
}

View File

@ -11,21 +11,126 @@ import (
"syscall"
)
func valueToBytes(v value) []byte {
in := v.([]value)
b := make([]byte, len(in))
for i := range in {
b[i] = in[i].(byte)
}
return b
}
func fillStat(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(fn *ssa.Function, args []value) value {
// func Close(fd int) (err error)
return wrapError(syscall.Close(args[0].(int)))
}
func ext۰syscall۰Fstat(fn *ssa.Function, 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۰Getdents(fn *ssa.Function, args []value) value {
// func GetDents(fd int, buf []byte) (n int, err error)
fd := args[0].(int)
p := args[1].([]value)
b := make([]byte, len(p))
n, err := syscall.Getdents(fd, b)
for i := 0; i < n; i++ {
p[i] = b[i]
}
return tuple{n, wrapError(err)}
}
func ext۰syscall۰Kill(fn *ssa.Function, args []value) value {
// We could emulate syscall.Syscall but it's more effort.
err := syscall.Kill(args[0].(int), syscall.Signal(args[1].(int)))
err = err // TODO(adonovan): fix: adapt concrete err to interpreted iface (e.g. call interpreted errors.New)
return iface{}
// func Kill(pid int, sig Signal) (err error)
return wrapError(syscall.Kill(args[0].(int), syscall.Signal(args[1].(int))))
}
func ext۰syscall۰Lstat(fn *ssa.Function, 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۰Open(fn *ssa.Function, 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(fn *ssa.Function, 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۰Read(fn *ssa.Function, 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۰Stat(fn *ssa.Function, 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۰Write(fn *ssa.Function, args []value) value {
// We could emulate syscall.Syscall but it's more effort.
p := args[1].([]value)
b := make([]byte, 0, len(p))
for i := range p {
b = append(b, p[i].(byte))
}
n, _ := syscall.Write(args[0].(int), b)
err := iface{} // TODO(adonovan): fix: adapt concrete err to interpreted iface.
return tuple{n, err}
// func Write(fd int, p []byte) (n int, err error)
n, err := syscall.Write(args[0].(int), valueToBytes(args[1]))
return tuple{n, wrapError(err)}
}

View File

@ -10,10 +10,33 @@ import (
"exp/ssa"
)
func ext۰syscall۰Close(fn *ssa.Function, args []value) value {
panic("syscall.Close not yet implemented")
}
func ext۰syscall۰Fstat(fn *ssa.Function, args []value) value {
panic("syscall.Fstat not yet implemented")
}
func ext۰syscall۰Getdents(fn *ssa.Function, args []value) value {
panic("syscall.Getdents not yet implemented")
}
func ext۰syscall۰Kill(fn *ssa.Function, args []value) value {
panic("syscall.Kill not yet implemented")
}
func ext۰syscall۰Lstat(fn *ssa.Function, args []value) value {
panic("syscall.Lstat not yet implemented")
}
func ext۰syscall۰Open(fn *ssa.Function, args []value) value {
panic("syscall.Open not yet implemented")
}
func ext۰syscall۰ParseDirent(fn *ssa.Function, args []value) value {
panic("syscall.ParseDirent not yet implemented")
}
func ext۰syscall۰Read(fn *ssa.Function, args []value) value {
panic("syscall.Read not yet implemented")
}
func ext۰syscall۰Stat(fn *ssa.Function, args []value) value {
panic("syscall.Stat not yet implemented")
}
func ext۰syscall۰Write(fn *ssa.Function, args []value) value {
panic("syscall.Write not yet implemented")
}

View File

@ -84,6 +84,7 @@ type interpreter struct {
globals map[ssa.Value]*value // addresses of global variables (immutable)
mode Mode // interpreter options
reflectPackage *ssa.Package // the fake reflect package
errorMethods ssa.MethodSet // the method set of reflect.error, which implements the error interface.
rtypeMethods ssa.MethodSet // the method set of rtype, which implements the reflect.Type interface.
}
@ -132,8 +133,11 @@ func (fr *frame) rundefers() {
// findMethodSet returns the method set for type typ, which may be one
// of the interpreter's fake types.
func findMethodSet(i *interpreter, typ types.Type) ssa.MethodSet {
if typ == rtypeType {
switch typ {
case rtypeType:
return i.rtypeMethods
case errorType:
return i.errorMethods
}
return i.prog.MethodSet(typ)
}
@ -211,8 +215,9 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
return kJump
case *ssa.Defer:
pos := instr.Pos // TODO(gri): workaround for bug in typeswitch+funclit.
fn, args := prepareCall(fr, &instr.CallCommon)
fr.defers = append(fr.defers, func() { call(fr.i, fr, instr.Pos, fn, args) })
fr.defers = append(fr.defers, func() { call(fr.i, fr, pos, fn, args) })
case *ssa.Go:
fn, args := prepareCall(fr, &instr.CallCommon)

View File

@ -28,6 +28,14 @@ var reflectTypesPackage = &types.Package{
// type rtype <opaque>
var rtypeType = makeNamedType("rtype", &types.Basic{Name: "rtype"})
// error is an (interpreted) named type whose underlying type is string.
// The interpreter uses it for all implementations of the built-in error
// interface that it creates.
// We put it in the "reflect" package for expedience.
//
// type error string
var errorType = makeNamedType("error", &types.Basic{Name: "error"})
func makeNamedType(name string, underlying types.Type) *types.NamedType {
nt := &types.NamedType{Underlying: underlying}
nt.Obj = &types.TypeName{
@ -123,6 +131,17 @@ func ext۰reflect۰rtype۰Kind(fn *ssa.Function, args []value) value {
return uint(reflectKind(args[0].(rtype).t))
}
func ext۰reflect۰rtype۰NumOut(fn *ssa.Function, args []value) value {
// Signature: func (t reflect.rtype) int
return len(args[0].(rtype).t.(*types.Signature).Results)
}
func ext۰reflect۰rtype۰Out(fn *ssa.Function, args []value) value {
// Signature: func (t reflect.rtype, i int) int
i := args[1].(int)
return makeReflectType(rtype{args[0].(rtype).t.(*types.Signature).Results[i].Type})
}
func ext۰reflect۰rtype۰String(fn *ssa.Function, args []value) value {
// Signature: func (t reflect.rtype) string
return args[0].(rtype).t.String()
@ -279,6 +298,11 @@ func ext۰reflect۰Value۰Index(fn *ssa.Function, args []value) value {
return nil // unreachable
}
func ext۰reflect۰Value۰Bool(fn *ssa.Function, args []value) value {
// Signature: func (reflect.Value) bool
return rV2V(args[0]).(bool)
}
func ext۰reflect۰Value۰CanAddr(fn *ssa.Function, args []value) value {
// Signature: func (v reflect.Value) bool
// Always false for our representation.
@ -373,6 +397,10 @@ func ext۰reflect۰valueInterface(fn *ssa.Function, args []value) value {
return iface{rV2T(v).t, rV2V(v)}
}
func ext۰reflect۰error۰Error(fn *ssa.Function, args []value) value {
return args[0]
}
// newMethod creates a new method of the specified name, package and receiver type.
func newMethod(pkg *ssa.Package, recvType types.Type, name string) *ssa.Function {
fn := &ssa.Function{
@ -402,6 +430,11 @@ func initReflect(i *interpreter) {
ssa.Id{nil, "Bits"}: newMethod(i.reflectPackage, rtypeType, "Bits"),
ssa.Id{nil, "Elem"}: newMethod(i.reflectPackage, rtypeType, "Elem"),
ssa.Id{nil, "Kind"}: newMethod(i.reflectPackage, rtypeType, "Kind"),
ssa.Id{nil, "NumOut"}: newMethod(i.reflectPackage, rtypeType, "NumOut"),
ssa.Id{nil, "Out"}: newMethod(i.reflectPackage, rtypeType, "Out"),
ssa.Id{nil, "String"}: newMethod(i.reflectPackage, rtypeType, "String"),
}
i.errorMethods = ssa.MethodSet{
ssa.Id{nil, "Error"}: newMethod(i.reflectPackage, errorType, "Error"),
}
}

View File

@ -23,6 +23,13 @@ func init() {
}
}
func init() {
// Call of variadic function with (implicit) empty slice.
if x := fmt.Sprint(); x != "" {
panic(x)
}
}
type empty interface{}
type I interface {

View File

@ -226,10 +226,8 @@ func equals(x, y value) bool {
return x == y.(*hashmap)
case map[value]value:
return (x != nil) == (y.(map[value]value) != nil)
case *ssa.Function:
return x == y.(*ssa.Function)
case *closure:
return x == y.(*closure)
case *ssa.Function, *closure:
return x == y
case []value:
return (x != nil) == (y.([]value) != nil)
}

View File

@ -9,6 +9,7 @@ import (
"exp/ssa/interp"
"flag"
"fmt"
"go/ast"
"log"
"os"
"runtime/pprof"
@ -36,7 +37,8 @@ T [T]race execution of the program. Best for single-threaded programs!
`)
const usage = `SSA builder and interpreter.
Usage: ssadump [<flag> ...] <file.go> ...
Usage: ssadump [<flag> ...] [<file.go> ...] [<arg> ...]
ssadump [<flag> ...] <import/path> [<arg> ...]
Use -help flag to display options.
Examples:
@ -56,11 +58,11 @@ func main() {
for _, c := range *buildFlag {
switch c {
case 'P':
mode |= ssa.LogPackages
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':
mode |= ssa.LogFunctions
mode |= ssa.LogFunctions | ssa.BuildSerially
case 'S':
mode |= ssa.LogSource
mode |= ssa.LogSource | ssa.BuildSerially
case 'C':
mode |= ssa.SanityCheckFunctions
case 'N':
@ -91,19 +93,6 @@ func main() {
os.Exit(1)
}
// Treat all leading consecutive "*.go" arguments as a single package.
//
// TODO(gri): make it a typechecker error for there to be
// duplicate (e.g.) main functions in the same package.
var gofiles []string
for len(args) > 0 && strings.HasSuffix(args[0], ".go") {
gofiles = append(gofiles, args[0])
args = args[1:]
}
if gofiles == nil {
log.Fatal("No *.go source files specified.")
}
// Profiling support.
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
@ -114,20 +103,46 @@ func main() {
defer pprof.StopCPUProfile()
}
// TODO(adonovan): permit naming a package directly instead of
// a list of .go files.
// TODO(adonovan/gri): the cascade of errors is confusing due
// to reentrant control flow. Disable for now and re-think.
var errh func(error)
// errh = func(err error) { fmt.Println(err.Error()) }
b := ssa.NewBuilder(mode, ssa.GorootLoader, errh)
files, err := ssa.ParseFiles(b.Prog.Files, ".", gofiles...)
loader := ssa.GorootLoader
b := ssa.NewBuilder(mode, loader, errh)
var pkgname string
var files []*ast.File
var err error
switch {
case len(args) == 0:
log.Fatal("No *.go source files nor package name was specified.")
case strings.HasSuffix(args[0], ".go"):
// % ssadump a.go b.go ...
// Leading consecutive *.go arguments constitute main package.
i := 1
for ; i < len(args) && strings.HasSuffix(args[i], ".go"); i++ {
}
files, err = ssa.ParseFiles(b.Prog.Files, ".", args[:i]...)
pkgname = "main"
args = args[i:]
default:
// % ssadump my/package ...
// First argument is import path of main package.
pkgname = args[0]
args = args[1:]
files, err = loader(b.Prog.Files, pkgname)
}
if err != nil {
log.Fatalf(err.Error())
}
mainpkg, err := b.CreatePackage("main", files)
// TODO(gri): make it a typechecker error for there to be
// duplicate (e.g.) main functions in the same package.
mainpkg, err := b.CreatePackage(pkgname, files)
if err != nil {
log.Fatalf(err.Error())
}
@ -135,6 +150,6 @@ func main() {
b = nil // discard Builder
if *runFlag {
interp.Interpret(mainpkg, interpMode, gofiles[0], args)
interp.Interpret(mainpkg, interpMode, pkgname, args)
}
}