diff options
Diffstat (limited to 'src')
34 files changed, 12992 insertions, 3 deletions
diff --git a/src/pkg/exp/eval/Makefile b/src/pkg/exp/eval/Makefile new file mode 100644 index 000000000..65bedf7ba --- /dev/null +++ b/src/pkg/exp/eval/Makefile @@ -0,0 +1,23 @@ +# Copyright 2009 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. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=exp/eval +GOFILES=\ + abort.go\ + bridge.go\ + compiler.go\ + expr.go\ + expr1.go\ + func.go\ + scope.go\ + stmt.go\ + type.go\ + typec.go\ + util.go\ + value.go\ + world.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/src/pkg/exp/eval/abort.go b/src/pkg/exp/eval/abort.go new file mode 100644 index 000000000..38ad2bf62 --- /dev/null +++ b/src/pkg/exp/eval/abort.go @@ -0,0 +1,91 @@ +// Copyright 2009 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 eval + +import ( + "fmt"; + "os"; + "runtime"; +) + +// Abort aborts the thread's current computation, +// causing the innermost Try to return err. +func (t *Thread) Abort(err os.Error) { + if t.abort == nil { + panicln("abort:", err.String()); + } + t.abort <- err; + runtime.Goexit(); +} + +// Try executes a computation; if the computation +// Aborts, Try returns the error passed to abort. +func (t *Thread) Try(f func(t *Thread)) os.Error { + oc := t.abort; + c := make(chan os.Error); + t.abort = c; + go func() { + f(t); + c <- nil; + }(); + err := <-c; + t.abort = oc; + return err; +} + +type DivByZeroError struct {} + +func (DivByZeroError) String() string { + return "divide by zero"; +} + +type NilPointerError struct {} + +func (NilPointerError) String() string { + return "nil pointer dereference"; +} + +type IndexError struct { + Idx, Len int64; +} + +func (e IndexError) String() string { + if e.Idx < 0 { + return fmt.Sprintf("negative index: %d", e.Idx); + } + return fmt.Sprintf("index %d exceeds length %d", e.Idx, e.Len); +} + +type SliceError struct { + Lo, Hi, Cap int64; +} + +func (e SliceError) String() string { + return fmt.Sprintf("slice [%d:%d]; cap %d", e.Lo, e.Hi, e.Cap); +} + +type KeyError struct { + Key interface {}; +} + +func (e KeyError) String() string { + return fmt.Sprintf("key '%v' not found in map", e.Key); +} + +type NegativeLengthError struct { + Len int64; +} + +func (e NegativeLengthError) String() string { + return fmt.Sprintf("negative length: %d", e.Len); +} + +type NegativeCapacityError struct { + Len int64; +} + +func (e NegativeCapacityError) String() string { + return fmt.Sprintf("negative capacity: %d", e.Len); +} diff --git a/src/pkg/exp/eval/bridge.go b/src/pkg/exp/eval/bridge.go new file mode 100644 index 000000000..da2dd52a9 --- /dev/null +++ b/src/pkg/exp/eval/bridge.go @@ -0,0 +1,170 @@ +// Copyright 2009 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 eval + +import ( + "log"; + "go/token"; + "reflect"; +) + +/* + * Type bridging + */ + +var ( + evalTypes = make(map[reflect.Type] Type); + nativeTypes = make(map[Type] reflect.Type); +) + +// TypeFromNative converts a regular Go type into a the corresponding +// interpreter Type. +func TypeFromNative(t reflect.Type) Type { + if et, ok := evalTypes[t]; ok { + return et; + } + + var nt *NamedType; + if t.Name() != "" { + name := t.PkgPath() + "ยท" + t.Name(); + nt = &NamedType{token.Position{}, name, nil, true, make(map[string] Method)}; + evalTypes[t] = nt; + } + + var et Type; + switch t := t.(type) { + case *reflect.BoolType: + et = BoolType; + case *reflect.Float32Type: + et = Float32Type; + case *reflect.Float64Type: + et = Float64Type; + case *reflect.FloatType: + et = FloatType; + case *reflect.Int16Type: + et = Int16Type; + case *reflect.Int32Type: + et = Int32Type; + case *reflect.Int64Type: + et = Int64Type; + case *reflect.Int8Type: + et = Int8Type; + case *reflect.IntType: + et = IntType; + case *reflect.StringType: + et = StringType; + case *reflect.Uint16Type: + et = Uint16Type; + case *reflect.Uint32Type: + et = Uint32Type; + case *reflect.Uint64Type: + et = Uint64Type; + case *reflect.Uint8Type: + et = Uint8Type; + case *reflect.UintType: + et = UintType; + case *reflect.UintptrType: + et = UintptrType; + + case *reflect.ArrayType: + et = NewArrayType(int64(t.Len()), TypeFromNative(t.Elem())); + case *reflect.ChanType: + log.Crashf("%T not implemented", t); + case *reflect.FuncType: + nin := t.NumIn(); + // Variadic functions have DotDotDotType at the end + varidic := false; + if nin > 0 { + if _, ok := t.In(nin - 1).(*reflect.DotDotDotType); ok { + varidic = true; + nin--; + } + } + in := make([]Type, nin); + for i := range in { + in[i] = TypeFromNative(t.In(i)); + } + out := make([]Type, t.NumOut()); + for i := range out { + out[i] = TypeFromNative(t.Out(i)); + } + et = NewFuncType(in, varidic, out); + case *reflect.InterfaceType: + log.Crashf("%T not implemented", t); + case *reflect.MapType: + log.Crashf("%T not implemented", t); + case *reflect.PtrType: + et = NewPtrType(TypeFromNative(t.Elem())); + case *reflect.SliceType: + et = NewSliceType(TypeFromNative(t.Elem())); + case *reflect.StructType: + n := t.NumField(); + fields := make([]StructField, n); + for i := 0; i < n; i++ { + sf := t.Field(i); + // TODO(austin) What to do about private fields? + fields[i].Name = sf.Name; + fields[i].Type = TypeFromNative(sf.Type); + fields[i].Anonymous = sf.Anonymous; + } + et = NewStructType(fields); + case *reflect.UnsafePointerType: + log.Crashf("%T not implemented", t); + default: + log.Crashf("unexpected reflect.Type: %T", t); + } + + if nt != nil { + if _, ok := et.(*NamedType); !ok { + nt.Complete(et); + et = nt; + } + } + + nativeTypes[et] = t; + evalTypes[t] = et; + + return et; +} + +// TypeOfNative returns the interpreter Type of a regular Go value. +func TypeOfNative(v interface {}) Type { + return TypeFromNative(reflect.Typeof(v)); +} + +/* + * Function bridging + */ + +type nativeFunc struct { + fn func(*Thread, []Value, []Value); + in, out int; +} + +func (f *nativeFunc) NewFrame() *Frame { + vars := make([]Value, f.in + f.out); + return &Frame{nil, vars}; +} + +func (f *nativeFunc) Call(t *Thread) { + f.fn(t, t.f.Vars[0:f.in], t.f.Vars[f.in:f.in+f.out]); +} + +// FuncFromNative creates an interpreter function from a native +// function that takes its in and out arguments as slices of +// interpreter Value's. While somewhat inconvenient, this avoids +// value marshalling. +func FuncFromNative(fn func(*Thread, []Value, []Value), t *FuncType) FuncValue { + return &funcV{&nativeFunc{fn, len(t.In), len(t.Out)}}; +} + +// FuncFromNativeTyped is like FuncFromNative, but constructs the +// function type from a function pointer using reflection. Typically, +// the type will be given as a nil pointer to a function with the +// desired signature. +func FuncFromNativeTyped(fn func(*Thread, []Value, []Value), t interface{}) (*FuncType, FuncValue) { + ft := TypeOfNative(t).(*FuncType); + return ft, FuncFromNative(fn, ft); +} diff --git a/src/pkg/exp/eval/compiler.go b/src/pkg/exp/eval/compiler.go new file mode 100644 index 000000000..f3c962c2b --- /dev/null +++ b/src/pkg/exp/eval/compiler.go @@ -0,0 +1,97 @@ +// Copyright 2009 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 eval + +import ( + "fmt"; + "go/scanner"; + "go/token"; +) + + +type positioned interface { + Pos() token.Position; +} + + +// A compiler captures information used throughout an entire +// compilation. Currently it includes only the error handler. +// +// TODO(austin) This might actually represent package level, in which +// case it should be package compiler. +type compiler struct { + errors scanner.ErrorHandler; + numErrors int; + silentErrors int; +} + +func (a *compiler) diagAt(pos positioned, format string, args ...) { + a.errors.Error(pos.Pos(), fmt.Sprintf(format, args)); + a.numErrors++; +} + +func (a *compiler) numError() int { + return a.numErrors + a.silentErrors; +} + +// The universal scope +func newUniverse() *Scope { + sc := &Scope{nil, 0}; + sc.block = &block{ + offset: 0, + scope: sc, + global: true, + defs: make(map[string] Def) + }; + return sc; +} +var universe *Scope = newUniverse(); + + +// TODO(austin) These can all go in stmt.go now +type label struct { + name string; + desc string; + // The PC goto statements should jump to, or nil if this label + // cannot be goto'd (such as an anonymous for loop label). + gotoPC *uint; + // The PC break statements should jump to, or nil if a break + // statement is invalid. + breakPC *uint; + // The PC continue statements should jump to, or nil if a + // continue statement is invalid. + continuePC *uint; + // The position where this label was resolved. If it has not + // been resolved yet, an invalid position. + resolved token.Position; + // The position where this label was first jumped to. + used token.Position; +} + +// A funcCompiler captures information used throughout the compilation +// of a single function body. +type funcCompiler struct { + *compiler; + fnType *FuncType; + // Whether the out variables are named. This affects what + // kinds of return statements are legal. + outVarsNamed bool; + *codeBuf; + flow *flowBuf; + labels map[string] *label; +} + +// A blockCompiler captures information used throughout the compilation +// of a single block within a function. +type blockCompiler struct { + *funcCompiler; + block *block; + // The label of this block, used for finding break and + // continue labels. + label *label; + // The blockCompiler for the block enclosing this one, or nil + // for a function-level block. + parent *blockCompiler; +} diff --git a/src/pkg/exp/eval/eval_test.go b/src/pkg/exp/eval/eval_test.go new file mode 100644 index 000000000..192a2e782 --- /dev/null +++ b/src/pkg/exp/eval/eval_test.go @@ -0,0 +1,270 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "flag"; + "fmt"; + "log"; + "os"; + "reflect"; + "testing"; +) + +// Print each statement or expression before parsing it +var noisy = false +func init() { + flag.BoolVar(&noisy, "noisy", false, "chatter during eval tests"); +} + +/* + * Generic statement/expression test framework + */ + +type test []job + +type job struct { + code string; + cerr string; + rterr string; + val Value; + noval bool; +} + +func runTests(t *testing.T, baseName string, tests []test) { + for i, test := range tests { + name := fmt.Sprintf("%s[%d]", baseName, i); + test.run(t, name); + } +} + +func (a test) run(t *testing.T, name string) { + w := newTestWorld(); + for _, j := range a { + src := j.code; + if noisy { + println("code:", src); + } + + code, err := w.Compile(src); + if err != nil { + if j.cerr == "" { + t.Errorf("%s: Compile %s: %v", name, src, err); + break; + } + if !match(t, err, j.cerr) { + t.Errorf("%s: Compile %s = error %s; want %v", name, src, err, j.cerr); + break; + } + continue; + } + if j.cerr != "" { + t.Errorf("%s: Compile %s succeeded; want %s", name, src, j.cerr); + break; + } + + val, err := code.Run(); + if err != nil { + if j.rterr == "" { + t.Errorf("%s: Run %s: %v", name, src, err); + break; + } + if !match(t, err, j.rterr) { + t.Errorf("%s: Run %s = error %s; want %v", name, src, err, j.rterr); + break; + } + continue; + } + if j.rterr != "" { + t.Errorf("%s: Run %s succeeded; want %s", name, src, j.rterr); + break; + } + + if !j.noval && !reflect.DeepEqual(val, j.val) { + t.Errorf("%s: Run %s = %T(%v) want %T(%v)", name, src, val, val, j.val, j.val); + } + } +} + +func match(t *testing.T, err os.Error, pat string) bool { + ok, errstr := testing.MatchString(pat, err.String()); + if errstr != "" { + t.Fatalf("compile regexp %s: %v", pat, errstr); + } + return ok; +} + + +/* + * Test constructors + */ + +// Expression compile error +func CErr(expr string, cerr string) test { + return test([]job{job{code: expr, cerr: cerr}}) +} + +// Expression runtime error +func RErr(expr string, rterr string) test { + return test([]job{job{code: expr, rterr: rterr}}) +} + +// Expression value +func Val(expr string, val interface{}) test { + return test([]job{job{code: expr, val: toValue(val)}}) +} + +// Statement runs without error +func Run(stmts string) test { + return test([]job{job{code: stmts, noval: true}}) +} + +// Two statements without error. +// TODO(rsc): Should be possible with Run but the parser +// won't let us do both top-level and non-top-level statements. +func Run2(stmt1, stmt2 string) test { + return test([]job{job{code: stmt1, noval: true}, job{code: stmt2, noval: true}}) +} + +// Statement runs and test one expression's value +func Val1(stmts string, expr1 string, val1 interface{}) test { + return test([]job{ + job{code: stmts, noval: true}, + job{code: expr1, val: toValue(val1)} + }) +} + +// Statement runs and test two expressions' values +func Val2(stmts string, expr1 string, val1 interface{}, expr2 string, val2 interface{}) test { + return test([]job{ + job{code: stmts, noval: true}, + job{code: expr1, val: toValue(val1)}, + job{code: expr2, val: toValue(val2)} + }) +} + +/* + * Value constructors + */ + +type vstruct []interface{} + +type varray []interface{} + +type vslice struct { + arr varray; + len, cap int; +} + +func toValue(val interface{}) Value { + switch val := val.(type) { + case bool: + r := boolV(val); + return &r; + case uint8: + r := uint8V(val); + return &r; + case uint: + r := uintV(val); + return &r; + case int: + r := intV(val); + return &r; + case *bignum.Integer: + return &idealIntV{val}; + case float: + r := floatV(val); + return &r; + case *bignum.Rational: + return &idealFloatV{val}; + case string: + r := stringV(val); + return &r; + case vstruct: + elems := make([]Value, len(val)); + for i, e := range val { + elems[i] = toValue(e); + } + r := structV(elems); + return &r; + case varray: + elems := make([]Value, len(val)); + for i, e := range val { + elems[i] = toValue(e); + } + r := arrayV(elems); + return &r; + case vslice: + return &sliceV{Slice{toValue(val.arr).(ArrayValue), int64(val.len), int64(val.cap)}}; + case Func: + return &funcV{val}; + } + log.Crashf("toValue(%T) not implemented", val); + panic(); +} + +/* + * Default test scope + */ + +type testFunc struct {}; + +func (*testFunc) NewFrame() *Frame { + return &Frame{nil, &[2]Value {}}; +} + +func (*testFunc) Call(t *Thread) { + n := t.f.Vars[0].(IntValue).Get(t); + + res := n + 1; + + t.f.Vars[1].(IntValue).Set(t, res); +} + +type oneTwoFunc struct {}; + +func (*oneTwoFunc) NewFrame() *Frame { + return &Frame{nil, &[2]Value {}}; +} + +func (*oneTwoFunc) Call(t *Thread) { + t.f.Vars[0].(IntValue).Set(t, 1); + t.f.Vars[1].(IntValue).Set(t, 2); +} + +type voidFunc struct {}; + +func (*voidFunc) NewFrame() *Frame { + return &Frame{nil, []Value {}}; +} + +func (*voidFunc) Call(t *Thread) { +} + +func newTestWorld() *World { + w := NewWorld(); + + def := func(name string, t Type, val interface{}) { + w.DefineVar(name, t, toValue(val)); + }; + + w.DefineConst("c", IdealIntType, toValue(bignum.Int(1))); + def("i", IntType, 1); + def("i2", IntType, 2); + def("u", UintType, uint(1)); + def("f", FloatType, 1.0); + def("s", StringType, "abc"); + def("t", NewStructType([]StructField {StructField{"a", IntType, false}}), vstruct{1}); + def("ai", NewArrayType(2, IntType), varray{1, 2}); + def("aai", NewArrayType(2, NewArrayType(2, IntType)), varray{varray{1,2}, varray{3,4}}); + def("aai2", NewArrayType(2, NewArrayType(2, IntType)), varray{varray{5,6}, varray{7,8}}); + def("fn", NewFuncType([]Type{IntType}, false, []Type {IntType}), &testFunc{}); + def("oneTwo", NewFuncType([]Type{}, false, []Type {IntType, IntType}), &oneTwoFunc{}); + def("void", NewFuncType([]Type{}, false, []Type {}), &voidFunc{}); + def("sli", NewSliceType(IntType), vslice{varray{1, 2, 3}, 2, 3}); + + return w; +} diff --git a/src/pkg/exp/eval/expr.go b/src/pkg/exp/eval/expr.go new file mode 100644 index 000000000..ea4fc082b --- /dev/null +++ b/src/pkg/exp/eval/expr.go @@ -0,0 +1,2007 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "go/ast"; + "go/token"; + "log"; + "strconv"; + "strings"; + "os"; +) + +// An expr is the result of compiling an expression. It stores the +// type of the expression and its evaluator function. +type expr struct { + *exprInfo; + t Type; + + // Evaluate this node as the given type. + eval interface{}; + + // Map index expressions permit special forms of assignment, + // for which we need to know the Map and key. + evalMapValue func(t *Thread) (Map, interface{}); + + // Evaluate to the "address of" this value; that is, the + // settable Value object. nil for expressions whose address + // cannot be taken. + evalAddr func(t *Thread) Value; + + // Execute this expression as a statement. Only expressions + // that are valid expression statements should set this. + exec func(t *Thread); + + // If this expression is a type, this is its compiled type. + // This is only permitted in the function position of a call + // expression. In this case, t should be nil. + valType Type; + + // A short string describing this expression for error + // messages. + desc string; +} + +// exprInfo stores information needed to compile any expression node. +// Each expr also stores its exprInfo so further expressions can be +// compiled from it. +type exprInfo struct { + *compiler; + pos token.Position; +} + +func (a *exprInfo) newExpr(t Type, desc string) *expr { + return &expr{exprInfo: a, t: t, desc: desc}; +} + +func (a *exprInfo) diag(format string, args ...) { + a.diagAt(&a.pos, format, args); +} + +func (a *exprInfo) diagOpType(op token.Token, vt Type) { + a.diag("illegal operand type for '%v' operator\n\t%v", op, vt); +} + +func (a *exprInfo) diagOpTypes(op token.Token, lt Type, rt Type) { + a.diag("illegal operand types for '%v' operator\n\t%v\n\t%v", op, lt, rt); +} + +/* + * Common expression manipulations + */ + +// a.convertTo(t) converts the value of the analyzed expression a, +// which must be a constant, ideal number, to a new analyzed +// expression with a constant value of type t. +// +// TODO(austin) Rename to resolveIdeal or something? +func (a *expr) convertTo(t Type) *expr { + if !a.t.isIdeal() { + log.Crashf("attempted to convert from %v, expected ideal", a.t); + } + + var rat *bignum.Rational; + + // XXX(Spec) The spec says "It is erroneous". + // + // It is an error to assign a value with a non-zero fractional + // part to an integer, or if the assignment would overflow or + // underflow, or in general if the value cannot be represented + // by the type of the variable. + switch a.t { + case IdealFloatType: + rat = a.asIdealFloat()(); + if t.isInteger() && !rat.IsInt() { + a.diag("constant %v truncated to integer", ratToString(rat)); + return nil; + } + case IdealIntType: + i := a.asIdealInt()(); + rat = bignum.MakeRat(i, bignum.Nat(1)); + default: + log.Crashf("unexpected ideal type %v", a.t); + } + + // Check bounds + if t, ok := t.lit().(BoundedType); ok { + if rat.Cmp(t.minVal()) < 0 { + a.diag("constant %v underflows %v", ratToString(rat), t); + return nil; + } + if rat.Cmp(t.maxVal()) > 0 { + a.diag("constant %v overflows %v", ratToString(rat), t); + return nil; + } + } + + // Convert rat to type t. + res := a.newExpr(t, a.desc); + switch t := t.lit().(type) { + case *uintType: + n, d := rat.Value(); + f := n.Quo(bignum.MakeInt(false, d)); + v := f.Abs().Value(); + res.eval = func(*Thread) uint64 { return v }; + case *intType: + n, d := rat.Value(); + f := n.Quo(bignum.MakeInt(false, d)); + v := f.Value(); + res.eval = func(*Thread) int64 { return v }; + case *idealIntType: + n, d := rat.Value(); + f := n.Quo(bignum.MakeInt(false, d)); + res.eval = func() *bignum.Integer { return f }; + case *floatType: + n, d := rat.Value(); + v := float64(n.Value())/float64(d.Value()); + res.eval = func(*Thread) float64 { return v }; + case *idealFloatType: + res.eval = func() *bignum.Rational { return rat }; + default: + log.Crashf("cannot convert to type %T", t); + } + + return res; +} + +// convertToInt converts this expression to an integer, if possible, +// or produces an error if not. This accepts ideal ints, uints, and +// ints. If max is not -1, produces an error if possible if the value +// exceeds max. If negErr is not "", produces an error if possible if +// the value is negative. +func (a *expr) convertToInt(max int64, negErr string, errOp string) *expr { + switch a.t.lit().(type) { + case *idealIntType: + val := a.asIdealInt()(); + if negErr != "" && val.IsNeg() { + a.diag("negative %s: %s", negErr, val); + return nil; + } + bound := max; + if negErr == "slice" { + bound++; + } + if max != -1 && val.Cmp(bignum.Int(bound)) >= 0 { + a.diag("index %s exceeds length %d", val, max); + return nil; + } + return a.convertTo(IntType); + + case *uintType: + // Convert to int + na := a.newExpr(IntType, a.desc); + af := a.asUint(); + na.eval = func(t *Thread) int64 { + return int64(af(t)); + }; + return na; + + case *intType: + // Good as is + return a; + } + + a.diag("illegal operand type for %s\n\t%v", errOp, a.t); + return nil; +} + +// derefArray returns an expression of array type if the given +// expression is a *array type. Otherwise, returns the given +// expression. +func (a *expr) derefArray() *expr { + if pt, ok := a.t.lit().(*PtrType); ok { + if _, ok := pt.Elem.lit().(*ArrayType); ok { + deref := a.compileStarExpr(a); + if deref == nil { + log.Crashf("failed to dereference *array"); + } + return deref; + } + } + return a; +} + +/* + * Assignments + */ + +// An assignCompiler compiles assignment operations. Anything other +// than short declarations should use the compileAssign wrapper. +// +// There are three valid types of assignment: +// 1) T = T +// Assigning a single expression with single-valued type to a +// single-valued type. +// 2) MT = T, T, ... +// Assigning multiple expressions with single-valued types to a +// multi-valued type. +// 3) MT = MT +// Assigning a single expression with multi-valued type to a +// multi-valued type. +type assignCompiler struct { + *compiler; + pos token.Position; + // The RHS expressions. This may include nil's for + // expressions that failed to compile. + rs []*expr; + // The (possibly unary) MultiType of the RHS. + rmt *MultiType; + // Whether this is an unpack assignment (case 3). + isUnpack bool; + // Whether map special assignment forms are allowed. + allowMap bool; + // Whether this is a "r, ok = a[x]" assignment. + isMapUnpack bool; + // The operation name to use in error messages, such as + // "assignment" or "function call". + errOp string; + // The name to use for positions in error messages, such as + // "argument". + errPosName string; +} + +// Type check the RHS of an assignment, returning a new assignCompiler +// and indicating if the type check succeeded. This always returns an +// assignCompiler with rmt set, but if type checking fails, slots in +// the MultiType may be nil. If rs contains nil's, type checking will +// fail and these expressions given a nil type. +func (a *compiler) checkAssign(pos token.Position, rs []*expr, errOp, errPosName string) (*assignCompiler, bool) { + c := &assignCompiler{ + compiler: a, + pos: pos, + rs: rs, + errOp: errOp, + errPosName: errPosName, + }; + + // Is this an unpack? + if len(rs) == 1 && rs[0] != nil { + if rmt, isUnpack := rs[0].t.(*MultiType); isUnpack { + c.rmt = rmt; + c.isUnpack = true; + return c, true; + } + } + + // Create MultiType for RHS and check that all RHS expressions + // are single-valued. + rts := make([]Type, len(rs)); + ok := true; + for i, r := range rs { + if r == nil { + ok = false; + continue; + } + + if _, isMT := r.t.(*MultiType); isMT { + r.diag("multi-valued expression not allowed in %s", errOp); + ok = false; + continue; + } + + rts[i] = r.t; + } + + c.rmt = NewMultiType(rts); + return c, ok; +} + +func (a *assignCompiler) allowMapForms(nls int) { + a.allowMap = true; + + // Update unpacking info if this is r, ok = a[x] + if nls == 2 && len(a.rs) == 1 && a.rs[0] != nil && a.rs[0].evalMapValue != nil { + a.isUnpack = true; + a.rmt = NewMultiType([]Type {a.rs[0].t, BoolType}); + a.isMapUnpack = true; + } +} + +// compile type checks and compiles an assignment operation, returning +// a function that expects an l-value and the frame in which to +// evaluate the RHS expressions. The l-value must have exactly the +// type given by lt. Returns nil if type checking fails. +func (a *assignCompiler) compile(b *block, lt Type) (func(Value, *Thread)) { + lmt, isMT := lt.(*MultiType); + rmt, isUnpack := a.rmt, a.isUnpack; + + // Create unary MultiType for single LHS + if !isMT { + lmt = NewMultiType([]Type{lt}); + } + + // Check that the assignment count matches + lcount := len(lmt.Elems); + rcount := len(rmt.Elems); + if lcount != rcount { + msg := "not enough"; + pos := a.pos; + if rcount > lcount { + msg = "too many"; + if lcount > 0 { + pos = a.rs[lcount-1].pos; + } + } + a.diagAt(&pos, "%s %ss for %s\n\t%s\n\t%s", msg, a.errPosName, a.errOp, lt, rmt); + return nil; + } + + bad := false; + + // If this is an unpack, create a temporary to store the + // multi-value and replace the RHS with expressions to pull + // out values from the temporary. Technically, this is only + // necessary when we need to perform assignment conversions. + var effect func(*Thread); + if isUnpack { + // This leaks a slot, but is definitely safe. + temp := b.DefineTemp(a.rmt); + tempIdx := temp.Index; + if tempIdx < 0 { + panicln("tempidx", tempIdx); + } + if a.isMapUnpack { + rf := a.rs[0].evalMapValue; + vt := a.rmt.Elems[0]; + effect = func(t *Thread) { + m, k := rf(t); + v := m.Elem(t, k); + found := boolV(true); + if v == nil { + found = boolV(false); + v = vt.Zero(); + } + t.f.Vars[tempIdx] = multiV([]Value {v, &found}); + }; + } else { + rf := a.rs[0].asMulti(); + effect = func(t *Thread) { + t.f.Vars[tempIdx] = multiV(rf(t)); + }; + } + orig := a.rs[0]; + a.rs = make([]*expr, len(a.rmt.Elems)); + for i, t := range a.rmt.Elems { + if t.isIdeal() { + log.Crashf("Right side of unpack contains ideal: %s", rmt); + } + a.rs[i] = orig.newExpr(t, orig.desc); + index := i; + a.rs[i].genValue(func(t *Thread) Value { return t.f.Vars[tempIdx].(multiV)[index] }); + } + } + // Now len(a.rs) == len(a.rmt) and we've reduced any unpacking + // to multi-assignment. + + // TODO(austin) Deal with assignment special cases. + + // Values of any type may always be assigned to variables of + // compatible static type. + for i, lt := range lmt.Elems { + rt := rmt.Elems[i]; + + // When [an ideal is] (used in an expression) assigned + // to a variable or typed constant, the destination + // must be able to represent the assigned value. + if rt.isIdeal() { + a.rs[i] = a.rs[i].convertTo(lmt.Elems[i]); + if a.rs[i] == nil { + bad = true; + continue; + } + rt = a.rs[i].t; + } + + // A pointer p to an array can be assigned to a slice + // variable v with compatible element type if the type + // of p or v is unnamed. + if rpt, ok := rt.lit().(*PtrType); ok { + if at, ok := rpt.Elem.lit().(*ArrayType); ok { + if lst, ok := lt.lit().(*SliceType); ok { + if lst.Elem.compat(at.Elem, false) && (rt.lit() == Type(rt) || lt.lit() == Type(lt)) { + rf := a.rs[i].asPtr(); + a.rs[i] = a.rs[i].newExpr(lt, a.rs[i].desc); + len := at.Len; + a.rs[i].eval = func(t *Thread) Slice { + return Slice{rf(t).(ArrayValue), len, len}; + }; + rt = a.rs[i].t; + } + } + } + } + + if !lt.compat(rt, false) { + if len(a.rs) == 1 { + a.rs[0].diag("illegal operand types for %s\n\t%v\n\t%v", a.errOp, lt, rt); + } else { + a.rs[i].diag("illegal operand types in %s %d of %s\n\t%v\n\t%v", a.errPosName, i+1, a.errOp, lt, rt); + } + bad = true; + } + } + if bad { + return nil; + } + + // Compile + if !isMT { + // Case 1 + return genAssign(lt, a.rs[0]); + } + // Case 2 or 3 + as := make([]func(lv Value, t *Thread), len(a.rs)); + for i, r := range a.rs { + as[i] = genAssign(lmt.Elems[i], r); + } + return func(lv Value, t *Thread) { + if effect != nil { + effect(t); + } + lmv := lv.(multiV); + for i, a := range as { + a(lmv[i], t); + } + }; +} + +// compileAssign compiles an assignment operation without the full +// generality of an assignCompiler. See assignCompiler for a +// description of the arguments. +func (a *compiler) compileAssign(pos token.Position, b *block, lt Type, rs []*expr, errOp, errPosName string) (func(Value, *Thread)) { + ac, ok := a.checkAssign(pos, rs, errOp, errPosName); + if !ok { + return nil; + } + return ac.compile(b, lt); +} + +/* + * Expression compiler + */ + +// An exprCompiler stores information used throughout the compilation +// of a single expression. It does not embed funcCompiler because +// expressions can appear at top level. +type exprCompiler struct { + *compiler; + // The block this expression is being compiled in. + block *block; + // Whether this expression is used in a constant context. + constant bool; +} + +// compile compiles an expression AST. callCtx should be true if this +// AST is in the function position of a function call node; it allows +// the returned expression to be a type or a built-in function (which +// otherwise result in errors). +func (a *exprCompiler) compile(x ast.Expr, callCtx bool) *expr { + ei := &exprInfo{a.compiler, x.Pos()}; + + switch x := x.(type) { + // Literals + case *ast.BasicLit: + switch x.Kind { + case token.INT: + return ei.compileIntLit(string(x.Value)); + case token.FLOAT: + return ei.compileFloatLit(string(x.Value)); + case token.CHAR: + return ei.compileCharLit(string(x.Value)); + case token.STRING: + return ei.compileStringLit(string(x.Value)); + default: + log.Crashf("unexpected basic literal type %v", x.Kind); + } + + case *ast.CompositeLit: + goto notimpl; + + case *ast.FuncLit: + decl := ei.compileFuncType(a.block, x.Type); + if decl == nil { + // TODO(austin) Try compiling the body, + // perhaps with dummy argument definitions + return nil; + } + fn := ei.compileFunc(a.block, decl, x.Body); + if fn == nil { + return nil; + } + if a.constant { + a.diagAt(x, "function literal used in constant expression"); + return nil; + } + return ei.compileFuncLit(decl, fn); + + // Types + case *ast.ArrayType: + // TODO(austin) Use a multi-type case + goto typeexpr; + + case *ast.ChanType: + goto typeexpr; + + case *ast.Ellipsis: + goto typeexpr; + + case *ast.FuncType: + goto typeexpr; + + case *ast.InterfaceType: + goto typeexpr; + + case *ast.MapType: + goto typeexpr; + + // Remaining expressions + case *ast.BadExpr: + // Error already reported by parser + a.silentErrors++; + return nil; + + case *ast.BinaryExpr: + l, r := a.compile(x.X, false), a.compile(x.Y, false); + if l == nil || r == nil { + return nil; + } + return ei.compileBinaryExpr(x.Op, l, r); + + case *ast.CallExpr: + l := a.compile(x.Fun, true); + args := make([]*expr, len(x.Args)); + bad := false; + for i, arg := range x.Args { + if i == 0 && l != nil && (l.t == Type(makeType) || l.t == Type(newType)) { + argei := &exprInfo{a.compiler, arg.Pos()}; + args[i] = argei.exprFromType(a.compileType(a.block, arg)); + } else { + args[i] = a.compile(arg, false); + } + if args[i] == nil { + bad = true; + } + } + if bad || l == nil { + return nil; + } + if a.constant { + a.diagAt(x, "function call in constant context"); + return nil; + } + + if l.valType != nil { + a.diagAt(x, "type conversions not implemented"); + return nil; + } else if ft, ok := l.t.(*FuncType); ok && ft.builtin != "" { + return ei.compileBuiltinCallExpr(a.block, ft, args); + } else { + return ei.compileCallExpr(a.block, l, args); + } + + case *ast.Ident: + return ei.compileIdent(a.block, a.constant, callCtx, x.Value); + + case *ast.IndexExpr: + if x.End != nil { + arr := a.compile(x.X, false); + lo := a.compile(x.Index, false); + hi := a.compile(x.End, false); + if arr == nil || lo == nil || hi == nil { + return nil; + } + return ei.compileSliceExpr(arr, lo, hi); + } + l, r := a.compile(x.X, false), a.compile(x.Index, false); + if l == nil || r == nil { + return nil; + } + return ei.compileIndexExpr(l, r); + + case *ast.KeyValueExpr: + goto notimpl; + + case *ast.ParenExpr: + return a.compile(x.X, callCtx); + + case *ast.SelectorExpr: + v := a.compile(x.X, false); + if v == nil { + return nil; + } + return ei.compileSelectorExpr(v, x.Sel.Value); + + case *ast.StarExpr: + // We pass down our call context because this could be + // a pointer type (and thus a type conversion) + v := a.compile(x.X, callCtx); + if v == nil { + return nil; + } + if v.valType != nil { + // Turns out this was a pointer type, not a dereference + return ei.exprFromType(NewPtrType(v.valType)); + } + return ei.compileStarExpr(v); + + case *ast.StringList: + strings := make([]*expr, len(x.Strings)); + bad := false; + for i, s := range x.Strings { + strings[i] = a.compile(s, false); + if strings[i] == nil { + bad = true; + } + } + if bad { + return nil; + } + return ei.compileStringList(strings); + + case *ast.StructType: + goto notimpl; + + case *ast.TypeAssertExpr: + goto notimpl; + + case *ast.UnaryExpr: + v := a.compile(x.X, false); + if v == nil { + return nil; + } + return ei.compileUnaryExpr(x.Op, v); + } + log.Crashf("unexpected ast node type %T", x); + panic(); + +typeexpr: + if !callCtx { + a.diagAt(x, "type used as expression"); + return nil; + } + return ei.exprFromType(a.compileType(a.block, x)); + +notimpl: + a.diagAt(x, "%T expression node not implemented", x); + return nil; +} + +func (a *exprInfo) exprFromType(t Type) *expr { + if t == nil { + return nil; + } + expr := a.newExpr(nil, "type"); + expr.valType = t; + return expr; +} + +func (a *exprInfo) compileIdent(b *block, constant bool, callCtx bool, name string) *expr { + bl, level, def := b.Lookup(name); + if def == nil { + a.diag("%s: undefined", name); + return nil; + } + switch def := def.(type) { + case *Constant: + expr := a.newExpr(def.Type, "constant"); + if ft, ok := def.Type.(*FuncType); ok && ft.builtin != "" { + // XXX(Spec) I don't think anything says that + // built-in functions can't be used as values. + if !callCtx { + a.diag("built-in function %s cannot be used as a value", ft.builtin); + return nil; + } + // Otherwise, we leave the evaluators empty + // because this is handled specially + } else { + expr.genConstant(def.Value); + } + return expr; + case *Variable: + if constant { + a.diag("variable %s used in constant expression", name); + return nil; + } + if bl.global { + return a.compileGlobalVariable(def); + } + return a.compileVariable(level, def); + case Type: + if callCtx { + return a.exprFromType(def); + } + a.diag("type %v used as expression", name); + return nil; + } + log.Crashf("name %s has unknown type %T", name, def); + panic(); +} + +func (a *exprInfo) compileVariable(level int, v *Variable) *expr { + if v.Type == nil { + // Placeholder definition from an earlier error + a.silentErrors++; + return nil; + } + expr := a.newExpr(v.Type, "variable"); + expr.genIdentOp(level, v.Index); + return expr; +} + +func (a *exprInfo) compileGlobalVariable(v *Variable) *expr { + if v.Type == nil { + // Placeholder definition from an earlier error + a.silentErrors++; + return nil; + } + if v.Init == nil { + v.Init = v.Type.Zero(); + } + expr := a.newExpr(v.Type, "variable"); + val := v.Init; + expr.genValue(func(t *Thread) Value { return val }); + return expr; +} + +func (a *exprInfo) compileIdealInt(i *bignum.Integer, desc string) *expr { + expr := a.newExpr(IdealIntType, desc); + expr.eval = func() *bignum.Integer { return i }; + return expr; +} + +func (a *exprInfo) compileIntLit(lit string) *expr { + i, _, _ := bignum.IntFromString(lit, 0); + return a.compileIdealInt(i, "integer literal"); +} + +func (a *exprInfo) compileCharLit(lit string) *expr { + if lit[0] != '\'' { + // Caught by parser + a.silentErrors++; + return nil; + } + v, _, tail, err := strconv.UnquoteChar(lit[1:len(lit)], '\''); + if err != nil || tail != "'" { + // Caught by parser + a.silentErrors++; + return nil; + } + return a.compileIdealInt(bignum.Int(int64(v)), "character literal"); +} + +func (a *exprInfo) compileFloatLit(lit string) *expr { + f, _, n := bignum.RatFromString(lit, 0); + if n != len(lit) { + log.Crashf("malformed float literal %s at %v passed parser", lit, a.pos); + } + expr := a.newExpr(IdealFloatType, "float literal"); + expr.eval = func() *bignum.Rational { return f }; + return expr; +} + +func (a *exprInfo) compileString(s string) *expr { + // Ideal strings don't have a named type but they are + // compatible with type string. + + // TODO(austin) Use unnamed string type. + expr := a.newExpr(StringType, "string literal"); + expr.eval = func(*Thread) string { return s }; + return expr; +} + +func (a *exprInfo) compileStringLit(lit string) *expr { + s, err := strconv.Unquote(lit); + if err != nil { + a.diag("illegal string literal, %v", err); + return nil; + } + return a.compileString(s); +} + +func (a *exprInfo) compileStringList(list []*expr) *expr { + ss := make([]string, len(list)); + for i, s := range list { + ss[i] = s.asString()(nil); + } + return a.compileString(strings.Join(ss, "")); +} + +func (a *exprInfo) compileFuncLit(decl *FuncDecl, fn func(*Thread) Func) *expr { + expr := a.newExpr(decl.Type, "function literal"); + expr.eval = fn; + return expr; +} + +func (a *exprInfo) compileSelectorExpr(v *expr, name string) *expr { + // mark marks a field that matches the selector name. It + // tracks the best depth found so far and whether more than + // one field has been found at that depth. + bestDepth := -1; + ambig := false; + amberr := ""; + mark := func(depth int, pathName string) { + switch { + case bestDepth == -1 || depth < bestDepth: + bestDepth = depth; + ambig = false; + amberr = ""; + + case depth == bestDepth: + ambig = true; + + default: + log.Crashf("Marked field at depth %d, but already found one at depth %d", depth, bestDepth); + } + amberr += "\n\t" + pathName[1:len(pathName)]; + }; + + visited := make(map[Type] bool); + + // find recursively searches for the named field, starting at + // type t. If it finds the named field, it returns a function + // which takes an expr that represents a value of type 't' and + // returns an expr that retrieves the named field. We delay + // expr construction to avoid producing lots of useless expr's + // as we search. + // + // TODO(austin) Now that the expression compiler works on + // semantic values instead of AST's, there should be a much + // better way of doing this. + var find func(Type, int, string) (func (*expr) *expr); + find = func(t Type, depth int, pathName string) (func (*expr) *expr) { + // Don't bother looking if we've found something shallower + if bestDepth != -1 && bestDepth < depth { + return nil; + } + + // Don't check the same type twice and avoid loops + if _, ok := visited[t]; ok { + return nil; + } + visited[t] = true; + + // Implicit dereference + deref := false; + if ti, ok := t.(*PtrType); ok { + deref = true; + t = ti.Elem; + } + + // If it's a named type, look for methods + if ti, ok := t.(*NamedType); ok { + _, ok := ti.methods[name]; + if ok { + mark(depth, pathName + "." + name); + log.Crash("Methods not implemented"); + } + t = ti.Def; + } + + // If it's a struct type, check fields and embedded types + var builder func(*expr) *expr; + if t, ok := t.(*StructType); ok { + for i, f := range t.Elems { + var sub func(*expr) *expr; + switch { + case f.Name == name: + mark(depth, pathName + "." + name); + sub = func(e *expr) *expr { return e }; + + case f.Anonymous: + sub = find(f.Type, depth+1, pathName + "." + f.Name); + if sub == nil { + continue; + } + + default: + continue; + } + + // We found something. Create a + // builder for accessing this field. + ft := f.Type; + index := i; + builder = func(parent *expr) *expr { + if deref { + parent = a.compileStarExpr(parent); + } + expr := a.newExpr(ft, "selector expression"); + pf := parent.asStruct(); + evalAddr := func(t *Thread) Value { + return pf(t).Field(t, index); + }; + expr.genValue(evalAddr); + return sub(expr); + }; + } + } + + return builder; + }; + + builder := find(v.t, 0, ""); + if builder == nil { + a.diag("type %v has no field or method %s", v.t, name); + return nil; + } + if ambig { + a.diag("field %s is ambiguous in type %v%s", name, v.t, amberr); + return nil; + } + + return builder(v); +} + +func (a *exprInfo) compileSliceExpr(arr, lo, hi *expr) *expr { + // Type check object + arr = arr.derefArray(); + + var at Type; + var maxIndex int64 = -1; + + switch lt := arr.t.lit().(type) { + case *ArrayType: + at = NewSliceType(lt.Elem); + maxIndex = lt.Len; + + case *SliceType: + at = lt; + + case *stringType: + at = lt; + + default: + a.diag("cannot slice %v", arr.t); + return nil; + } + + // Type check index and convert to int + // XXX(Spec) It's unclear if ideal floats with no + // fractional part are allowed here. 6g allows it. I + // believe that's wrong. + lo = lo.convertToInt(maxIndex, "slice", "slice"); + hi = hi.convertToInt(maxIndex, "slice", "slice"); + if lo == nil || hi == nil { + return nil; + } + + expr := a.newExpr(at, "slice expression"); + + // Compile + lof := lo.asInt(); + hif := hi.asInt(); + switch lt := arr.t.lit().(type) { + case *ArrayType: + arrf := arr.asArray(); + bound := lt.Len; + expr.eval = func(t *Thread) Slice { + arr, lo, hi := arrf(t), lof(t), hif(t); + if lo > hi || hi > bound || lo < 0 { + t.Abort(SliceError{lo, hi, bound}); + } + return Slice{arr.Sub(lo, bound - lo), hi - lo, bound - lo} + }; + + case *SliceType: + arrf := arr.asSlice(); + expr.eval = func(t *Thread) Slice { + arr, lo, hi := arrf(t), lof(t), hif(t); + if lo > hi || hi > arr.Cap || lo < 0 { + t.Abort(SliceError{lo, hi, arr.Cap}); + } + return Slice{arr.Base.Sub(lo, arr.Cap - lo), hi - lo, arr.Cap - lo} + }; + + case *stringType: + arrf := arr.asString(); + // TODO(austin) This pulls over the whole string in a + // remote setting, instead of creating a substring backed + // by remote memory. + expr.eval = func(t *Thread) string { + arr, lo, hi := arrf(t), lof(t), hif(t); + if lo > hi || hi > int64(len(arr)) || lo < 0 { + t.Abort(SliceError{lo, hi, int64(len(arr))}); + } + return arr[lo:hi]; + } + + default: + log.Crashf("unexpected left operand type %T", arr.t.lit()); + } + + return expr; +} + +func (a *exprInfo) compileIndexExpr(l, r *expr) *expr { + // Type check object + l = l.derefArray(); + + var at Type; + intIndex := false; + var maxIndex int64 = -1; + + switch lt := l.t.lit().(type) { + case *ArrayType: + at = lt.Elem; + intIndex = true; + maxIndex = lt.Len; + + case *SliceType: + at = lt.Elem; + intIndex = true; + + case *stringType: + at = Uint8Type; + intIndex = true; + + case *MapType: + at = lt.Elem; + if r.t.isIdeal() { + r = r.convertTo(lt.Key); + if r == nil { + return nil; + } + } + if !lt.Key.compat(r.t, false) { + a.diag("cannot use %s as index into %s", r.t, lt); + return nil; + } + + default: + a.diag("cannot index into %v", l.t); + return nil; + } + + // Type check index and convert to int if necessary + if intIndex { + // XXX(Spec) It's unclear if ideal floats with no + // fractional part are allowed here. 6g allows it. I + // believe that's wrong. + r = r.convertToInt(maxIndex, "index", "index"); + if r == nil { + return nil; + } + } + + expr := a.newExpr(at, "index expression"); + + // Compile + switch lt := l.t.lit().(type) { + case *ArrayType: + lf := l.asArray(); + rf := r.asInt(); + bound := lt.Len; + expr.genValue(func(t *Thread) Value { + l, r := lf(t), rf(t); + if r < 0 || r >= bound { + t.Abort(IndexError{r, bound}); + } + return l.Elem(t, r); + }); + + case *SliceType: + lf := l.asSlice(); + rf := r.asInt(); + expr.genValue(func(t *Thread) Value { + l, r := lf(t), rf(t); + if l.Base == nil { + t.Abort(NilPointerError{}); + } + if r < 0 || r >= l.Len { + t.Abort(IndexError{r, l.Len}); + } + return l.Base.Elem(t, r); + }); + + case *stringType: + lf := l.asString(); + rf := r.asInt(); + // TODO(austin) This pulls over the whole string in a + // remote setting, instead of just the one character. + expr.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + if r < 0 || r >= int64(len(l)) { + t.Abort(IndexError{r, int64(len(l))}); + } + return uint64(l[r]); + } + + case *MapType: + lf := l.asMap(); + rf := r.asInterface(); + expr.genValue(func(t *Thread) Value { + m := lf(t); + k := rf(t); + if m == nil { + t.Abort(NilPointerError{}); + } + e := m.Elem(t, k); + if e == nil { + t.Abort(KeyError{k}); + } + return e; + }); + // genValue makes things addressable, but map values + // aren't addressable. + expr.evalAddr = nil; + expr.evalMapValue = func(t *Thread) (Map, interface{}) { + // TODO(austin) Key check? nil check? + return lf(t), rf(t); + }; + + default: + log.Crashf("unexpected left operand type %T", l.t.lit()); + } + + return expr; +} + +func (a *exprInfo) compileCallExpr(b *block, l *expr, as []*expr) *expr { + // TODO(austin) Variadic functions. + + // Type check + + // XXX(Spec) Calling a named function type is okay. I really + // think there needs to be a general discussion of named + // types. A named type creates a new, distinct type, but the + // type of that type is still whatever it's defined to. Thus, + // in "type Foo int", Foo is still an integer type and in + // "type Foo func()", Foo is a function type. + lt, ok := l.t.lit().(*FuncType); + if !ok { + a.diag("cannot call non-function type %v", l.t); + return nil; + } + + // The arguments must be single-valued expressions assignment + // compatible with the parameters of F. + // + // XXX(Spec) The spec is wrong. It can also be a single + // multi-valued expression. + nin := len(lt.In); + assign := a.compileAssign(a.pos, b, NewMultiType(lt.In), as, "function call", "argument"); + if assign == nil { + return nil; + } + + var t Type; + nout := len(lt.Out); + switch nout { + case 0: + t = EmptyType; + case 1: + t = lt.Out[0]; + default: + t = NewMultiType(lt.Out); + } + expr := a.newExpr(t, "function call"); + + // Gather argument and out types to initialize frame variables + vts := make([]Type, nin + nout); + for i, t := range lt.In { + vts[i] = t; + } + for i, t := range lt.Out { + vts[i+nin] = t; + } + + // Compile + lf := l.asFunc(); + call := func(t *Thread) []Value { + fun := lf(t); + fr := fun.NewFrame(); + for i, t := range vts { + fr.Vars[i] = t.Zero(); + } + assign(multiV(fr.Vars[0:nin]), t); + oldf := t.f; + t.f = fr; + fun.Call(t); + t.f = oldf; + return fr.Vars[nin:nin+nout]; + }; + expr.genFuncCall(call); + + return expr; +} + +func (a *exprInfo) compileBuiltinCallExpr(b *block, ft *FuncType, as []*expr) *expr { + checkCount := func(min, max int) bool { + if len(as) < min { + a.diag("not enough arguments to %s", ft.builtin); + return false; + } else if len(as) > max { + a.diag("too many arguments to %s", ft.builtin); + return false; + } + return true; + }; + + switch ft { + case capType: + if !checkCount(1, 1) { + return nil; + } + arg := as[0].derefArray(); + expr := a.newExpr(IntType, "function call"); + switch t := arg.t.lit().(type) { + case *ArrayType: + // TODO(austin) It would be nice if this could + // be a constant int. + v := t.Len; + expr.eval = func(t *Thread) int64 { + return v; + }; + + case *SliceType: + vf := arg.asSlice(); + expr.eval = func(t *Thread) int64 { + return vf(t).Cap; + }; + + //case *ChanType: + + default: + a.diag("illegal argument type for cap function\n\t%v", arg.t); + return nil; + } + return expr; + + case lenType: + if !checkCount(1, 1) { + return nil; + } + arg := as[0].derefArray(); + expr := a.newExpr(IntType, "function call"); + switch t := arg.t.lit().(type) { + case *stringType: + vf := arg.asString(); + expr.eval = func(t *Thread) int64 { + return int64(len(vf(t))); + }; + + case *ArrayType: + // TODO(austin) It would be nice if this could + // be a constant int. + v := t.Len; + expr.eval = func(t *Thread) int64 { + return v; + }; + + case *SliceType: + vf := arg.asSlice(); + expr.eval = func(t *Thread) int64 { + return vf(t).Len; + }; + + case *MapType: + vf := arg.asMap(); + expr.eval = func(t *Thread) int64 { + // XXX(Spec) What's the len of an + // uninitialized map? + m := vf(t); + if m == nil { + return 0; + } + return m.Len(t); + }; + + //case *ChanType: + + default: + a.diag("illegal argument type for len function\n\t%v", arg.t); + return nil; + } + return expr; + + case makeType: + if !checkCount(1, 3) { + return nil; + } + // XXX(Spec) What are the types of the + // arguments? Do they have to be ints? 6g + // accepts any integral type. + var lenexpr, capexpr *expr; + var lenf, capf func(*Thread) int64; + if len(as) > 1 { + lenexpr = as[1].convertToInt(-1, "length", "make function"); + if lenexpr == nil { + return nil; + } + lenf = lenexpr.asInt(); + } + if len(as) > 2 { + capexpr = as[2].convertToInt(-1, "capacity", "make function"); + if capexpr == nil { + return nil; + } + capf = capexpr.asInt(); + } + + switch t := as[0].valType.lit().(type) { + case *SliceType: + // A new, initialized slice value for a given + // element type T is made using the built-in + // function make, which takes a slice type and + // parameters specifying the length and + // optionally the capacity. + if !checkCount(2, 3) { + return nil; + } + et := t.Elem; + expr := a.newExpr(t, "function call"); + expr.eval = func(t *Thread) Slice { + l := lenf(t); + // XXX(Spec) What if len or cap is + // negative? The runtime panics. + if l < 0 { + t.Abort(NegativeLengthError{l}); + } + c := l; + if capf != nil { + c = capf(t); + if c < 0 { + t.Abort(NegativeCapacityError{c}); + } + // XXX(Spec) What happens if + // len > cap? The runtime + // sets cap to len. + if l > c { + c = l; + } + } + base := arrayV(make([]Value, c)); + for i := int64(0); i < c; i++ { + base[i] = et.Zero(); + } + return Slice{&base, l, c}; + }; + return expr; + + case *MapType: + // A new, empty map value is made using the + // built-in function make, which takes the map + // type and an optional capacity hint as + // arguments. + if !checkCount(1, 2) { + return nil; + } + expr := a.newExpr(t, "function call"); + expr.eval = func(t *Thread) Map { + if lenf == nil { + return make(evalMap); + } + l := lenf(t); + return make(evalMap, l); + }; + return expr; + + //case *ChanType: + + default: + a.diag("illegal argument type for make function\n\t%v", as[0].valType); + return nil; + } + + case closeType, closedType: + a.diag("built-in function %s not implemented", ft.builtin); + return nil; + + case newType: + if !checkCount(1, 1) { + return nil; + } + + t := as[0].valType; + expr := a.newExpr(NewPtrType(t), "new"); + expr.eval = func(*Thread) Value { + return t.Zero(); + }; + return expr; + + case panicType, paniclnType, printType, printlnType: + evals := make([]func(*Thread)interface{}, len(as)); + for i, x := range as { + evals[i] = x.asInterface(); + } + spaces := ft == paniclnType || ft == printlnType; + newline := ft != printType; + printer := func(t *Thread) { + for i, eval := range evals { + if i > 0 && spaces { + print(" "); + } + v := eval(t); + type stringer interface { String() string } + switch v1 := v.(type) { + case bool: + print(v1); + case uint64: + print(v1); + case int64: + print(v1); + case float64: + print(v1); + case string: + print(v1); + case stringer: + print(v1.String()); + default: + print("???"); + } + } + if newline { + print("\n"); + } + }; + expr := a.newExpr(EmptyType, "print"); + expr.exec = printer; + if ft == panicType || ft == paniclnType { + expr.exec = func(t *Thread) { + printer(t); + t.Abort(os.NewError("panic")); + } + } + return expr; + } + + log.Crashf("unexpected built-in function '%s'", ft.builtin); + panic(); +} + +func (a *exprInfo) compileStarExpr(v *expr) *expr { + switch vt := v.t.lit().(type) { + case *PtrType: + expr := a.newExpr(vt.Elem, "indirect expression"); + vf := v.asPtr(); + expr.genValue(func(t *Thread) Value { + v := vf(t); + if v == nil { + t.Abort(NilPointerError{}); + } + return v; + }); + return expr; + } + + a.diagOpType(token.MUL, v.t); + return nil; +} + +var unaryOpDescs = make(map[token.Token] string) + +func (a *exprInfo) compileUnaryExpr(op token.Token, v *expr) *expr { + // Type check + var t Type; + switch op { + case token.ADD, token.SUB: + if !v.t.isInteger() && !v.t.isFloat() { + a.diagOpType(op, v.t); + return nil; + } + t = v.t; + + case token.NOT: + if !v.t.isBoolean() { + a.diagOpType(op, v.t); + return nil; + } + t = BoolType; + + case token.XOR: + if !v.t.isInteger() { + a.diagOpType(op, v.t); + return nil; + } + t = v.t; + + case token.AND: + // The unary prefix address-of operator & generates + // the address of its operand, which must be a + // variable, pointer indirection, field selector, or + // array or slice indexing operation. + if v.evalAddr == nil { + a.diag("cannot take the address of %s", v.desc); + return nil; + } + + // TODO(austin) Implement "It is illegal to take the + // address of a function result variable" once I have + // function result variables. + + t = NewPtrType(v.t); + + case token.ARROW: + log.Crashf("Unary op %v not implemented", op); + + default: + log.Crashf("unknown unary operator %v", op); + } + + desc, ok := unaryOpDescs[op]; + if !ok { + desc = "unary " + op.String() + " expression"; + unaryOpDescs[op] = desc; + } + + // Compile + expr := a.newExpr(t, desc); + switch op { + case token.ADD: + // Just compile it out + expr = v; + expr.desc = desc; + + case token.SUB: + expr.genUnaryOpNeg(v); + + case token.NOT: + expr.genUnaryOpNot(v); + + case token.XOR: + expr.genUnaryOpXor(v); + + case token.AND: + vf := v.evalAddr; + expr.eval = func(t *Thread) Value { return vf(t) }; + + default: + log.Crashf("Compilation of unary op %v not implemented", op); + } + + return expr; +} + +var binOpDescs = make(map[token.Token] string) + +func (a *exprInfo) compileBinaryExpr(op token.Token, l, r *expr) *expr { + // Save the original types of l.t and r.t for error messages. + origlt := l.t; + origrt := r.t; + + // XXX(Spec) What is the exact definition of a "named type"? + + // XXX(Spec) Arithmetic operators: "Integer types" apparently + // means all types compatible with basic integer types, though + // this is never explained. Likewise for float types, etc. + // This relates to the missing explanation of named types. + + // XXX(Spec) Operators: "If both operands are ideal numbers, + // the conversion is to ideal floats if one of the operands is + // an ideal float (relevant for / and %)." How is that + // relevant only for / and %? If I add an ideal int and an + // ideal float, I get an ideal float. + + if op != token.SHL && op != token.SHR { + // Except in shift expressions, if one operand has + // numeric type and the other operand is an ideal + // number, the ideal number is converted to match the + // type of the other operand. + if (l.t.isInteger() || l.t.isFloat()) && !l.t.isIdeal() && r.t.isIdeal() { + r = r.convertTo(l.t); + } else if (r.t.isInteger() || r.t.isFloat()) && !r.t.isIdeal() && l.t.isIdeal() { + l = l.convertTo(r.t); + } + if l == nil || r == nil { + return nil; + } + + // Except in shift expressions, if both operands are + // ideal numbers and one is an ideal float, the other + // is converted to ideal float. + if l.t.isIdeal() && r.t.isIdeal() { + if l.t.isInteger() && r.t.isFloat() { + l = l.convertTo(r.t); + } else if l.t.isFloat() && r.t.isInteger() { + r = r.convertTo(l.t); + } + if l == nil || r == nil { + return nil; + } + } + } + + // Useful type predicates + // TODO(austin) CL 33668 mandates identical types except for comparisons. + compat := func() bool { + return l.t.compat(r.t, false); + }; + integers := func() bool { + return l.t.isInteger() && r.t.isInteger(); + }; + floats := func() bool { + return l.t.isFloat() && r.t.isFloat(); + }; + strings := func() bool { + // TODO(austin) Deal with named types + return l.t == StringType && r.t == StringType; + }; + booleans := func() bool { + return l.t.isBoolean() && r.t.isBoolean(); + }; + + // Type check + var t Type; + switch op { + case token.ADD: + if !compat() || (!integers() && !floats() && !strings()) { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + t = l.t; + + case token.SUB, token.MUL, token.QUO: + if !compat() || (!integers() && !floats()) { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + t = l.t; + + case token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: + if !compat() || !integers() { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + t = l.t; + + case token.SHL, token.SHR: + // XXX(Spec) Is it okay for the right operand to be an + // ideal float with no fractional part? "The right + // operand in a shift operation must be always be of + // unsigned integer type or an ideal number that can + // be safely converted into an unsigned integer type + // (ยงArithmetic operators)" suggests so and 6g agrees. + + if !l.t.isInteger() || !(r.t.isInteger() || r.t.isIdeal()) { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + + // The right operand in a shift operation must be + // always be of unsigned integer type or an ideal + // number that can be safely converted into an + // unsigned integer type. + if r.t.isIdeal() { + r2 := r.convertTo(UintType); + if r2 == nil { + return nil; + } + + // If the left operand is not ideal, convert + // the right to not ideal. + if !l.t.isIdeal() { + r = r2; + } + + // If both are ideal, but the right side isn't + // an ideal int, convert it to simplify things. + if l.t.isIdeal() && !r.t.isInteger() { + r = r.convertTo(IdealIntType); + if r == nil { + log.Crashf("conversion to uintType succeeded, but conversion to idealIntType failed"); + } + } + } else if _, ok := r.t.lit().(*uintType); !ok { + a.diag("right operand of shift must be unsigned"); + return nil; + } + + if l.t.isIdeal() && !r.t.isIdeal() { + // XXX(Spec) What is the meaning of "ideal >> + // non-ideal"? Russ says the ideal should be + // converted to an int. 6g propagates the + // type down from assignments as a hint. + + l = l.convertTo(IntType); + if l == nil { + return nil; + } + } + + // At this point, we should have one of three cases: + // 1) uint SHIFT uint + // 2) int SHIFT uint + // 3) ideal int SHIFT ideal int + + t = l.t; + + case token.LOR, token.LAND: + if !booleans() { + return nil; + } + // XXX(Spec) There's no mention of *which* boolean + // type the logical operators return. From poking at + // 6g, it appears to be the named boolean type, NOT + // the type of the left operand, and NOT an unnamed + // boolean type. + + t = BoolType; + + case token.ARROW: + // The operands in channel sends differ in type: one + // is always a channel and the other is a variable or + // value of the channel's element type. + log.Crash("Binary op <- not implemented"); + t = BoolType; + + case token.LSS, token.GTR, token.LEQ, token.GEQ: + // XXX(Spec) It's really unclear what types which + // comparison operators apply to. I feel like the + // text is trying to paint a Venn diagram for me, + // which it's really pretty simple: <, <=, >, >= apply + // only to numeric types and strings. == and != apply + // to everything except arrays and structs, and there + // are some restrictions on when it applies to slices. + + if !compat() || (!integers() && !floats() && !strings()) { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + t = BoolType; + + case token.EQL, token.NEQ: + // XXX(Spec) The rules for type checking comparison + // operators are spread across three places that all + // partially overlap with each other: the Comparison + // Compatibility section, the Operators section, and + // the Comparison Operators section. The Operators + // section should just say that operators require + // identical types (as it does currently) except that + // there a few special cases for comparison, which are + // described in section X. Currently it includes just + // one of the four special cases. The Comparison + // Compatibility section and the Comparison Operators + // section should either be merged, or at least the + // Comparison Compatibility section should be + // exclusively about type checking and the Comparison + // Operators section should be exclusively about + // semantics. + + // XXX(Spec) Comparison operators: "All comparison + // operators apply to basic types except bools." This + // is very difficult to parse. It's explained much + // better in the Comparison Compatibility section. + + // XXX(Spec) Comparison compatibility: "Function + // values are equal if they refer to the same + // function." is rather vague. It should probably be + // similar to the way the rule for map values is + // written: Function values are equal if they were + // created by the same execution of a function literal + // or refer to the same function declaration. This is + // *almost* but not quite waht 6g implements. If a + // function literals does not capture any variables, + // then multiple executions of it will result in the + // same closure. Russ says he'll change that. + + // TODO(austin) Deal with remaining special cases + + if !compat() { + a.diagOpTypes(op, origlt, origrt); + return nil; + } + // Arrays and structs may not be compared to anything. + switch l.t.(type) { + case *ArrayType, *StructType: + a.diagOpTypes(op, origlt, origrt); + return nil; + } + t = BoolType; + + default: + log.Crashf("unknown binary operator %v", op); + } + + desc, ok := binOpDescs[op]; + if !ok { + desc = op.String() + " expression"; + binOpDescs[op] = desc; + } + + // Check for ideal divide by zero + switch op { + case token.QUO, token.REM: + if r.t.isIdeal() { + if (r.t.isInteger() && r.asIdealInt()().IsZero()) || + (r.t.isFloat() && r.asIdealFloat()().IsZero()) { + a.diag("divide by zero"); + return nil; + } + } + } + + // Compile + expr := a.newExpr(t, desc); + switch op { + case token.ADD: + expr.genBinOpAdd(l, r); + + case token.SUB: + expr.genBinOpSub(l, r); + + case token.MUL: + expr.genBinOpMul(l, r); + + case token.QUO: + expr.genBinOpQuo(l, r); + + case token.REM: + expr.genBinOpRem(l, r); + + case token.AND: + expr.genBinOpAnd(l, r); + + case token.OR: + expr.genBinOpOr(l, r); + + case token.XOR: + expr.genBinOpXor(l, r); + + case token.AND_NOT: + expr.genBinOpAndNot(l, r); + + case token.SHL: + if l.t.isIdeal() { + lv := l.asIdealInt()(); + rv := r.asIdealInt()(); + const maxShift = 99999; + if rv.Cmp(bignum.Int(maxShift)) > 0 { + a.diag("left shift by %v; exceeds implementation limit of %v", rv, maxShift); + expr.t = nil; + return nil; + } + val := lv.Shl(uint(rv.Value())); + expr.eval = func() *bignum.Integer { return val }; + } else { + expr.genBinOpShl(l, r); + } + + case token.SHR: + if l.t.isIdeal() { + lv := l.asIdealInt()(); + rv := r.asIdealInt()(); + val := lv.Shr(uint(rv.Value())); + expr.eval = func() *bignum.Integer { return val }; + } else { + expr.genBinOpShr(l, r); + } + + case token.LSS: + expr.genBinOpLss(l, r); + + case token.GTR: + expr.genBinOpGtr(l, r); + + case token.LEQ: + expr.genBinOpLeq(l, r); + + case token.GEQ: + expr.genBinOpGeq(l, r); + + case token.EQL: + expr.genBinOpEql(l, r); + + case token.NEQ: + expr.genBinOpNeq(l, r); + + case token.LAND: + expr.genBinOpLogAnd(l, r); + + case token.LOR: + expr.genBinOpLogOr(l, r); + + default: + log.Crashf("Compilation of binary op %v not implemented", op); + } + + return expr; +} + +// TODO(austin) This is a hack to eliminate a circular dependency +// between type.go and expr.go +func (a *compiler) compileArrayLen(b *block, expr ast.Expr) (int64, bool) { + lenExpr := a.compileExpr(b, true, expr); + if lenExpr == nil { + return 0, false; + } + + // XXX(Spec) Are ideal floats with no fractional part okay? + if lenExpr.t.isIdeal() { + lenExpr = lenExpr.convertTo(IntType); + if lenExpr == nil { + return 0, false; + } + } + + if !lenExpr.t.isInteger() { + a.diagAt(expr, "array size must be an integer"); + return 0, false; + } + + switch lenExpr.t.lit().(type) { + case *intType: + return lenExpr.asInt()(nil), true; + case *uintType: + return int64(lenExpr.asUint()(nil)), true; + } + log.Crashf("unexpected integer type %T", lenExpr.t); + return 0, false; +} + +func (a *compiler) compileExpr(b *block, constant bool, expr ast.Expr) *expr { + ec := &exprCompiler{a, b, constant}; + nerr := a.numError(); + e := ec.compile(expr, false); + if e == nil && nerr == a.numError() { + log.Crashf("expression compilation failed without reporting errors"); + } + return e; +} + +// extractEffect separates out any effects that the expression may +// have, returning a function that will perform those effects and a +// new exprCompiler that is guaranteed to be side-effect free. These +// are the moral equivalents of "temp := expr" and "temp" (or "temp := +// &expr" and "*temp" for addressable exprs). Because this creates a +// temporary variable, the caller should create a temporary block for +// the compilation of this expression and the evaluation of the +// results. +func (a *expr) extractEffect(b *block, errOp string) (func(*Thread), *expr) { + // Create "&a" if a is addressable + rhs := a; + if a.evalAddr != nil { + rhs = a.compileUnaryExpr(token.AND, rhs); + } + + // Create temp + ac, ok := a.checkAssign(a.pos, []*expr{rhs}, errOp, ""); + if !ok { + return nil, nil; + } + if len(ac.rmt.Elems) != 1 { + a.diag("multi-valued expression not allowed in %s", errOp); + return nil, nil; + } + tempType := ac.rmt.Elems[0]; + if tempType.isIdeal() { + // It's too bad we have to duplicate this rule. + switch { + case tempType.isInteger(): + tempType = IntType; + case tempType.isFloat(): + tempType = FloatType; + default: + log.Crashf("unexpected ideal type %v", tempType); + } + } + temp := b.DefineTemp(tempType); + tempIdx := temp.Index; + + // Create "temp := rhs" + assign := ac.compile(b, tempType); + if assign == nil { + log.Crashf("compileAssign type check failed"); + } + + effect := func(t *Thread) { + tempVal := tempType.Zero(); + t.f.Vars[tempIdx] = tempVal; + assign(tempVal, t); + }; + + // Generate "temp" or "*temp" + getTemp := a.compileVariable(0, temp); + if a.evalAddr == nil { + return effect, getTemp; + } + + deref := a.compileStarExpr(getTemp); + if deref == nil { + return nil, nil; + } + return effect, deref; +} diff --git a/src/pkg/exp/eval/expr1.go b/src/pkg/exp/eval/expr1.go new file mode 100644 index 000000000..7787a2112 --- /dev/null +++ b/src/pkg/exp/eval/expr1.go @@ -0,0 +1,1836 @@ +// This file is machine generated by gen.go. +// 6g gen.go && 6l gen.6 && ./6.out >expr1.go + +package eval + +import ( + "bignum"; + "log"; +) + +/* + * "As" functions. These retrieve evaluator functions from an + * expr, panicking if the requested evaluator has the wrong type. + */ +func (a *expr) asBool() (func(*Thread) bool) { + return a.eval.(func(*Thread)(bool)) +} +func (a *expr) asUint() (func(*Thread) uint64) { + return a.eval.(func(*Thread)(uint64)) +} +func (a *expr) asInt() (func(*Thread) int64) { + return a.eval.(func(*Thread)(int64)) +} +func (a *expr) asIdealInt() (func() *bignum.Integer) { + return a.eval.(func()(*bignum.Integer)) +} +func (a *expr) asFloat() (func(*Thread) float64) { + return a.eval.(func(*Thread)(float64)) +} +func (a *expr) asIdealFloat() (func() *bignum.Rational) { + return a.eval.(func()(*bignum.Rational)) +} +func (a *expr) asString() (func(*Thread) string) { + return a.eval.(func(*Thread)(string)) +} +func (a *expr) asArray() (func(*Thread) ArrayValue) { + return a.eval.(func(*Thread)(ArrayValue)) +} +func (a *expr) asStruct() (func(*Thread) StructValue) { + return a.eval.(func(*Thread)(StructValue)) +} +func (a *expr) asPtr() (func(*Thread) Value) { + return a.eval.(func(*Thread)(Value)) +} +func (a *expr) asFunc() (func(*Thread) Func) { + return a.eval.(func(*Thread)(Func)) +} +func (a *expr) asSlice() (func(*Thread) Slice) { + return a.eval.(func(*Thread)(Slice)) +} +func (a *expr) asMap() (func(*Thread) Map) { + return a.eval.(func(*Thread)(Map)) +} +func (a *expr) asMulti() (func(*Thread) []Value) { + return a.eval.(func(*Thread)[]Value) +} + +func (a *expr) asInterface() (func(*Thread) interface{}) { + switch sf := a.eval.(type) { + case func(t *Thread)bool: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)uint64: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)int64: + return func(t *Thread) interface{} { return sf(t) } + case func()*bignum.Integer: + return func(*Thread) interface{} { return sf() } + case func(t *Thread)float64: + return func(t *Thread) interface{} { return sf(t) } + case func()*bignum.Rational: + return func(*Thread) interface{} { return sf() } + case func(t *Thread)string: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)ArrayValue: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)StructValue: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)Value: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)Func: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)Slice: + return func(t *Thread) interface{} { return sf(t) } + case func(t *Thread)Map: + return func(t *Thread) interface{} { return sf(t) } + default: + log.Crashf("unexpected expression node type %T at %v", a.eval, a.pos); + } + panic(); +} + +/* + * Operator generators. + */ + +func (a *expr) genConstant(v Value) { + switch a.t.lit().(type) { + case *boolType: + a.eval = func(t *Thread) bool { return v.(BoolValue).Get(t) } + case *uintType: + a.eval = func(t *Thread) uint64 { return v.(UintValue).Get(t) } + case *intType: + a.eval = func(t *Thread) int64 { return v.(IntValue).Get(t) } + case *idealIntType: + val := v.(IdealIntValue).Get(); + a.eval = func() *bignum.Integer { return val } + case *floatType: + a.eval = func(t *Thread) float64 { return v.(FloatValue).Get(t) } + case *idealFloatType: + val := v.(IdealFloatValue).Get(); + a.eval = func() *bignum.Rational { return val } + case *stringType: + a.eval = func(t *Thread) string { return v.(StringValue).Get(t) } + case *ArrayType: + a.eval = func(t *Thread) ArrayValue { return v.(ArrayValue).Get(t) } + case *StructType: + a.eval = func(t *Thread) StructValue { return v.(StructValue).Get(t) } + case *PtrType: + a.eval = func(t *Thread) Value { return v.(PtrValue).Get(t) } + case *FuncType: + a.eval = func(t *Thread) Func { return v.(FuncValue).Get(t) } + case *SliceType: + a.eval = func(t *Thread) Slice { return v.(SliceValue).Get(t) } + case *MapType: + a.eval = func(t *Thread) Map { return v.(MapValue).Get(t) } + default: + log.Crashf("unexpected constant type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genIdentOp(level, index int) { + a.evalAddr = func(t *Thread) Value { return t.f.Get(level, index) }; + switch a.t.lit().(type) { + case *boolType: + a.eval = func(t *Thread) bool { return t.f.Get(level, index).(BoolValue).Get(t) } + case *uintType: + a.eval = func(t *Thread) uint64 { return t.f.Get(level, index).(UintValue).Get(t) } + case *intType: + a.eval = func(t *Thread) int64 { return t.f.Get(level, index).(IntValue).Get(t) } + case *floatType: + a.eval = func(t *Thread) float64 { return t.f.Get(level, index).(FloatValue).Get(t) } + case *stringType: + a.eval = func(t *Thread) string { return t.f.Get(level, index).(StringValue).Get(t) } + case *ArrayType: + a.eval = func(t *Thread) ArrayValue { return t.f.Get(level, index).(ArrayValue).Get(t) } + case *StructType: + a.eval = func(t *Thread) StructValue { return t.f.Get(level, index).(StructValue).Get(t) } + case *PtrType: + a.eval = func(t *Thread) Value { return t.f.Get(level, index).(PtrValue).Get(t) } + case *FuncType: + a.eval = func(t *Thread) Func { return t.f.Get(level, index).(FuncValue).Get(t) } + case *SliceType: + a.eval = func(t *Thread) Slice { return t.f.Get(level, index).(SliceValue).Get(t) } + case *MapType: + a.eval = func(t *Thread) Map { return t.f.Get(level, index).(MapValue).Get(t) } + default: + log.Crashf("unexpected identifier type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genFuncCall(call func(t *Thread) []Value) { + a.exec = func(t *Thread) { call(t)}; + switch a.t.lit().(type) { + case *boolType: + a.eval = func(t *Thread) bool { return call(t)[0].(BoolValue).Get(t) } + case *uintType: + a.eval = func(t *Thread) uint64 { return call(t)[0].(UintValue).Get(t) } + case *intType: + a.eval = func(t *Thread) int64 { return call(t)[0].(IntValue).Get(t) } + case *floatType: + a.eval = func(t *Thread) float64 { return call(t)[0].(FloatValue).Get(t) } + case *stringType: + a.eval = func(t *Thread) string { return call(t)[0].(StringValue).Get(t) } + case *ArrayType: + a.eval = func(t *Thread) ArrayValue { return call(t)[0].(ArrayValue).Get(t) } + case *StructType: + a.eval = func(t *Thread) StructValue { return call(t)[0].(StructValue).Get(t) } + case *PtrType: + a.eval = func(t *Thread) Value { return call(t)[0].(PtrValue).Get(t) } + case *FuncType: + a.eval = func(t *Thread) Func { return call(t)[0].(FuncValue).Get(t) } + case *SliceType: + a.eval = func(t *Thread) Slice { return call(t)[0].(SliceValue).Get(t) } + case *MapType: + a.eval = func(t *Thread) Map { return call(t)[0].(MapValue).Get(t) } + case *MultiType: + a.eval = func(t *Thread) []Value { return call(t) } + default: + log.Crashf("unexpected result type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genValue(vf func(*Thread) Value) { + a.evalAddr = vf; + switch a.t.lit().(type) { + case *boolType: + a.eval = func(t *Thread) bool { return vf(t).(BoolValue).Get(t) } + case *uintType: + a.eval = func(t *Thread) uint64 { return vf(t).(UintValue).Get(t) } + case *intType: + a.eval = func(t *Thread) int64 { return vf(t).(IntValue).Get(t) } + case *floatType: + a.eval = func(t *Thread) float64 { return vf(t).(FloatValue).Get(t) } + case *stringType: + a.eval = func(t *Thread) string { return vf(t).(StringValue).Get(t) } + case *ArrayType: + a.eval = func(t *Thread) ArrayValue { return vf(t).(ArrayValue).Get(t) } + case *StructType: + a.eval = func(t *Thread) StructValue { return vf(t).(StructValue).Get(t) } + case *PtrType: + a.eval = func(t *Thread) Value { return vf(t).(PtrValue).Get(t) } + case *FuncType: + a.eval = func(t *Thread) Func { return vf(t).(FuncValue).Get(t) } + case *SliceType: + a.eval = func(t *Thread) Slice { return vf(t).(SliceValue).Get(t) } + case *MapType: + a.eval = func(t *Thread) Map { return vf(t).(MapValue).Get(t) } + default: + log.Crashf("unexpected result type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genUnaryOpNeg(v *expr) { + switch a.t.lit().(type) { + case *uintType: + vf := v.asUint(); + a.eval = func(t *Thread) uint64 { v := vf(t); return -v } + case *intType: + vf := v.asInt(); + a.eval = func(t *Thread) int64 { v := vf(t); return -v } + case *idealIntType: + v := v.asIdealInt()(); + val := v.Neg(); + a.eval = func() *bignum.Integer { return val } + case *floatType: + vf := v.asFloat(); + a.eval = func(t *Thread) float64 { v := vf(t); return -v } + case *idealFloatType: + v := v.asIdealFloat()(); + val := v.Neg(); + a.eval = func() *bignum.Rational { return val } + default: + log.Crashf("unexpected type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genUnaryOpNot(v *expr) { + switch a.t.lit().(type) { + case *boolType: + vf := v.asBool(); + a.eval = func(t *Thread) bool { v := vf(t); return !v } + default: + log.Crashf("unexpected type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genUnaryOpXor(v *expr) { + switch a.t.lit().(type) { + case *uintType: + vf := v.asUint(); + a.eval = func(t *Thread) uint64 { v := vf(t); return ^v } + case *intType: + vf := v.asInt(); + a.eval = func(t *Thread) int64 { v := vf(t); return ^v } + case *idealIntType: + v := v.asIdealInt()(); + val := v.Neg().Sub(bignum.Int(1)); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genBinOpLogAnd(l, r *expr) { + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { return lf(t) && rf(t) } +} + +func (a *expr) genBinOpLogOr(l, r *expr) { + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { return lf(t) || rf(t) } +} + +func (a *expr) genBinOpAdd(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l + r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l + r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l + r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l + r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l + r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l + r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l + r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l + r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l + r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l + r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Add(r); + a.eval = func() *bignum.Integer { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + switch t.Bits { + case 32: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l + r; + return float64(float32(ret)) + } + case 64: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l + r; + return float64(float64(ret)) + } + case 0: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l + r; + return float64(float(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Add(r); + a.eval = func() *bignum.Rational { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) string { + l, r := lf(t), rf(t); + return l + r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpSub(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l - r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l - r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l - r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l - r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l - r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l - r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l - r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l - r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l - r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l - r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Sub(r); + a.eval = func() *bignum.Integer { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + switch t.Bits { + case 32: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l - r; + return float64(float32(ret)) + } + case 64: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l - r; + return float64(float64(ret)) + } + case 0: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l - r; + return float64(float(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Sub(r); + a.eval = func() *bignum.Rational { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpMul(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l * r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l * r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l * r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l * r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l * r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l * r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l * r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l * r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l * r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l * r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Mul(r); + a.eval = func() *bignum.Integer { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + switch t.Bits { + case 32: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l * r; + return float64(float32(ret)) + } + case 64: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l * r; + return float64(float64(ret)) + } + case 0: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + ret = l * r; + return float64(float(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Mul(r); + a.eval = func() *bignum.Rational { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpQuo(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Quo(r); + a.eval = func() *bignum.Integer { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + switch t.Bits { + case 32: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return float64(float32(ret)) + } + case 64: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return float64(float64(ret)) + } + case 0: + a.eval = func(t *Thread) float64 { + l, r := lf(t), rf(t); + var ret float64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r; + return float64(float(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Quo(r); + a.eval = func() *bignum.Rational { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpRem(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Rem(r); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpAnd(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l & r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l & r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l & r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l & r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l & r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l & r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l & r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l & r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l & r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l & r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.And(r); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpOr(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l | r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l | r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l | r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l | r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l | r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l | r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l | r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l | r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l | r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l | r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Or(r); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpXor(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l ^ r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l ^ r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l ^ r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l ^ r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l ^ r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l ^ r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l ^ r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l ^ r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l ^ r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l ^ r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Xor(r); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpAndNot(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l &^ r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l &^ r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l &^ r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l &^ r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l &^ r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l &^ r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l &^ r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l &^ r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l &^ r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l &^ r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.AndNot(r); + a.eval = func() *bignum.Integer { return val } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpShl(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l << r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l << r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l << r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l << r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l << r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l << r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l << r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l << r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l << r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l << r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpShr(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l >> r; + return uint64(uint8(ret)) + } + case 16: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l >> r; + return uint64(uint16(ret)) + } + case 32: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l >> r; + return uint64(uint32(ret)) + } + case 64: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l >> r; + return uint64(uint64(ret)) + } + case 0: + a.eval = func(t *Thread) uint64 { + l, r := lf(t), rf(t); + var ret uint64; + ret = l >> r; + return uint64(uint(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + case *intType: + lf := l.asInt(); + rf := r.asUint(); + switch t.Bits { + case 8: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l >> r; + return int64(int8(ret)) + } + case 16: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l >> r; + return int64(int16(ret)) + } + case 32: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l >> r; + return int64(int32(ret)) + } + case 64: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l >> r; + return int64(int64(ret)) + } + case 0: + a.eval = func(t *Thread) int64 { + l, r := lf(t), rf(t); + var ret int64; + ret = l >> r; + return int64(int(ret)) + } + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpLss(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l < r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l < r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) < 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l < r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) < 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l < r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpGtr(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l > r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l > r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) > 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l > r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) > 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l > r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpLeq(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l <= r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l <= r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) <= 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l <= r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) <= 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l <= r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpGeq(l, r *expr) { + switch t := l.t.lit().(type) { + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l >= r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l >= r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) >= 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l >= r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) >= 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l >= r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpEql(l, r *expr) { + switch t := l.t.lit().(type) { + case *boolType: + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) == 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) == 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *PtrType: + lf := l.asPtr(); + rf := r.asPtr(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *FuncType: + lf := l.asFunc(); + rf := r.asFunc(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + case *MapType: + lf := l.asMap(); + rf := r.asMap(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l == r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func (a *expr) genBinOpNeq(l, r *expr) { + switch t := l.t.lit().(type) { + case *boolType: + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *uintType: + lf := l.asUint(); + rf := r.asUint(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *intType: + lf := l.asInt(); + rf := r.asInt(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *idealIntType: + l := l.asIdealInt()(); + r := r.asIdealInt()(); + val := l.Cmp(r) != 0; + a.eval = func(t *Thread) bool { return val } + case *floatType: + lf := l.asFloat(); + rf := r.asFloat(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *idealFloatType: + l := l.asIdealFloat()(); + r := r.asIdealFloat()(); + val := l.Cmp(r) != 0; + a.eval = func(t *Thread) bool { return val } + case *stringType: + lf := l.asString(); + rf := r.asString(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *PtrType: + lf := l.asPtr(); + rf := r.asPtr(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *FuncType: + lf := l.asFunc(); + rf := r.asFunc(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + case *MapType: + lf := l.asMap(); + rf := r.asMap(); + a.eval = func(t *Thread) bool { + l, r := lf(t), rf(t); + return l != r + } + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +func genAssign(lt Type, r *expr) (func(lv Value, t *Thread)) { + switch lt.lit().(type) { + case *boolType: + rf := r.asBool(); + return func(lv Value, t *Thread) { lv.(BoolValue).Set(t, rf(t)) } + case *uintType: + rf := r.asUint(); + return func(lv Value, t *Thread) { lv.(UintValue).Set(t, rf(t)) } + case *intType: + rf := r.asInt(); + return func(lv Value, t *Thread) { lv.(IntValue).Set(t, rf(t)) } + case *floatType: + rf := r.asFloat(); + return func(lv Value, t *Thread) { lv.(FloatValue).Set(t, rf(t)) } + case *stringType: + rf := r.asString(); + return func(lv Value, t *Thread) { lv.(StringValue).Set(t, rf(t)) } + case *ArrayType: + rf := r.asArray(); + return func(lv Value, t *Thread) { lv.Assign(t, rf(t)) } + case *StructType: + rf := r.asStruct(); + return func(lv Value, t *Thread) { lv.Assign(t, rf(t)) } + case *PtrType: + rf := r.asPtr(); + return func(lv Value, t *Thread) { lv.(PtrValue).Set(t, rf(t)) } + case *FuncType: + rf := r.asFunc(); + return func(lv Value, t *Thread) { lv.(FuncValue).Set(t, rf(t)) } + case *SliceType: + rf := r.asSlice(); + return func(lv Value, t *Thread) { lv.(SliceValue).Set(t, rf(t)) } + case *MapType: + rf := r.asMap(); + return func(lv Value, t *Thread) { lv.(MapValue).Set(t, rf(t)) } + default: + log.Crashf("unexpected left operand type %v at %v", lt, r.pos); + } + panic(); +} diff --git a/src/pkg/exp/eval/expr_test.go b/src/pkg/exp/eval/expr_test.go new file mode 100644 index 000000000..ea11cf69d --- /dev/null +++ b/src/pkg/exp/eval/expr_test.go @@ -0,0 +1,345 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "testing"; +) + +var undefined = "undefined" +var typeAsExpr = "type .* used as expression" +var badCharLit = "character literal" +var illegalEscape = "illegal char escape" +var opTypes = "illegal (operand|argument) type|cannot index into" +var badAddrOf = "cannot take the address" +var constantTruncated = "constant [^ ]* truncated" +var constantUnderflows = "constant [^ ]* underflows" +var constantOverflows = "constant [^ ]* overflows" +var implLimit = "implementation limit" +var mustBeUnsigned = "must be unsigned" +var divByZero = "divide by zero" + +var hugeInteger = bignum.Int(1).Shl(64); + +var exprTests = []test { + Val("i", 1), + CErr("zzz", undefined), + // TODO(austin) Test variable in constant context + //CErr("t", typeAsExpr), + + Val("'a'", bignum.Int('a')), + Val("'\\uffff'", bignum.Int('\uffff')), + Val("'\\n'", bignum.Int('\n')), + CErr("''+x", badCharLit), + // Produces two parse errors + //CErr("'''", ""), + CErr("'\n'", badCharLit), + CErr("'\\z'", illegalEscape), + CErr("'ab'", badCharLit), + + Val("1.0", bignum.Rat(1, 1)), + Val("1.", bignum.Rat(1, 1)), + Val(".1", bignum.Rat(1, 10)), + Val("1e2", bignum.Rat(100, 1)), + + Val("\"abc\"", "abc"), + Val("\"\"", ""), + Val("\"\\n\\\"\"", "\n\""), + CErr("\"\\z\"", illegalEscape), + CErr("\"abc", "string not terminated"), + + Val("\"abc\" \"def\"", "abcdef"), + CErr("\"abc\" \"\\z\"", illegalEscape), + + Val("(i)", 1), + + Val("ai[0]", 1), + Val("(&ai)[0]", 1), + Val("ai[1]", 2), + Val("ai[i]", 2), + Val("ai[u]", 2), + CErr("ai[f]", opTypes), + CErr("ai[0][0]", opTypes), + CErr("ai[2]", "index 2 exceeds"), + CErr("ai[1+1]", "index 2 exceeds"), + CErr("ai[-1]", "negative index"), + RErr("ai[i+i]", "index 2 exceeds"), + RErr("ai[-i]", "negative index"), + CErr("i[0]", opTypes), + CErr("f[0]", opTypes), + + Val("aai[0][0]", 1), + Val("aai[1][1]", 4), + CErr("aai[2][0]", "index 2 exceeds"), + CErr("aai[0][2]", "index 2 exceeds"), + + Val("sli[0]", 1), + Val("sli[1]", 2), + CErr("sli[-1]", "negative index"), + RErr("sli[-i]", "negative index"), + RErr("sli[2]", "index 2 exceeds"), + + Val("s[0]", uint8('a')), + Val("s[1]", uint8('b')), + CErr("s[-1]", "negative index"), + RErr("s[-i]", "negative index"), + RErr("s[3]", "index 3 exceeds"), + + CErr("1(2)", "cannot call"), + CErr("fn(1,2)", "too many"), + CErr("fn()", "not enough"), + CErr("fn(true)", opTypes), + CErr("fn(true)", "function call"), + // Single argument functions don't say which argument. + //CErr("fn(true)", "argument 1"), + Val("fn(1)", 2), + Val("fn(1.0)", 2), + CErr("fn(1.5)", constantTruncated), + Val("fn(i)", 2), + CErr("fn(u)", opTypes), + + CErr("void()+2", opTypes), + CErr("oneTwo()+2", opTypes), + + Val("cap(ai)", 2), + Val("cap(&ai)", 2), + Val("cap(aai)", 2), + Val("cap(sli)", 3), + CErr("cap(0)", opTypes), + CErr("cap(i)", opTypes), + CErr("cap(s)", opTypes), + + Val("len(s)", 3), + Val("len(ai)", 2), + Val("len(&ai)", 2), + Val("len(aai)", 2), + Val("len(sli)", 2), + // TODO(austin) Test len of map + CErr("len(0)", opTypes), + CErr("len(i)", opTypes), + + CErr("*i", opTypes), + Val("*&i", 1), + Val("*&(i)", 1), + CErr("&1", badAddrOf), + CErr("&c", badAddrOf), + Val("*(&ai[0])", 1), + + Val("+1", bignum.Int(+1)), + Val("+1.0", bignum.Rat(1, 1)), + CErr("+\"x\"", opTypes), + + Val("-42", bignum.Int(-42)), + Val("-i", -1), + Val("-f", -1.0), + // 6g bug? + //Val("-(f-1)", -0.0), + CErr("-\"x\"", opTypes), + + // TODO(austin) Test unary ! + + Val("^2", bignum.Int(^2)), + Val("^(-2)", bignum.Int(^(-2))), + CErr("^2.0", opTypes), + CErr("^2.5", opTypes), + Val("^i", ^1), + Val("^u", ^uint(1)), + CErr("^f", opTypes), + + Val("1+i", 2), + Val("1+u", uint(2)), + Val("3.0+i", 4), + Val("1+1", bignum.Int(2)), + Val("f+f", 2.0), + Val("1+f", 2.0), + Val("1.0+1", bignum.Rat(2, 1)), + Val("\"abc\" + \"def\"", "abcdef"), + CErr("i+u", opTypes), + CErr("-1+u", constantUnderflows), + // TODO(austin) Test named types + + Val("2-1", bignum.Int(1)), + Val("2.0-1", bignum.Rat(1, 1)), + Val("f-2", -1.0), + // TOOD(austin) bignum can't do negative 0? + //Val("-0.0", XXX), + Val("2*2", bignum.Int(4)), + Val("2*i", 2), + Val("3/2", bignum.Int(1)), + Val("3/i", 3), + CErr("1/0", divByZero), + CErr("1.0/0", divByZero), + RErr("i/0", divByZero), + Val("3%2", bignum.Int(1)), + Val("i%2", 1), + CErr("3%0", divByZero), + CErr("3.0%0", opTypes), + RErr("i%0", divByZero), + + // Examples from "Arithmetic operators" + Val("5/3", bignum.Int(1)), + Val("(i+4)/(i+2)", 1), + Val("5%3", bignum.Int(2)), + Val("(i+4)%(i+2)", 2), + Val("-5/3", bignum.Int(-1)), + Val("(i-6)/(i+2)", -1), + Val("-5%3", bignum.Int(-2)), + Val("(i-6)%(i+2)", -2), + Val("5/-3", bignum.Int(-1)), + Val("(i+4)/(i-4)", -1), + Val("5%-3", bignum.Int(2)), + Val("(i+4)%(i-4)", 2), + Val("-5/-3", bignum.Int(1)), + Val("(i-6)/(i-4)", 1), + Val("-5%-3", bignum.Int(-2)), + Val("(i-6)%(i-4)", -2), + + // Examples from "Arithmetic operators" + Val("11/4", bignum.Int(2)), + Val("(i+10)/4", 2), + Val("11%4", bignum.Int(3)), + Val("(i+10)%4", 3), + Val("11>>2", bignum.Int(2)), + Val("(i+10)>>2", 2), + Val("11&3", bignum.Int(3)), + Val("(i+10)&3", 3), + Val("-11/4", bignum.Int(-2)), + Val("(i-12)/4", -2), + Val("-11%4", bignum.Int(-3)), + Val("(i-12)%4", -3), + Val("-11>>2", bignum.Int(-3)), + Val("(i-12)>>2", -3), + Val("-11&3", bignum.Int(1)), + Val("(i-12)&3", 1), + + // TODO(austin) Test bit ops + + // For shift, we try nearly every combination of positive + // ideal int, negative ideal int, big ideal int, ideal + // fractional float, ideal non-fractional float, int, uint, + // and float. + Val("2<<2", bignum.Int(2<<2)), + CErr("2<<(-1)", constantUnderflows), + CErr("2<<0x10000000000000000", constantOverflows), + CErr("2<<2.5", constantTruncated), + Val("2<<2.0", bignum.Int(2<<2.0)), + CErr("2<<i", mustBeUnsigned), + Val("2<<u", 2<<1), + CErr("2<<f", opTypes), + + Val("-2<<2", bignum.Int(-2<<2)), + CErr("-2<<(-1)", constantUnderflows), + CErr("-2<<0x10000000000000000", constantOverflows), + CErr("-2<<2.5", constantTruncated), + Val("-2<<2.0", bignum.Int(-2<<2.0)), + CErr("-2<<i", mustBeUnsigned), + Val("-2<<u", -2<<1), + CErr("-2<<f", opTypes), + + Val("0x10000000000000000<<2", hugeInteger.Shl(2)), + CErr("0x10000000000000000<<(-1)", constantUnderflows), + CErr("0x10000000000000000<<0x10000000000000000", constantOverflows), + CErr("0x10000000000000000<<2.5", constantTruncated), + Val("0x10000000000000000<<2.0", hugeInteger.Shl(2)), + CErr("0x10000000000000000<<i", mustBeUnsigned), + CErr("0x10000000000000000<<u", constantOverflows), + CErr("0x10000000000000000<<f", opTypes), + + CErr("2.5<<2", opTypes), + CErr("2.0<<2", opTypes), + + Val("i<<2", 1<<2), + CErr("i<<(-1)", constantUnderflows), + CErr("i<<0x10000000000000000", constantOverflows), + CErr("i<<2.5", constantTruncated), + Val("i<<2.0", 1<<2), + CErr("i<<i", mustBeUnsigned), + Val("i<<u", 1<<1), + CErr("i<<f", opTypes), + Val("i<<u", 1<<1), + + Val("u<<2", uint(1<<2)), + CErr("u<<(-1)", constantUnderflows), + CErr("u<<0x10000000000000000", constantOverflows), + CErr("u<<2.5", constantTruncated), + Val("u<<2.0", uint(1<<2)), + CErr("u<<i", mustBeUnsigned), + Val("u<<u", uint(1<<1)), + CErr("u<<f", opTypes), + Val("u<<u", uint(1<<1)), + + CErr("f<<2", opTypes), + + // <, <=, >, >= + Val("1<2", 1<2), + Val("1<=2", 1<=2), + Val("2<=2", 2<=2), + Val("1>2", 1>2), + Val("1>=2", 1>=2), + Val("2>=2", 2>=2), + + Val("i<2", 1<2), + Val("i<=2", 1<=2), + Val("i+1<=2", 2<=2), + Val("i>2", 1>2), + Val("i>=2", 1>=2), + Val("i+1>=2", 2>=2), + + Val("u<2", 1<2), + Val("f<2", 1<2), + + Val("s<\"b\"", true), + Val("s<\"a\"", false), + Val("s<=\"abc\"", true), + Val("s>\"aa\"", true), + Val("s>\"ac\"", false), + Val("s>=\"abc\"", true), + + CErr("i<u", opTypes), + CErr("i<f", opTypes), + CErr("i<s", opTypes), + CErr("&i<&i", opTypes), + CErr("ai<ai", opTypes), + + // ==, != + Val("1==1", true), + Val("1!=1", false), + Val("1==2", false), + Val("1!=2", true), + + Val("1.0==1", true), + Val("1.5==1", false), + + Val("i==1", true), + Val("i!=1", false), + Val("i==2", false), + Val("i!=2", true), + + Val("u==1", true), + Val("f==1", true), + + Val("s==\"abc\"", true), + Val("s!=\"abc\"", false), + Val("s==\"abcd\"", false), + Val("s!=\"abcd\"", true), + + Val("&i==&i", true), + Val("&i==&i2", false), + + Val("fn==fn", true), + Val("fn==func(int)int{return 0}", false), + + CErr("i==u", opTypes), + CErr("i==f", opTypes), + CErr("&i==&f", opTypes), + CErr("ai==ai", opTypes), + CErr("t==t", opTypes), + CErr("fn==oneTwo", opTypes), +} + +func TestExpr(t *testing.T) { + runTests(t, "exprTests", exprTests); +} diff --git a/src/pkg/exp/eval/func.go b/src/pkg/exp/eval/func.go new file mode 100644 index 000000000..3bf52871d --- /dev/null +++ b/src/pkg/exp/eval/func.go @@ -0,0 +1,89 @@ +// Copyright 2009 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 eval + +import "os" + +/* + * Virtual machine + */ + +type Thread struct { + abort chan os.Error; + pc uint; + // The execution frame of this function. This remains the + // same throughout a function invocation. + f *Frame; +} + +type code []func(*Thread) + +func (i code) exec(t *Thread) { + opc := t.pc; + t.pc = 0; + l := uint(len(i)); + for t.pc < l { + pc := t.pc; + t.pc++; + i[pc](t); + } + t.pc = opc; +} + +/* + * Code buffer + */ + +type codeBuf struct { + instrs code; +} + +func newCodeBuf() *codeBuf { + return &codeBuf{make(code, 0, 16)}; +} + +func (b *codeBuf) push(instr func(*Thread)) { + n := len(b.instrs); + if n >= cap(b.instrs) { + a := make(code, n, n*2); + for i := range b.instrs { + a[i] = b.instrs[i]; + } + b.instrs = a; + } + b.instrs = b.instrs[0:n+1]; + b.instrs[n] = instr; +} + +func (b *codeBuf) nextPC() uint { + return uint(len(b.instrs)); +} + +func (b *codeBuf) get() code { + // Freeze this buffer into an array of exactly the right size + a := make(code, len(b.instrs)); + for i := range b.instrs { + a[i] = b.instrs[i]; + } + return code(a); +} + +/* + * User-defined functions + */ + +type evalFunc struct { + outer *Frame; + frameSize int; + code code; +} + +func (f *evalFunc) NewFrame() *Frame { + return f.outer.child(f.frameSize); +} + +func (f *evalFunc) Call(t *Thread) { + f.code.exec(t); +} diff --git a/src/pkg/exp/eval/gen.go b/src/pkg/exp/eval/gen.go new file mode 100644 index 000000000..06939e58c --- /dev/null +++ b/src/pkg/exp/eval/gen.go @@ -0,0 +1,375 @@ +// Copyright 2009 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 main + +// generate operator implementations + +import ( + "log"; + "os"; + "template"; +) + +type Op struct { + Name string; + Expr string; + Body string; // overrides Expr + ConstExpr string; + AsRightName string; + ReturnType string; + Types []*Type; +} + +type Size struct { + Bits int; + Sized string; +} + +type Type struct { + Repr string; + Value string; + Native string; + As string; + IsIdeal bool; + HasAssign bool; + Sizes []Size; +} + +var ( + boolType = &Type{ Repr: "*boolType", Value: "BoolValue", Native: "bool", As: "asBool" }; + uintType = &Type{ Repr: "*uintType", Value: "UintValue", Native: "uint64", As: "asUint", + Sizes: []Size{ Size{8, "uint8"}, Size{16, "uint16"}, Size{32, "uint32"}, Size{64, "uint64"}, Size{0, "uint"}} + }; + intType = &Type{ Repr: "*intType", Value: "IntValue", Native: "int64", As: "asInt", + Sizes: []Size{Size{8, "int8"}, Size{16, "int16"}, Size{32, "int32"}, Size{64, "int64"}, Size{0, "int"}} + }; + idealIntType = &Type{ Repr: "*idealIntType", Value: "IdealIntValue", Native: "*bignum.Integer", As: "asIdealInt", IsIdeal: true }; + floatType = &Type{ Repr: "*floatType", Value: "FloatValue", Native: "float64", As: "asFloat", + Sizes: []Size{Size{32, "float32"}, Size{64, "float64"}, Size{0, "float"}} + }; + idealFloatType = &Type{ Repr: "*idealFloatType", Value: "IdealFloatValue", Native: "*bignum.Rational", As: "asIdealFloat", IsIdeal: true }; + stringType = &Type{ Repr: "*stringType", Value: "StringValue", Native: "string", As: "asString" }; + arrayType = &Type{ Repr: "*ArrayType", Value: "ArrayValue", Native: "ArrayValue", As: "asArray", HasAssign: true }; + structType = &Type{ Repr: "*StructType", Value: "StructValue", Native: "StructValue", As: "asStruct", HasAssign: true }; + ptrType = &Type{ Repr: "*PtrType", Value: "PtrValue", Native: "Value", As: "asPtr" }; + funcType = &Type{ Repr: "*FuncType", Value: "FuncValue", Native: "Func", As: "asFunc" }; + sliceType = &Type{ Repr: "*SliceType", Value: "SliceValue", Native: "Slice", As: "asSlice" }; + mapType = &Type{ Repr: "*MapType", Value: "MapValue", Native: "Map", As: "asMap" }; + + all = []*Type{ + boolType, + uintType, + intType, + idealIntType, + floatType, + idealFloatType, + stringType, + arrayType, + structType, + ptrType, + funcType, + sliceType, + mapType, + }; + bools = all[0:1]; + integers = all[1:4]; + shiftable = all[1:3]; + numbers = all[1:6]; + addable = all[1:7]; + cmpable = []*Type{ + boolType, + uintType, + intType, + idealIntType, + floatType, + idealFloatType, + stringType, + ptrType, + funcType, + mapType, + }; +) + +var unOps = []Op{ + Op{ Name: "Neg", Expr: "-v", ConstExpr: "v.Neg()", Types: numbers }, + Op{ Name: "Not", Expr: "!v", Types: bools }, + Op{ Name: "Xor", Expr: "^v", ConstExpr: "v.Neg().Sub(bignum.Int(1))", Types: integers }, +} + +var binOps = []Op{ + Op{ Name: "Add", Expr: "l + r", ConstExpr: "l.Add(r)", Types: addable }, + Op{ Name: "Sub", Expr: "l - r", ConstExpr: "l.Sub(r)", Types: numbers }, + Op{ Name: "Mul", Expr: "l * r", ConstExpr: "l.Mul(r)", Types: numbers }, + Op{ Name: "Quo", + Body: "if r == 0 { t.Abort(DivByZeroError{}) } ret = l / r", + ConstExpr: "l.Quo(r)", + Types: numbers, + }, + Op{ Name: "Rem", + Body: "if r == 0 { t.Abort(DivByZeroError{}) } ret = l % r", + ConstExpr: "l.Rem(r)", + Types: integers, + }, + Op{ Name: "And", Expr: "l & r", ConstExpr: "l.And(r)", Types: integers }, + Op{ Name: "Or", Expr: "l | r", ConstExpr: "l.Or(r)", Types: integers }, + Op{ Name: "Xor", Expr: "l ^ r", ConstExpr: "l.Xor(r)", Types: integers }, + Op{ Name: "AndNot", Expr: "l &^ r", ConstExpr: "l.AndNot(r)", Types: integers }, + Op{ Name: "Shl", Expr: "l << r", ConstExpr: "l.Shl(uint(r.Value()))", + AsRightName: "asUint", Types: shiftable + }, + Op{ Name: "Shr", Expr: "l >> r", ConstExpr: "l.Shr(uint(r.Value()))", + AsRightName: "asUint", Types: shiftable + }, + Op{ Name: "Lss", Expr: "l < r", ConstExpr: "l.Cmp(r) < 0", ReturnType: "bool", Types: addable }, + Op{ Name: "Gtr", Expr: "l > r", ConstExpr: "l.Cmp(r) > 0", ReturnType: "bool", Types: addable }, + Op{ Name: "Leq", Expr: "l <= r", ConstExpr: "l.Cmp(r) <= 0", ReturnType: "bool", Types: addable }, + Op{ Name: "Geq", Expr: "l >= r", ConstExpr: "l.Cmp(r) >= 0", ReturnType: "bool", Types: addable }, + Op{ Name: "Eql", Expr: "l == r", ConstExpr: "l.Cmp(r) == 0", ReturnType: "bool", Types: cmpable }, + Op{ Name: "Neq", Expr: "l != r", ConstExpr: "l.Cmp(r) != 0", ReturnType: "bool", Types: cmpable }, +} + +type Data struct { + UnaryOps []Op; + BinaryOps []Op; + Types []*Type; +} + +var data = Data { + unOps, + binOps, + all, +} + +const templateStr = ` +// This file is machine generated by gen.go. +// 6g gen.go && 6l gen.6 && ./6.out >expr1.go + +package eval + +import ( + "bignum"; + "log"; +) + +/* + * "As" functions. These retrieve evaluator functions from an + * expr, panicking if the requested evaluator has the wrong type. + */ +ยซ.repeated section Typesยป +ยซ.section IsIdealยป +func (a *expr) ยซAsยป() (func() ยซNativeยป) { + return a.eval.(func()(ยซNativeยป)) +} +ยซ.orยป +func (a *expr) ยซAsยป() (func(*Thread) ยซNativeยป) { + return a.eval.(func(*Thread)(ยซNativeยป)) +} +ยซ.endยป +ยซ.endยป +func (a *expr) asMulti() (func(*Thread) []Value) { + return a.eval.(func(*Thread)[]Value) +} + +func (a *expr) asInterface() (func(*Thread) interface{}) { + switch sf := a.eval.(type) { +ยซ.repeated section Typesยป +ยซ.section IsIdealยป + case func()ยซNativeยป: + return func(*Thread) interface{} { return sf() } +ยซ.orยป + case func(t *Thread)ยซNativeยป: + return func(t *Thread) interface{} { return sf(t) } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected expression node type %T at %v", a.eval, a.pos); + } + panic(); +} + +/* + * Operator generators. + */ + +func (a *expr) genConstant(v Value) { + switch a.t.lit().(type) { +ยซ.repeated section Typesยป + case ยซReprยป: +ยซ.section IsIdealยป + val := v.(ยซValueยป).Get(); + a.eval = func() ยซNativeยป { return val } +ยซ.orยป + a.eval = func(t *Thread) ยซNativeยป { return v.(ยซValueยป).Get(t) } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected constant type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genIdentOp(level, index int) { + a.evalAddr = func(t *Thread) Value { return t.f.Get(level, index) }; + switch a.t.lit().(type) { +ยซ.repeated section Typesยป +ยซ.section IsIdealยป +ยซ.orยป + case ยซReprยป: + a.eval = func(t *Thread) ยซNativeยป { return t.f.Get(level, index).(ยซValueยป).Get(t) } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected identifier type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genFuncCall(call func(t *Thread) []Value) { + a.exec = func(t *Thread) { call(t)}; + switch a.t.lit().(type) { +ยซ.repeated section Typesยป +ยซ.section IsIdealยป +ยซ.orยป + case ยซReprยป: + a.eval = func(t *Thread) ยซNativeยป { return call(t)[0].(ยซValueยป).Get(t) } +ยซ.endยป +ยซ.endยป + case *MultiType: + a.eval = func(t *Thread) []Value { return call(t) } + default: + log.Crashf("unexpected result type %v at %v", a.t, a.pos); + } +} + +func (a *expr) genValue(vf func(*Thread) Value) { + a.evalAddr = vf; + switch a.t.lit().(type) { +ยซ.repeated section Typesยป +ยซ.section IsIdealยป +ยซ.orยป + case ยซReprยป: + a.eval = func(t *Thread) ยซNativeยป { return vf(t).(ยซValueยป).Get(t) } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected result type %v at %v", a.t, a.pos); + } +} + +ยซ.repeated section UnaryOpsยป +func (a *expr) genUnaryOpยซNameยป(v *expr) { + switch a.t.lit().(type) { +ยซ.repeated section Typesยป + case ยซReprยป: +ยซ.section IsIdealยป + v := v.ยซAsยป()(); + val := ยซConstExprยป; + a.eval = func() ยซNativeยป { return val } +ยซ.orยป + vf := v.ยซAsยป(); + a.eval = func(t *Thread) ยซNativeยป { v := vf(t); return ยซExprยป } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected type %v at %v", a.t, a.pos); + } +} + +ยซ.endยป +func (a *expr) genBinOpLogAnd(l, r *expr) { + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { return lf(t) && rf(t) } +} + +func (a *expr) genBinOpLogOr(l, r *expr) { + lf := l.asBool(); + rf := r.asBool(); + a.eval = func(t *Thread) bool { return lf(t) || rf(t) } +} + +ยซ.repeated section BinaryOpsยป +func (a *expr) genBinOpยซNameยป(l, r *expr) { + switch t := l.t.lit().(type) { +ยซ.repeated section Typesยป + case ยซReprยป: + ยซ.section IsIdealยป + l := l.ยซAsยป()(); + r := r.ยซAsยป()(); + val := ยซConstExprยป; + ยซ.section ReturnTypeยป + a.eval = func(t *Thread) ยซReturnTypeยป { return val } + ยซ.orยป + a.eval = func() ยซNativeยป { return val } + ยซ.endยป + ยซ.orยป + lf := l.ยซAsยป(); + rf := r.ยซ.section AsRightNameยปยซ@ยปยซ.orยปยซAsยปยซ.endยป(); + ยซ.section ReturnTypeยป + a.eval = func(t *Thread) ยซ@ยป { + l, r := lf(t), rf(t); + return ยซExprยป + } + ยซ.orยป + ยซ.section Sizesยป + switch t.Bits { + ยซ.repeated section @ยป + case ยซBitsยป: + a.eval = func(t *Thread) ยซNativeยป { + l, r := lf(t), rf(t); + var ret ยซNativeยป; + ยซ.section Bodyยป + ยซBodyยป; + ยซ.orยป + ret = ยซExprยป; + ยซ.endยป + return ยซNativeยป(ยซSizedยป(ret)) + } + ยซ.endยป + default: + log.Crashf("unexpected size %d in type %v at %v", t.Bits, t, a.pos); + } + ยซ.orยป + a.eval = func(t *Thread) ยซNativeยป { + l, r := lf(t), rf(t); + return ยซExprยป + } + ยซ.endยป + ยซ.endยป + ยซ.endยป + ยซ.endยป + default: + log.Crashf("unexpected type %v at %v", l.t, a.pos); + } +} + +ยซ.endยป +func genAssign(lt Type, r *expr) (func(lv Value, t *Thread)) { + switch lt.lit().(type) { +ยซ.repeated section Typesยป +ยซ.section IsIdealยป +ยซ.orยป + case ยซReprยป: + rf := r.ยซAsยป(); + return func(lv Value, t *Thread) { ยซ.section HasAssignยปlv.Assign(t, rf(t))ยซ.orยปlv.(ยซValueยป).Set(t, rf(t))ยซ.endยป } +ยซ.endยป +ยซ.endยป + default: + log.Crashf("unexpected left operand type %v at %v", lt, r.pos); + } + panic(); +} +` + +func main() { + t := template.New(nil); + t.SetDelims("ยซ", "ยป"); + err := t.Parse(templateStr); + if err != nil { + log.Exit(err); + } + err = t.Execute(data, os.Stdout); + if err != nil { + log.Exit(err); + } +} diff --git a/src/pkg/exp/eval/main.go b/src/pkg/exp/eval/main.go new file mode 100644 index 000000000..2a6d94845 --- /dev/null +++ b/src/pkg/exp/eval/main.go @@ -0,0 +1,92 @@ +// Copyright 2009 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 main + +import ( + "./_obj/eval"; + "bufio"; + "flag"; + "go/parser"; + "go/scanner"; + "io"; + "os"; +) + +var filename = flag.String("f", "", "file to run"); + +func main() { + flag.Parse(); + w := eval.NewWorld(); + if *filename != "" { + data, err := io.ReadFile(*filename); + if err != nil { + println(err.String()); + os.Exit(1); + } + file, err := parser.ParseFile(*filename, data, 0); + if err != nil { + println(err.String()); + os.Exit(1); + } + code, err := w.CompileDeclList(file.Decls); + if err != nil { + if list, ok := err.(scanner.ErrorList); ok { + for _, e := range list { + println(e.String()); + } + } else { + println(err.String()); + } + os.Exit(1); + } + _, err := code.Run(); + if err != nil { + println(err.String()); + os.Exit(1); + } + code, err = w.Compile("init()"); + if code != nil { + _, err := code.Run(); + if err != nil { + println(err.String()); + os.Exit(1); + } + } + code, err = w.Compile("main()"); + if err != nil { + println(err.String()); + os.Exit(1); + } + _, err = code.Run(); + if err != nil { + println(err.String()); + os.Exit(1); + } + os.Exit(0); + } + + r := bufio.NewReader(os.Stdin); + for { + print("; "); + line, err := r.ReadString('\n'); + if err != nil { + break; + } + code, err := w.Compile(line); + if err != nil { + println(err.String()); + continue; + } + v, err := code.Run(); + if err != nil { + println(err.String()); + continue; + } + if v != nil { + println(v.String()); + } + } +} + diff --git a/src/pkg/exp/eval/scope.go b/src/pkg/exp/eval/scope.go new file mode 100644 index 000000000..7ee4a8915 --- /dev/null +++ b/src/pkg/exp/eval/scope.go @@ -0,0 +1,203 @@ +// Copyright 2009 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 eval + +import ( + "go/token"; + "log"; +) + +/* + * Blocks and scopes + */ + +// A definition can be a *Variable, *Constant, or Type. +type Def interface { + Pos() token.Position; +} + +type Variable struct { + token.Position; + // Index of this variable in the Frame structure + Index int; + // Static type of this variable + Type Type; + // Value of this variable. This is only used by Scope.NewFrame; + // therefore, it is useful for global scopes but cannot be used + // in function scopes. + Init Value; +} + +type Constant struct { + token.Position; + Type Type; + Value Value; +} + +// A block represents a definition block in which a name may not be +// defined more than once. +type block struct { + // The block enclosing this one, including blocks in other + // scopes. + outer *block; + // The nested block currently being compiled, or nil. + inner *block; + // The Scope containing this block. + scope *Scope; + // The Variables, Constants, and Types defined in this block. + defs map[string] Def; + // The index of the first variable defined in this block. + // This must be greater than the index of any variable defined + // in any parent of this block within the same Scope at the + // time this block is entered. + offset int; + // The number of Variables defined in this block. + numVars int; + // If global, do not allocate new vars and consts in + // the frame; assume that the refs will be compiled in + // using defs[name].Init. + global bool; +} + +// A Scope is the compile-time analogue of a Frame, which captures +// some subtree of blocks. +type Scope struct { + // The root block of this scope. + *block; + // The maximum number of variables required at any point in + // this Scope. This determines the number of slots needed in + // Frame's created from this Scope at run-time. + maxVars int; +} + +func (b *block) enterChild() *block { + if b.inner != nil && b.inner.scope == b.scope { + log.Crash("Failed to exit child block before entering another child"); + } + sub := &block{ + outer: b, + scope: b.scope, + defs: make(map[string] Def), + offset: b.offset+b.numVars, + }; + b.inner = sub; + return sub; +} + +func (b *block) exit() { + if b.outer == nil { + log.Crash("Cannot exit top-level block"); + } + if b.outer.scope == b.scope { + if b.outer.inner != b { + log.Crash("Already exited block"); + } + if b.inner != nil && b.inner.scope == b.scope { + log.Crash("Exit of parent block without exit of child block"); + } + } + b.outer.inner = nil; +} + +func (b *block) ChildScope() *Scope { + if b.inner != nil && b.inner.scope == b.scope { + log.Crash("Failed to exit child block before entering a child scope"); + } + sub := b.enterChild(); + sub.offset = 0; + sub.scope = &Scope{sub, 0}; + return sub.scope; +} + +func (b *block) DefineVar(name string, pos token.Position, t Type) (*Variable, Def) { + if prev, ok := b.defs[name]; ok { + return nil, prev; + } + v := b.defineSlot(t, false); + v.Position = pos; + b.defs[name] = v; + return v, nil; +} + +func (b *block) DefineTemp(t Type) *Variable { + return b.defineSlot(t, true) +} + +func (b *block) defineSlot(t Type, temp bool) *Variable { + if b.inner != nil && b.inner.scope == b.scope { + log.Crash("Failed to exit child block before defining variable"); + } + index := -1; + if !b.global || temp { + index = b.offset+b.numVars; + b.numVars++; + if index >= b.scope.maxVars { + b.scope.maxVars = index+1; + } + } + v := &Variable{token.Position{}, index, t, nil}; + return v; +} + +func (b *block) DefineConst(name string, pos token.Position, t Type, v Value) (*Constant, Def) { + if prev, ok := b.defs[name]; ok { + return nil, prev; + } + c := &Constant{pos, t, v}; + b.defs[name] = c; + return c, nil; +} + +func (b *block) DefineType(name string, pos token.Position, t Type) Type { + if _, ok := b.defs[name]; ok { + return nil; + } + nt := &NamedType{pos, name, nil, true, make(map[string] Method)}; + if t != nil { + nt.Complete(t); + } + b.defs[name] = nt; + return nt; +} + +func (b *block) Lookup(name string) (bl *block, level int, def Def) { + for b != nil { + if d, ok := b.defs[name]; ok { + return b, level, d; + } + if b.outer != nil && b.scope != b.outer.scope { + level++; + } + b = b.outer; + } + return nil, 0, nil; +} + +func (s *Scope) NewFrame(outer *Frame) *Frame { + return outer.child(s.maxVars); +} + +/* + * Frames + */ + +type Frame struct { + Outer *Frame; + Vars []Value; +} + +func (f *Frame) Get(level int, index int) Value { + for ; level > 0; level-- { + f = f.Outer; + } + return f.Vars[index]; +} + +func (f *Frame) child(numVars int) *Frame { + // TODO(austin) This is probably rather expensive. All values + // require heap allocation and zeroing them when we execute a + // definition typically requires some computation. + return &Frame{f, make([]Value, numVars)}; +} diff --git a/src/pkg/exp/eval/stmt.go b/src/pkg/exp/eval/stmt.go new file mode 100644 index 000000000..9ec6fb83d --- /dev/null +++ b/src/pkg/exp/eval/stmt.go @@ -0,0 +1,1313 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "log"; + "go/ast"; + "go/token"; +) + +const ( + returnPC = ^uint(0); + badPC = ^uint(1); +) + +/* + * Statement compiler + */ + +type stmtCompiler struct { + *blockCompiler; + pos token.Position; + // This statement's label, or nil if it is not labeled. + stmtLabel *label; +} + +func (a *stmtCompiler) diag(format string, args ...) { + a.diagAt(&a.pos, format, args); +} + +/* + * Flow checker + */ + +type flowEnt struct { + // Whether this flow entry is conditional. If true, flow can + // continue to the next PC. + cond bool; + // True if this will terminate flow (e.g., a return statement). + // cond must be false and jumps must be nil if this is true. + term bool; + // PC's that can be reached from this flow entry. + jumps []*uint; + // Whether this flow entry has been visited by reachesEnd. + visited bool; +} + +type flowBlock struct { + // If this is a goto, the target label. + target string; + // The inner-most block containing definitions. + block *block; + // The numVars from each block leading to the root of the + // scope, starting at block. + numVars []int; +} + +type flowBuf struct { + cb *codeBuf; + // ents is a map from PC's to flow entries. Any PC missing + // from this map is assumed to reach only PC+1. + ents map[uint] *flowEnt; + // gotos is a map from goto positions to information on the + // block at the point of the goto. + gotos map[*token.Position] *flowBlock; + // labels is a map from label name to information on the block + // at the point of the label. labels are tracked by name, + // since mutliple labels at the same PC can have different + // blocks. + labels map[string] *flowBlock; +} + +func newFlowBuf(cb *codeBuf) *flowBuf { + return &flowBuf{cb, make(map[uint] *flowEnt), make(map[*token.Position] *flowBlock), make(map[string] *flowBlock)}; +} + +// put creates a flow control point for the next PC in the code buffer. +// This should be done before pushing the instruction into the code buffer. +func (f *flowBuf) put(cond bool, term bool, jumps []*uint) { + pc := f.cb.nextPC(); + if ent, ok := f.ents[pc]; ok { + log.Crashf("Flow entry already exists at PC %d: %+v", pc, ent); + } + f.ents[pc] = &flowEnt{cond, term, jumps, false}; +} + +// putTerm creates a flow control point at the next PC that +// unconditionally terminates execution. +func (f *flowBuf) putTerm() { + f.put(false, true, nil); +} + +// put1 creates a flow control point at the next PC that jumps to one +// PC and, if cond is true, can also continue to the PC following the +// next PC. +func (f *flowBuf) put1(cond bool, jumpPC *uint) { + f.put(cond, false, []*uint {jumpPC}); +} + +func newFlowBlock(target string, b *block) *flowBlock { + // Find the inner-most block containing definitions + for b.numVars == 0 && b.outer != nil && b.outer.scope == b.scope { + b = b.outer; + } + + // Count parents leading to the root of the scope + n := 0; + for bp := b; bp.scope == b.scope; bp = bp.outer { + n++; + } + + // Capture numVars from each block to the root of the scope + numVars := make([]int, n); + i := 0; + for bp := b; i < n; bp = bp.outer { + numVars[i] = bp.numVars; + i++; + } + + return &flowBlock{target, b, numVars}; +} + +// putGoto captures the block at a goto statement. This should be +// called in addition to putting a flow control point. +func (f *flowBuf) putGoto(pos token.Position, target string, b *block) { + f.gotos[&pos] = newFlowBlock(target, b); +} + +// putLabel captures the block at a label. +func (f *flowBuf) putLabel(name string, b *block) { + f.labels[name] = newFlowBlock("", b); +} + +// reachesEnd returns true if the end of f's code buffer can be +// reached from the given program counter. Error reporting is the +// caller's responsibility. +func (f *flowBuf) reachesEnd(pc uint) bool { + endPC := f.cb.nextPC(); + if pc > endPC { + log.Crashf("Reached bad PC %d past end PC %d", pc, endPC); + } + + for ; pc < endPC; pc++ { + ent, ok := f.ents[pc]; + if !ok { + continue; + } + + if ent.visited { + return false; + } + ent.visited = true; + + if ent.term { + return false; + } + + // If anything can reach the end, we can reach the end + // from pc. + for _, j := range ent.jumps { + if f.reachesEnd(*j) { + return true; + } + } + // If the jump was conditional, we can reach the next + // PC, so try reaching the end from it. + if ent.cond { + continue; + } + return false; + } + return true; +} + +// gotosObeyScopes returns true if no goto statement causes any +// variables to come into scope that were not in scope at the point of +// the goto. Reports any errors using the given compiler. +func (f *flowBuf) gotosObeyScopes(a *compiler) { + for pos, src := range f.gotos { + tgt := f.labels[src.target]; + + // The target block must be a parent of this block + numVars := src.numVars; + b := src.block; + for len(numVars) > 0 && b != tgt.block { + b = b.outer; + numVars = numVars[1:len(numVars)]; + } + if b != tgt.block { + // We jumped into a deeper block + a.diagAt(pos, "goto causes variables to come into scope"); + return; + } + + // There must be no variables in the target block that + // did not exist at the jump + tgtNumVars := tgt.numVars; + for i := range numVars { + if tgtNumVars[i] > numVars[i] { + a.diagAt(pos, "goto causes variables to come into scope"); + return; + } + } + } +} + +/* + * Statement generation helpers + */ + +func (a *stmtCompiler) defineVar(ident *ast.Ident, t Type) *Variable { + v, prev := a.block.DefineVar(ident.Value, ident.Pos(), t); + if prev != nil { + // TODO(austin) It's silly that we have to capture + // Pos() in a variable. + pos := prev.Pos(); + if pos.IsValid() { + a.diagAt(ident, "variable %s redeclared in this block\n\tprevious declaration at %s", ident.Value, &pos); + } else { + a.diagAt(ident, "variable %s redeclared in this block", ident.Value); + } + return nil; + } + + // Initialize the variable + index := v.Index; + if v.Index >= 0 { + a.push(func(v *Thread) { + v.f.Vars[index] = t.Zero(); + }); + } + return v; +} + +// TODO(austin) Move doAssign to here + +/* + * Statement compiler + */ + +func (a *stmtCompiler) compile(s ast.Stmt) { + if a.block.inner != nil { + log.Crash("Child scope still entered"); + } + + notimpl := false; + switch s := s.(type) { + case *ast.BadStmt: + // Error already reported by parser. + a.silentErrors++; + + case *ast.DeclStmt: + a.compileDeclStmt(s); + + case *ast.EmptyStmt: + // Do nothing. + + case *ast.LabeledStmt: + a.compileLabeledStmt(s); + + case *ast.ExprStmt: + a.compileExprStmt(s); + + case *ast.IncDecStmt: + a.compileIncDecStmt(s); + + case *ast.AssignStmt: + a.compileAssignStmt(s); + + case *ast.GoStmt: + notimpl = true; + + case *ast.DeferStmt: + notimpl = true; + + case *ast.ReturnStmt: + a.compileReturnStmt(s); + + case *ast.BranchStmt: + a.compileBranchStmt(s); + + case *ast.BlockStmt: + a.compileBlockStmt(s); + + case *ast.IfStmt: + a.compileIfStmt(s); + + case *ast.CaseClause: + a.diag("case clause outside switch"); + + case *ast.SwitchStmt: + a.compileSwitchStmt(s); + + case *ast.TypeCaseClause: + notimpl = true; + + case *ast.TypeSwitchStmt: + notimpl = true; + + case *ast.CommClause: + notimpl = true; + + case *ast.SelectStmt: + notimpl = true; + + case *ast.ForStmt: + a.compileForStmt(s); + + case *ast.RangeStmt: + notimpl = true; + + default: + log.Crashf("unexpected ast node type %T", s); + } + + if notimpl { + a.diag("%T statment node not implemented", s); + } + + if a.block.inner != nil { + log.Crash("Forgot to exit child scope"); + } +} + +func (a *stmtCompiler) compileDeclStmt(s *ast.DeclStmt) { + switch decl := s.Decl.(type) { + case *ast.BadDecl: + // Do nothing. Already reported by parser. + a.silentErrors++; + + case *ast.FuncDecl: + if !a.block.global { + log.Crash("FuncDecl at statement level"); + } + + case *ast.GenDecl: + if decl.Tok == token.IMPORT && !a.block.global { + log.Crash("import at statement level"); + } + + default: + log.Crashf("Unexpected Decl type %T", s.Decl); + } + a.compileDecl(s.Decl); +} + +func (a *stmtCompiler) compileVarDecl(decl *ast.GenDecl) { + for _, spec := range decl.Specs { + spec := spec.(*ast.ValueSpec); + if spec.Values == nil { + // Declaration without assignment + if spec.Type == nil { + // Parser should have caught + log.Crash("Type and Values nil"); + } + t := a.compileType(a.block, spec.Type); + // Define placeholders even if type compile failed + for _, n := range spec.Names { + a.defineVar(n, t); + } + } else { + // Declaration with assignment + lhs := make([]ast.Expr, len(spec.Names)); + for i, n := range spec.Names { + lhs[i] = n; + } + a.doAssign(lhs, spec.Values, decl.Tok, spec.Type); + } + } +} + +func (a *stmtCompiler) compileDecl(decl ast.Decl) { + switch d := decl.(type) { + case *ast.BadDecl: + // Do nothing. Already reported by parser. + a.silentErrors++; + + case *ast.FuncDecl: + decl := a.compileFuncType(a.block, d.Type); + if decl == nil { + return; + } + // Declare and initialize v before compiling func + // so that body can refer to itself. + c, prev := a.block.DefineConst(d.Name.Value, a.pos, decl.Type, decl.Type.Zero()); + if prev != nil { + pos := prev.Pos(); + if pos.IsValid() { + a.diagAt(d.Name, "identifier %s redeclared in this block\n\tprevious declaration at %s", d.Name.Value, &pos); + } else { + a.diagAt(d.Name, "identifier %s redeclared in this block", d.Name.Value); + } + } + fn := a.compileFunc(a.block, decl, d.Body); + if c == nil || fn == nil { + return; + } + var zeroThread Thread; + c.Value.(FuncValue).Set(nil, fn(&zeroThread)); + + case *ast.GenDecl: + switch d.Tok { + case token.IMPORT: + log.Crashf("%v not implemented", d.Tok); + case token.CONST: + log.Crashf("%v not implemented", d.Tok); + case token.TYPE: + a.compileTypeDecl(a.block, d); + case token.VAR: + a.compileVarDecl(d); + } + + default: + log.Crashf("Unexpected Decl type %T", decl); + } +} + +func (a *stmtCompiler) compileLabeledStmt(s *ast.LabeledStmt) { + // Define label + l, ok := a.labels[s.Label.Value]; + if ok { + if l.resolved.IsValid() { + a.diag("label %s redeclared in this block\n\tprevious declaration at %s", s.Label.Value, &l.resolved); + } + } else { + pc := badPC; + l = &label{name: s.Label.Value, gotoPC: &pc}; + a.labels[l.name] = l; + } + l.desc = "regular label"; + l.resolved = s.Pos(); + + // Set goto PC + *l.gotoPC = a.nextPC(); + + // Define flow entry so we can check for jumps over declarations. + a.flow.putLabel(l.name, a.block); + + // Compile the statement. Reuse our stmtCompiler for simplicity. + sc := &stmtCompiler{a.blockCompiler, s.Stmt.Pos(), l}; + sc.compile(s.Stmt); +} + +func (a *stmtCompiler) compileExprStmt(s *ast.ExprStmt) { + bc := a.enterChild(); + defer bc.exit(); + + e := a.compileExpr(bc.block, false, s.X); + if e == nil { + return; + } + + if e.exec == nil { + a.diag("%s cannot be used as expression statement", e.desc); + return; + } + + a.push(e.exec); +} + +func (a *stmtCompiler) compileIncDecStmt(s *ast.IncDecStmt) { + // Create temporary block for extractEffect + bc := a.enterChild(); + defer bc.exit(); + + l := a.compileExpr(bc.block, false, s.X); + if l == nil { + return; + } + + if l.evalAddr == nil { + l.diag("cannot assign to %s", l.desc); + return; + } + if !(l.t.isInteger() || l.t.isFloat()) { + l.diagOpType(s.Tok, l.t); + return; + } + + var op token.Token; + var desc string; + switch s.Tok { + case token.INC: + op = token.ADD; + desc = "increment statement"; + case token.DEC: + op = token.SUB; + desc = "decrement statement"; + default: + log.Crashf("Unexpected IncDec token %v", s.Tok); + } + + effect, l := l.extractEffect(bc.block, desc); + + one := l.newExpr(IdealIntType, "constant"); + one.pos = s.Pos(); + one.eval = func() *bignum.Integer { return bignum.Int(1) }; + + binop := l.compileBinaryExpr(op, l, one); + if binop == nil { + return; + } + + assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "", ""); + if assign == nil { + log.Crashf("compileAssign type check failed"); + } + + lf := l.evalAddr; + a.push(func(v *Thread) { + effect(v); + assign(lf(v), v); + }); +} + +func (a *stmtCompiler) doAssign(lhs []ast.Expr, rhs []ast.Expr, tok token.Token, declTypeExpr ast.Expr) { + nerr := a.numError(); + + // Compile right side first so we have the types when + // compiling the left side and so we don't see definitions + // made on the left side. + rs := make([]*expr, len(rhs)); + for i, re := range rhs { + rs[i] = a.compileExpr(a.block, false, re); + } + + errOp := "assignment"; + if tok == token.DEFINE || tok == token.VAR { + errOp = "declaration"; + } + ac, ok := a.checkAssign(a.pos, rs, errOp, "value"); + ac.allowMapForms(len(lhs)); + + // If this is a definition and the LHS is too big, we won't be + // able to produce the usual error message because we can't + // begin to infer the types of the LHS. + if (tok == token.DEFINE || tok == token.VAR) && len(lhs) > len(ac.rmt.Elems) { + a.diag("not enough values for definition"); + } + + // Compile left type if there is one + var declType Type; + if declTypeExpr != nil { + declType = a.compileType(a.block, declTypeExpr); + } + + // Compile left side + ls := make([]*expr, len(lhs)); + nDefs := 0; + for i, le := range lhs { + // If this is a definition, get the identifier and its type + var ident *ast.Ident; + var lt Type; + switch tok { + case token.DEFINE: + // Check that it's an identifier + ident, ok = le.(*ast.Ident); + if !ok { + a.diagAt(le, "left side of := must be a name"); + // Suppress new defitions errors + nDefs++; + continue; + } + + // Is this simply an assignment? + if _, ok := a.block.defs[ident.Value]; ok { + ident = nil; + break; + } + nDefs++; + + case token.VAR: + ident = le.(*ast.Ident); + } + + // If it's a definition, get or infer its type. + if ident != nil { + // Compute the identifier's type from the RHS + // type. We use the computed MultiType so we + // don't have to worry about unpacking. + switch { + case declTypeExpr != nil: + // We have a declaration type, use it. + // If declType is nil, we gave an + // error when we compiled it. + lt = declType; + + case i >= len(ac.rmt.Elems): + // Define a placeholder. We already + // gave the "not enough" error above. + lt = nil; + + case ac.rmt.Elems[i] == nil: + // We gave the error when we compiled + // the RHS. + lt = nil; + + case ac.rmt.Elems[i].isIdeal(): + // If the type is absent and the + // corresponding expression is a + // constant expression of ideal + // integer or ideal float type, the + // type of the declared variable is + // int or float respectively. + switch { + case ac.rmt.Elems[i].isInteger(): + lt = IntType; + case ac.rmt.Elems[i].isFloat(): + lt = FloatType; + default: + log.Crashf("unexpected ideal type %v", rs[i].t); + } + + default: + lt = ac.rmt.Elems[i]; + } + } + + // If it's a definition, define the identifier + if ident != nil { + if a.defineVar(ident, lt) == nil { + continue; + } + } + + // Compile LHS + ls[i] = a.compileExpr(a.block, false, le); + if ls[i] == nil { + continue; + } + + if ls[i].evalMapValue != nil { + // Map indexes are not generally addressable, + // but they are assignable. + // + // TODO(austin) Now that the expression + // compiler uses semantic values, this might + // be easier to implement as a function call. + sub := ls[i]; + ls[i] = ls[i].newExpr(sub.t, sub.desc); + ls[i].evalMapValue = sub.evalMapValue; + mvf := sub.evalMapValue; + et := sub.t; + ls[i].evalAddr = func(t *Thread) Value { + m, k := mvf(t); + e := m.Elem(t, k); + if e == nil { + e = et.Zero(); + m.SetElem(t, k, e); + } + return e; + }; + } else if ls[i].evalAddr == nil { + ls[i].diag("cannot assign to %s", ls[i].desc); + continue; + } + } + + // A short variable declaration may redeclare variables + // provided they were originally declared in the same block + // with the same type, and at least one of the variables is + // new. + if tok == token.DEFINE && nDefs == 0 { + a.diag("at least one new variable must be declared"); + return; + } + + // If there have been errors, our arrays are full of nil's so + // get out of here now. + if nerr != a.numError() { + return; + } + + // Check for 'a[x] = r, ok' + if len(ls) == 1 && len(rs) == 2 && ls[0].evalMapValue != nil { + a.diag("a[x] = r, ok form not implemented"); + return; + } + + // Create assigner + var lt Type; + n := len(lhs); + if n == 1 { + lt = ls[0].t; + } else { + lts := make([]Type, len(ls)); + for i, l := range ls { + if l != nil { + lts[i] = l.t; + } + } + lt = NewMultiType(lts); + } + bc := a.enterChild(); + defer bc.exit(); + assign := ac.compile(bc.block, lt); + if assign == nil { + return; + } + + // Compile + if n == 1 { + // Don't need temporaries and can avoid []Value. + lf := ls[0].evalAddr; + a.push(func(t *Thread) { assign(lf(t), t) }); + } else if tok == token.VAR || (tok == token.DEFINE && nDefs == n) { + // Don't need temporaries + lfs := make([]func(*Thread) Value, n); + for i, l := range ls { + lfs[i] = l.evalAddr; + } + a.push(func(t *Thread) { + dest := make([]Value, n); + for i, lf := range lfs { + dest[i] = lf(t); + } + assign(multiV(dest), t); + }); + } else { + // Need temporaries + lmt := lt.(*MultiType); + lfs := make([]func(*Thread) Value, n); + for i, l := range ls { + lfs[i] = l.evalAddr; + } + a.push(func(t *Thread) { + temp := lmt.Zero().(multiV); + assign(temp, t); + // Copy to destination + for i := 0; i < n; i ++ { + // TODO(austin) Need to evaluate LHS + // before RHS + lfs[i](t).Assign(t, temp[i]); + } + }); + } +} + +var assignOpToOp = map[token.Token] token.Token { + token.ADD_ASSIGN : token.ADD, + token.SUB_ASSIGN : token.SUB, + token.MUL_ASSIGN : token.MUL, + token.QUO_ASSIGN : token.QUO, + token.REM_ASSIGN : token.REM, + + token.AND_ASSIGN : token.AND, + token.OR_ASSIGN : token.OR, + token.XOR_ASSIGN : token.XOR, + token.SHL_ASSIGN : token.SHL, + token.SHR_ASSIGN : token.SHR, + token.AND_NOT_ASSIGN : token.AND_NOT, +} + +func (a *stmtCompiler) doAssignOp(s *ast.AssignStmt) { + if len(s.Lhs) != 1 || len(s.Rhs) != 1 { + a.diag("tuple assignment cannot be combined with an arithmetic operation"); + return; + } + + // Create temporary block for extractEffect + bc := a.enterChild(); + defer bc.exit(); + + l := a.compileExpr(bc.block, false, s.Lhs[0]); + r := a.compileExpr(bc.block, false, s.Rhs[0]); + if l == nil || r == nil { + return; + } + + if l.evalAddr == nil { + l.diag("cannot assign to %s", l.desc); + return; + } + + effect, l := l.extractEffect(bc.block, "operator-assignment"); + + binop := r.compileBinaryExpr(assignOpToOp[s.Tok], l, r); + if binop == nil { + return; + } + + assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "assignment", "value"); + if assign == nil { + log.Crashf("compileAssign type check failed"); + } + + lf := l.evalAddr; + a.push(func(t *Thread) { + effect(t); + assign(lf(t), t); + }); +} + +func (a *stmtCompiler) compileAssignStmt(s *ast.AssignStmt) { + switch s.Tok { + case token.ASSIGN, token.DEFINE: + a.doAssign(s.Lhs, s.Rhs, s.Tok, nil); + + default: + a.doAssignOp(s); + } +} + +func (a *stmtCompiler) compileReturnStmt(s *ast.ReturnStmt) { + if a.fnType == nil { + a.diag("cannot return at the top level"); + return; + } + + if len(s.Results) == 0 && (len(a.fnType.Out) == 0 || a.outVarsNamed) { + // Simple case. Simply exit from the function. + a.flow.putTerm(); + a.push(func(v *Thread) { v.pc = returnPC }); + return; + } + + bc := a.enterChild(); + defer bc.exit(); + + // Compile expressions + bad := false; + rs := make([]*expr, len(s.Results)); + for i, re := range s.Results { + rs[i] = a.compileExpr(bc.block, false, re); + if rs[i] == nil { + bad = true; + } + } + if bad { + return; + } + + // Create assigner + + // However, if the expression list in the "return" statement + // is a single call to a multi-valued function, the values + // returned from the called function will be returned from + // this one. + assign := a.compileAssign(s.Pos(), bc.block, NewMultiType(a.fnType.Out), rs, "return", "value"); + + // XXX(Spec) "The result types of the current function and the + // called function must match." Match is fuzzy. It should + // say that they must be assignment compatible. + + // Compile + start := len(a.fnType.In); + nout := len(a.fnType.Out); + a.flow.putTerm(); + a.push(func(t *Thread) { + assign(multiV(t.f.Vars[start:start+nout]), t); + t.pc = returnPC; + }); +} + +func (a *stmtCompiler) findLexicalLabel(name *ast.Ident, pred func(*label) bool, errOp, errCtx string) *label { + bc := a.blockCompiler; + for ; bc != nil; bc = bc.parent { + if bc.label == nil { + continue; + } + l := bc.label; + if name == nil && pred(l) { + return l; + } + if name != nil && l.name == name.Value { + if !pred(l) { + a.diag("cannot %s to %s %s", errOp, l.desc, l.name); + return nil; + } + return l; + } + } + if name == nil { + a.diag("%s outside %s", errOp, errCtx); + } else { + a.diag("%s label %s not defined", errOp, name.Value); + } + return nil; +} + +func (a *stmtCompiler) compileBranchStmt(s *ast.BranchStmt) { + var pc *uint; + + switch s.Tok { + case token.BREAK: + l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.breakPC != nil }, "break", "for loop, switch, or select"); + if l == nil { + return; + } + pc = l.breakPC; + + case token.CONTINUE: + l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.continuePC != nil }, "continue", "for loop"); + if l == nil { + return; + } + pc = l.continuePC; + + case token.GOTO: + l, ok := a.labels[s.Label.Value]; + if !ok { + pc := badPC; + l = &label{name: s.Label.Value, desc: "unresolved label", gotoPC: &pc, used: s.Pos()}; + a.labels[l.name] = l; + } + + pc = l.gotoPC; + a.flow.putGoto(s.Pos(), l.name, a.block); + + case token.FALLTHROUGH: + a.diag("fallthrough outside switch"); + return; + + default: + log.Crash("Unexpected branch token %v", s.Tok); + } + + a.flow.put1(false, pc); + a.push(func(v *Thread) { v.pc = *pc }); +} + +func (a *stmtCompiler) compileBlockStmt(s *ast.BlockStmt) { + bc := a.enterChild(); + bc.compileStmts(s); + bc.exit(); +} + +func (a *stmtCompiler) compileIfStmt(s *ast.IfStmt) { + // The scope of any variables declared by [the init] statement + // extends to the end of the "if" statement and the variables + // are initialized once before the statement is entered. + // + // XXX(Spec) What this really wants to say is that there's an + // implicit scope wrapping every if, for, and switch + // statement. This is subtly different from what it actually + // says when there's a non-block else clause, because that + // else claus has to execute in a scope that is *not* the + // surrounding scope. + bc := a.enterChild(); + defer bc.exit(); + + // Compile init statement, if any + if s.Init != nil { + bc.compileStmt(s.Init); + } + + elsePC := badPC; + endPC := badPC; + + // Compile condition, if any. If there is no condition, we + // fall through to the body. + if s.Cond != nil { + e := bc.compileExpr(bc.block, false, s.Cond); + switch { + case e == nil: + // Error reported by compileExpr + case !e.t.isBoolean(): + e.diag("'if' condition must be boolean\n\t%v", e.t); + default: + eval := e.asBool(); + a.flow.put1(true, &elsePC); + a.push(func(t *Thread) { + if !eval(t) { + t.pc = elsePC; + } + }); + } + } + + // Compile body + body := bc.enterChild(); + body.compileStmts(s.Body); + body.exit(); + + // Compile else + if s.Else != nil { + // Skip over else if we executed the body + a.flow.put1(false, &endPC); + a.push(func(v *Thread) { + v.pc = endPC; + }); + elsePC = a.nextPC(); + bc.compileStmt(s.Else); + } else { + elsePC = a.nextPC(); + } + endPC = a.nextPC(); +} + +func (a *stmtCompiler) compileSwitchStmt(s *ast.SwitchStmt) { + // Create implicit scope around switch + bc := a.enterChild(); + defer bc.exit(); + + // Compile init statement, if any + if s.Init != nil { + bc.compileStmt(s.Init); + } + + // Compile condition, if any, and extract its effects + var cond *expr; + condbc := bc.enterChild(); + if s.Tag != nil { + e := condbc.compileExpr(condbc.block, false, s.Tag); + if e != nil { + var effect func(*Thread); + effect, cond = e.extractEffect(condbc.block, "switch"); + a.push(effect); + } + } + + // Count cases + ncases := 0; + hasDefault := false; + for _, c := range s.Body.List { + clause, ok := c.(*ast.CaseClause); + if !ok { + a.diagAt(clause, "switch statement must contain case clauses"); + continue; + } + if clause.Values == nil { + if hasDefault { + a.diagAt(clause, "switch statement contains more than one default case"); + } + hasDefault = true; + } else { + ncases += len(clause.Values); + } + } + + // Compile case expressions + cases := make([]func(*Thread) bool, ncases); + i := 0; + for _, c := range s.Body.List { + clause, ok := c.(*ast.CaseClause); + if !ok { + continue; + } + for _, v := range clause.Values { + e := condbc.compileExpr(condbc.block, false, v); + switch { + case e == nil: + // Error reported by compileExpr + case cond == nil && !e.t.isBoolean(): + a.diagAt(v, "'case' condition must be boolean"); + case cond == nil: + cases[i] = e.asBool(); + case cond != nil: + // Create comparison + // TOOD(austin) This produces bad error messages + compare := e.compileBinaryExpr(token.EQL, cond, e); + if compare != nil { + cases[i] = compare.asBool(); + } + } + i++; + } + } + + // Emit condition + casePCs := make([]*uint, ncases+1); + endPC := badPC; + + a.flow.put(false, false, casePCs); + a.push(func(t *Thread) { + for i, c := range cases { + if c(t) { + t.pc = *casePCs[i]; + return; + } + } + t.pc = *casePCs[ncases]; + }); + condbc.exit(); + + // Compile cases + i = 0; + for _, c := range s.Body.List { + clause, ok := c.(*ast.CaseClause); + if !ok { + continue; + } + + // Save jump PC's + pc := a.nextPC(); + if clause.Values != nil { + for _ = range clause.Values { + casePCs[i] = &pc; + i++; + } + } else { + // Default clause + casePCs[ncases] = &pc; + } + + // Compile body + fall := false; + for j, s := range clause.Body { + if br, ok := s.(*ast.BranchStmt); ok && br.Tok == token.FALLTHROUGH { + // println("Found fallthrough"); + // It may be used only as the final + // non-empty statement in a case or + // default clause in an expression + // "switch" statement. + for _, s2 := range clause.Body[j+1:len(clause.Body)] { + // XXX(Spec) 6g also considers + // empty blocks to be empty + // statements. + if _, ok := s2.(*ast.EmptyStmt); !ok { + a.diagAt(s, "fallthrough statement must be final statement in case"); + break; + } + } + fall = true; + } else { + bc.compileStmt(s); + } + } + // Jump out of switch, unless there was a fallthrough + if !fall { + a.flow.put1(false, &endPC); + a.push(func(v *Thread) { v.pc = endPC }); + } + } + + // Get end PC + endPC = a.nextPC(); + if !hasDefault { + casePCs[ncases] = &endPC; + } +} + +func (a *stmtCompiler) compileForStmt(s *ast.ForStmt) { + // Wrap the entire for in a block. + bc := a.enterChild(); + defer bc.exit(); + + // Compile init statement, if any + if s.Init != nil { + bc.compileStmt(s.Init); + } + + bodyPC := badPC; + postPC := badPC; + checkPC := badPC; + endPC := badPC; + + // Jump to condition check. We generate slightly less code by + // placing the condition check after the body. + a.flow.put1(false, &checkPC); + a.push(func(v *Thread) { v.pc = checkPC }); + + // Compile body + bodyPC = a.nextPC(); + body := bc.enterChild(); + if a.stmtLabel != nil { + body.label = a.stmtLabel; + } else { + body.label = &label{resolved: s.Pos()}; + } + body.label.desc = "for loop"; + body.label.breakPC = &endPC; + body.label.continuePC = &postPC; + body.compileStmts(s.Body); + body.exit(); + + // Compile post, if any + postPC = a.nextPC(); + if s.Post != nil { + // TODO(austin) Does the parser disallow short + // declarations in s.Post? + bc.compileStmt(s.Post); + } + + // Compile condition check, if any + checkPC = a.nextPC(); + if s.Cond == nil { + // If the condition is absent, it is equivalent to true. + a.flow.put1(false, &bodyPC); + a.push(func(v *Thread) { v.pc = bodyPC }); + } else { + e := bc.compileExpr(bc.block, false, s.Cond); + switch { + case e == nil: + // Error reported by compileExpr + case !e.t.isBoolean(): + a.diag("'for' condition must be boolean\n\t%v", e.t); + default: + eval := e.asBool(); + a.flow.put1(true, &bodyPC); + a.push(func(t *Thread) { + if eval(t) { + t.pc = bodyPC; + } + }); + } + } + + endPC = a.nextPC(); +} + +/* + * Block compiler + */ + +func (a *blockCompiler) compileStmt(s ast.Stmt) { + sc := &stmtCompiler{a, s.Pos(), nil}; + sc.compile(s); +} + +func (a *blockCompiler) compileStmts(block *ast.BlockStmt) { + for _, sub := range block.List { + a.compileStmt(sub); + } +} + +func (a *blockCompiler) enterChild() *blockCompiler { + block := a.block.enterChild(); + return &blockCompiler{ + funcCompiler: a.funcCompiler, + block: block, + parent: a, + }; +} + +func (a *blockCompiler) exit() { + a.block.exit(); +} + +/* + * Function compiler + */ + +func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (func (*Thread) Func) { + // Create body scope + // + // The scope of a parameter or result is the body of the + // corresponding function. + bodyScope := b.ChildScope(); + defer bodyScope.exit(); + for i, t := range decl.Type.In { + if decl.InNames[i] != nil { + bodyScope.DefineVar(decl.InNames[i].Value, decl.InNames[i].Pos(), t); + } else { + bodyScope.DefineTemp(t); + } + } + for i, t := range decl.Type.Out { + if decl.OutNames[i] != nil { + bodyScope.DefineVar(decl.OutNames[i].Value, decl.OutNames[i].Pos(), t); + } else { + bodyScope.DefineTemp(t); + } + } + + // Create block context + cb := newCodeBuf(); + fc := &funcCompiler{ + compiler: a, + fnType: decl.Type, + outVarsNamed: len(decl.OutNames) > 0 && decl.OutNames[0] != nil, + codeBuf: cb, + flow: newFlowBuf(cb), + labels: make(map[string] *label), + }; + bc := &blockCompiler{ + funcCompiler: fc, + block: bodyScope.block, + }; + + // Compile body + nerr := a.numError(); + bc.compileStmts(body); + fc.checkLabels(); + if nerr != a.numError() { + return nil; + } + + // Check that the body returned if necessary. We only check + // this if there were no errors compiling the body. + if len(decl.Type.Out) > 0 && fc.flow.reachesEnd(0) { + // XXX(Spec) Not specified. + a.diagAt(&body.Rbrace, "function ends without a return statement"); + return nil; + } + + code := fc.get(); + maxVars := bodyScope.maxVars; + return func(t *Thread) Func { return &evalFunc{t.f, maxVars, code} }; +} + +// Checks that labels were resolved and that all jumps obey scoping +// rules. Reports an error and set fc.err if any check fails. +func (a *funcCompiler) checkLabels() { + nerr := a.numError(); + for _, l := range a.labels { + if !l.resolved.IsValid() { + a.diagAt(&l.used, "label %s not defined", l.name); + } + } + if nerr != a.numError() { + // Don't check scopes if we have unresolved labels + return; + } + + // Executing the "goto" statement must not cause any variables + // to come into scope that were not already in scope at the + // point of the goto. + a.flow.gotosObeyScopes(a.compiler); +} diff --git a/src/pkg/exp/eval/stmt_test.go b/src/pkg/exp/eval/stmt_test.go new file mode 100644 index 000000000..e94adfb60 --- /dev/null +++ b/src/pkg/exp/eval/stmt_test.go @@ -0,0 +1,343 @@ +// Copyright 2009 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 eval + +import "testing" + +var atLeastOneDecl = "at least one new variable must be declared"; + +var stmtTests = []test { + // Short declarations + Val1("x := i", "x", 1), + Val1("x := f", "x", 1.0), + // Type defaulting + Val1("a := 42", "a", 42), + Val1("a := 1.0", "a", 1.0), + // Parallel assignment + Val2("a, b := 1, 2", "a", 1, "b", 2), + Val2("a, i := 1, 2", "a", 1, "i", 2), + CErr("a, i := 1, f", opTypes), + // TODO(austin) The parser produces an error message for this + // one that's inconsistent with the errors I give for other + // things + //CErr("a, b := 1, 2, 3", "too many"), + CErr("a, b := 1, 2, 3", "arity"), + CErr("a := 1, 2", "too many"), + CErr("a, b := 1", "not enough"), + // Mixed declarations + CErr("i := 1", atLeastOneDecl), + CErr("i, u := 1, 2", atLeastOneDecl), + Val2("i, x := 2, f", "i", 2, "x", 1.0), + // Various errors + CErr("1 := 2", "left side of := must be a name"), + CErr("c, a := 1, 1", "cannot assign"), + // Unpacking + Val2("x, y := oneTwo()", "x", 1, "y", 2), + CErr("x := oneTwo()", "too many"), + CErr("x, y, z := oneTwo()", "not enough"), + CErr("x, y := oneTwo(), 2", "multi-valued"), + CErr("x := oneTwo()+2", opTypes), + // TOOD(austin) This error message is weird + CErr("x := void()", "not enough"), + // Placeholders + CErr("x := 1+\"x\"; i=x+1", opTypes), + + // Assignment + Val1("i = 2", "i", 2), + Val1("(i) = 2", "i", 2), + CErr("1 = 2", "cannot assign"), + CErr("1-1 = 2", "- expression"), + Val1("i = 2.0", "i", 2), + CErr("i = 2.2", constantTruncated), + CErr("u = -2", constantUnderflows), + CErr("i = f", opTypes), + CErr("i, u = 0, f", opTypes), + CErr("i, u = 0, f", "value 2"), + Val2("i, i2 = i2, i", "i", 2, "i2", 1), + CErr("c = 1", "cannot assign"), + + Val1("x := &i; *x = 2", "i", 2), + + Val1("ai[0] = 42", "ai", varray{ 42, 2 }), + Val1("aai[1] = ai; ai[0] = 42", "aai", varray{ varray{1, 2}, varray{1, 2} }), + Val1("aai = aai2", "aai", varray{ varray{5, 6}, varray{7, 8} }), + + // Assignment conversions + Run("var sl []int; sl = &ai"), + CErr("type ST []int; type AT *[2]int; var x AT = &ai; var y ST = x", opTypes), + Run("type ST []int; var y ST = &ai"), + Run("type AT *[2]int; var x AT = &ai; var y []int = x"), + + // Op-assignment + Val1("i += 2", "i", 3), + Val("i", 1), + Val1("f += 2", "f", 3.0), + CErr("2 += 2", "cannot assign"), + CErr("i, j += 2", "cannot be combined"), + CErr("i += 2, 3", "cannot be combined"), + Val2("s2 := s; s += \"def\"", "s2", "abc", "s", "abcdef"), + CErr("s += 1", opTypes), + // Single evaluation + Val2("ai[func()int{i+=1;return 0}()] *= 3; i2 = ai[0]", "i", 2, "i2", 3), + + // Type declarations + // Identifiers + Run("type T int"), + CErr("type T x", "undefined"), + CErr("type T c", "constant"), + CErr("type T i", "variable"), + CErr("type T T", "recursive"), + CErr("type T x; type U T; var v U; v = 1", "undefined"), + // Pointer types + Run("type T *int"), + Run("type T *T"), + // Array types + Run("type T [5]int"), + Run("type T [c+42/2]int"), + Run("type T [2.0]int"), + CErr("type T [i]int", "constant expression"), + CErr("type T [2.5]int", constantTruncated), + CErr("type T [-1]int", "negative"), + CErr("type T [2]T", "recursive"), + // Struct types + Run("type T struct { a int; b int }"), + Run("type T struct { a int; int }"), + Run("type T struct { x *T }"), + Run("type T int; type U struct { T }"), + CErr("type T *int; type U struct { T }", "embedded.*pointer"), + CErr("type T *struct { T }", "embedded.*pointer"), + CErr("type T struct { a int; a int }", " a .*redeclared.*:1:17"), + CErr("type T struct { int; int }", "int .*redeclared.*:1:17"), + CErr("type T struct { int int; int }", "int .*redeclared.*:1:17"), + Run("type T struct { x *struct { T } }"), + CErr("type T struct { x struct { T } }", "recursive"), + CErr("type T struct { x }; type U struct { T }", "undefined"), + // Function types + Run("type T func()"), + Run("type T func(a, b int) int"), + Run("type T func(a, b int) (x int, y int)"), + Run("type T func(a, a int) (a int, a int)"), + Run("type T func(a, b int) (x, y int)"), + Run("type T func(int, int) (int, int)"), + CErr("type T func(x); type U T", "undefined"), + CErr("type T func(a T)", "recursive"), + // Parens + Run("type T (int)"), + + // Variable declarations + Val2("var x int", "i", 1, "x", 0), + Val1("var x = 1", "x", 1), + Val1("var x = 1.0", "x", 1.0), + Val1("var x int = 1.0", "x", 1), + // Placeholders + CErr("var x foo; x = 1", "undefined"), + CErr("var x foo = 1; x = 1", "undefined"), + // Redeclaration + CErr("var i, x int", " i .*redeclared"), + CErr("var x int; var x int", " x .*redeclared.*:1:5"), + + // Expression statements + CErr("x := func(){ 1-1 }", "expression statement"), + CErr("x := func(){ 1-1 }", "- expression"), + Val1("fn(2)", "i", 1), + + // IncDec statements + Val1("i++", "i", 2), + Val1("i--", "i", 0), + Val1("u++", "u", uint(2)), + Val1("u--", "u", uint(0)), + Val1("f++", "f", 2.0), + Val1("f--", "f", 0.0), + // Single evaluation + Val2("ai[func()int{i+=1;return 0}()]++; i2 = ai[0]", "i", 2, "i2", 2), + // Operand types + CErr("s++", opTypes), + CErr("s++", "'\\+\\+'"), + CErr("2++", "cannot assign"), + CErr("c++", "cannot assign"), + + // Function scoping + Val1("fn1 := func() { i=2 }; fn1()", "i", 2), + Val1("fn1 := func() { i:=2 }; fn1()", "i", 1), + Val2("fn1 := func() int { i=2; i:=3; i=4; return i }; x := fn1()", "i", 2, "x", 4), + + // Basic returns + CErr("fn1 := func() int {}", "return"), + Run("fn1 := func() {}"), + CErr("fn1 := func() (r int) {}", "return"), + Val1("fn1 := func() (r int) {return}; i = fn1()", "i", 0), + Val1("fn1 := func() (r int) {r = 2; return}; i = fn1()", "i", 2), + Val1("fn1 := func() (r int) {return 2}; i = fn1()", "i", 2), + Val1("fn1 := func(int) int {return 2}; i = fn1(1)", "i", 2), + + // Multi-valued returns + Val2("fn1 := func() (bool, int) {return true, 2}; x, y := fn1()", "x", true, "y", 2), + CErr("fn1 := func() int {return}", "not enough values"), + CErr("fn1 := func() int {return 1,2}", "too many values"), + CErr("fn1 := func() {return 1}", "too many values"), + CErr("fn1 := func() (int,int,int) {return 1,2}", "not enough values"), + Val2("fn1 := func() (int, int) {return oneTwo()}; x, y := fn1()", "x", 1, "y", 2), + CErr("fn1 := func() int {return oneTwo()}", "too many values"), + CErr("fn1 := func() (int,int,int) {return oneTwo()}", "not enough values"), + Val1("fn1 := func(x,y int) int {return x+y}; x := fn1(oneTwo())", "x", 3), + + // Return control flow + Val2("fn1 := func(x *int) bool { *x = 2; return true; *x = 3; }; x := fn1(&i)", "i", 2, "x", true), + + // Break/continue/goto/fallthrough + CErr("break", "outside"), + CErr("break foo", "break.*foo.*not defined"), + CErr("continue", "outside"), + CErr("continue foo", "continue.*foo.*not defined"), + CErr("fallthrough", "outside"), + CErr("goto foo", "foo.*not defined"), + CErr(" foo: foo:;", "foo.*redeclared.*:1:2"), + Val1("i+=2; goto L; i+=4; L: i+=8", "i", 1+2+8), + // Return checking + CErr("fn1 := func() int { goto L; return 1; L: }", "return"), + Run("fn1 := func() int { L: goto L; i = 2 }"), + Run("fn1 := func() int { return 1; L: goto L }"), + // Scope checking + Run("fn1 := func() { { L: x:=1 } goto L }"), + CErr("fn1 := func() { { x:=1; L: } goto L }", "into scope"), + CErr("fn1 := func() { goto L; x:=1; L: }", "into scope"), + Run("fn1 := func() { goto L; { L: x:=1 } }"), + CErr("fn1 := func() { goto L; { x:=1; L: } }", "into scope"), + + // Blocks + CErr("fn1 := func() int {{}}", "return"), + Val1("fn1 := func() bool { { return true } }; b := fn1()", "b", true), + + // If + Val2("if true { i = 2 } else { i = 3 }; i2 = 4", "i", 2, "i2", 4), + Val2("if false { i = 2 } else { i = 3 }; i2 = 4", "i", 3, "i2", 4), + Val2("if i == i2 { i = 2 } else { i = 3 }; i2 = 4", "i", 3, "i2", 4), + // Omit optional parts + Val2("if { i = 2 } else { i = 3 }; i2 = 4", "i", 2, "i2", 4), + Val2("if true { i = 2 }; i2 = 4", "i", 2, "i2", 4), + Val2("if false { i = 2 }; i2 = 4", "i", 1, "i2", 4), + // Init + Val2("if x := true; x { i = 2 } else { i = 3 }; i2 = 4", "i", 2, "i2", 4), + Val2("if x := false; x { i = 2 } else { i = 3 }; i2 = 4", "i", 3, "i2", 4), + // Statement else + Val2("if true { i = 2 } else i = 3; i2 = 4", "i", 2, "i2", 4), + Val2("if false { i = 2 } else i = 3; i2 = 4", "i", 3, "i2", 4), + // Scoping + Val2("if true { i := 2 } else { i := 3 }; i2 = i", "i", 1, "i2", 1), + Val2("if false { i := 2 } else { i := 3 }; i2 = i", "i", 1, "i2", 1), + Val2("if false { i := 2 } else i := 3; i2 = i", "i", 1, "i2", 1), + CErr("if true { x := 2 }; x = 4", undefined), + Val2("if i := 2; true { i2 = i; i := 3 }", "i", 1, "i2", 2), + Val2("if i := 2; false {} else { i2 = i; i := 3 }", "i", 1, "i2", 2), + // Return checking + Run("fn1 := func() int { if true { return 1 } else { return 2 } }"), + Run("fn1 := func() int { if true { return 1 } else return 2 }"), + CErr("fn1 := func() int { if true { return 1 } else { } }", "return"), + CErr("fn1 := func() int { if true { } else { return 1 } }", "return"), + CErr("fn1 := func() int { if true { } else return 1 }", "return"), + CErr("fn1 := func() int { if true { } else { } }", "return"), + CErr("fn1 := func() int { if true { return 1 } }", "return"), + CErr("fn1 := func() int { if true { } }", "return"), + Run("fn1 := func() int { if true { }; return 1 }"), + CErr("fn1 := func() int { if { } }", "return"), + CErr("fn1 := func() int { if { } else { return 2 } }", "return"), + Run("fn1 := func() int { if { return 1 } }"), + Run("fn1 := func() int { if { return 1 } else { } }"), + Run("fn1 := func() int { if { return 1 } else { } }"), + + // Switch + Val1("switch { case false: i += 2; case true: i += 4; default: i += 8 }", "i", 1+4), + Val1("switch { default: i += 2; case false: i += 4; case true: i += 8 }", "i", 1+8), + CErr("switch { default: i += 2; default: i += 4 }", "more than one"), + Val1("switch false { case false: i += 2; case true: i += 4; default: i += 8 }", "i", 1+2), + CErr("switch s { case 1: }", opTypes), + CErr("switch ai { case ai: i += 2 }", opTypes), + Val1("switch 1.0 { case 1: i += 2; case 2: i += 4 }", "i", 1+2), + Val1("switch 1.5 { case 1: i += 2; case 2: i += 4 }", "i", 1), + CErr("switch oneTwo() {}", "multi-valued expression"), + Val1("switch 2 { case 1: i += 2; fallthrough; case 2: i += 4; fallthrough; case 3: i += 8; fallthrough }", "i", 1+4+8), + Val1("switch 5 { case 1: i += 2; fallthrough; default: i += 4; fallthrough; case 2: i += 8; fallthrough; case 3: i += 16; fallthrough }", "i", 1+4+8+16), + CErr("switch { case true: fallthrough; i += 2 }", "final statement"), + Val1("switch { case true: i += 2; fallthrough; ; ; case false: i += 4 }", "i", 1+2+4), + Val1("switch 2 { case 0, 1: i += 2; case 2, 3: i += 4 }", "i", 1+4), + Val2("switch func()int{i2++;return 5}() { case 1, 2: i += 2; case 4, 5: i += 4 }", "i", 1+4, "i2", 3), + Run("switch i { case i: }"), + // TODO(austin) Why doesn't this fail? + //CErr("case 1:", "XXX"), + + // For + Val2("for x := 1; x < 5; x++ { i+=x }; i2 = 4", "i", 11, "i2", 4), + Val2("for x := 1; x < 5; x++ { i+=x; break; i++ }; i2 = 4", "i", 2, "i2", 4), + Val2("for x := 1; x < 5; x++ { i+=x; continue; i++ }; i2 = 4", "i", 11, "i2", 4), + Val2("for i = 2; false; i = 3 { i = 4 }; i2 = 4", "i", 2, "i2", 4), + Val2("for i < 5 { i++ }; i2 = 4", "i", 5, "i2", 4), + Val2("for i < 0 { i++ }; i2 = 4", "i", 1, "i2", 4), + // Scoping + Val2("for i := 2; true; { i2 = i; i := 3; break }", "i", 1, "i2", 2), + // Labeled break/continue + Val1("L1: for { L2: for { i+=2; break L1; i+=4 } i+=8 }", "i", 1+2), + Val1("L1: for { L2: for { i+=2; break L2; i+=4 } i+=8; break; i+=16 }", "i", 1+2+8), + CErr("L1: { for { break L1 } }", "break.*not defined"), + CErr("L1: for {} for { break L1 }", "break.*not defined"), + CErr("L1:; for { break L1 }", "break.*not defined"), + Val2("L1: for i = 0; i < 2; i++ { L2: for { i2++; continue L1; i2++ } }", "i", 2, "i2", 4), + CErr("L1: { for { continue L1 } }", "continue.*not defined"), + CErr("L1:; for { continue L1 }", "continue.*not defined"), + // Return checking + Run("fn1 := func() int{ for {} }"), + CErr("fn1 := func() int{ for true {} }", "return"), + CErr("fn1 := func() int{ for true {return 1} }", "return"), + CErr("fn1 := func() int{ for {break} }", "return"), + Run("fn1 := func() int{ for { for {break} } }"), + CErr("fn1 := func() int{ L1: for { for {break L1} } }", "return"), + Run("fn1 := func() int{ for true {} return 1 }"), + + // Selectors + Val1("var x struct { a int; b int }; x.a = 42; i = x.a", "i", 42), + Val1("type T struct { x int }; var y struct { T }; y.x = 42; i = y.x", "i", 42), + Val2("type T struct { x int }; var y struct { T; x int }; y.x = 42; i = y.x; i2 = y.T.x", "i", 42, "i2", 0), + Run("type T struct { x int }; var y struct { *T }; a := func(){i=y.x}"), + CErr("type T struct { x int }; var x T; x.y = 42", "no field"), + CErr("type T struct { x int }; type U struct { x int }; var y struct { T; U }; y.x = 42", "ambiguous.*\tT\\.x\n\tU\\.x"), + CErr("type T struct { *T }; var x T; x.foo", "no field"), + + Val1("fib := func(int) int{return 0;}; fib = func(v int) int { if v < 2 { return 1 } return fib(v-1)+fib(v-2) }; i = fib(20)", "i", 10946), + + // Make slice + Val2("x := make([]int, 2); x[0] = 42; i, i2 = x[0], x[1]", "i", 42, "i2", 0), + Val2("x := make([]int, 2); x[1] = 42; i, i2 = x[0], x[1]", "i", 0, "i2", 42), + RErr("x := make([]int, 2); x[-i] = 42", "negative index"), + RErr("x := make([]int, 2); x[2] = 42", "index 2 exceeds"), + Val2("x := make([]int, 2, 3); i, i2 = len(x), cap(x)", "i", 2, "i2", 3), + Val2("x := make([]int, 3, 2); i, i2 = len(x), cap(x)", "i", 3, "i2", 3), + RErr("x := make([]int, -i)", "negative length"), + RErr("x := make([]int, 2, -i)", "negative capacity"), + RErr("x := make([]int, 2, 3); x[2] = 42", "index 2 exceeds"), + CErr("x := make([]int, 2, 3, 4)", "too many"), + CErr("x := make([]int)", "not enough"), + + // TODO(austin) Test make map + + // Maps + Val1("x := make(map[int] int); x[1] = 42; i = x[1]", "i", 42), + Val2("x := make(map[int] int); x[1] = 42; i, y := x[1]", "i", 42, "y", true), + Val2("x := make(map[int] int); x[1] = 42; i, y := x[2]", "i", 0, "y", false), + // Not implemented + //Val1("x := make(map[int] int); x[1] = 42, true; i = x[1]", "i", 42), + //Val2("x := make(map[int] int); x[1] = 42; x[1] = 42, false; i, y := x[1]", "i", 0, "y", false), + Run("var x int; a := make(map[int] int); a[0], x = 1, 2"), + CErr("x := make(map[int] int); (func(a,b int){})(x[0])", "not enough"), + CErr("x := make(map[int] int); x[1] = oneTwo()", "too many"), + RErr("x := make(map[int] int); i = x[1]", "key '1' not found"), + + // Functions + Val2("func fib(n int) int { if n <= 2 { return n } return fib(n-1) + fib(n-2) }", "fib(4)", 5, "fib(10)", 89), + Run("func f1(){}"), + Run2("func f1(){}", "f1()"), +} + +func TestStmt(t *testing.T) { + runTests(t, "stmtTests", stmtTests); +} diff --git a/src/pkg/exp/eval/test.bash b/src/pkg/exp/eval/test.bash new file mode 100755 index 000000000..81f4c3dd1 --- /dev/null +++ b/src/pkg/exp/eval/test.bash @@ -0,0 +1,34 @@ +#!/bin/bash +# Copyright 2009 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. + +# Run the interpreter against all the Go test programs +# that begin with the magic +# // $G $D/$F.go && $L $F.$A && ./$A.out +# line and do not contain imports. + +set -e +make +6g main.go && 6l main.6 +( +for i in $(egrep -l '// \$G (\$D/)?\$F\.go \&\& \$L \$F\.\$A && \./\$A\.out' $GOROOT/test/*.go $GOROOT/test/*/*.go) +do + if grep '^import' $i >/dev/null 2>&1 + then + true + else + if $GOROOT/usr/austin/eval/6.out -f $i >/tmp/out 2>&1 && ! test -s /tmp/out + then + echo PASS $i + else + echo FAIL $i + ( + echo '>>> ' $i + cat /tmp/out + echo + ) 1>&3 + fi + fi +done | (tee /dev/fd/2 | awk '{print $1}' | sort | uniq -c) 2>&1 +) 3>test.log diff --git a/src/pkg/exp/eval/type.go b/src/pkg/exp/eval/type.go new file mode 100644 index 000000000..b73f92163 --- /dev/null +++ b/src/pkg/exp/eval/type.go @@ -0,0 +1,1174 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "go/ast"; + "go/token"; + "log"; + "reflect"; + "unsafe"; // For Sizeof +) + + +// XXX(Spec) The type compatibility section is very confusing because +// it makes it seem like there are three distinct types of +// compatibility: plain compatibility, assignment compatibility, and +// comparison compatibility. As I understand it, there's really only +// assignment compatibility and comparison and conversion have some +// restrictions and have special meaning in some cases where the types +// are not otherwise assignment compatible. The comparison +// compatibility section is almost all about the semantics of +// comparison, not the type checking of it, so it would make much more +// sense in the comparison operators section. The compatibility and +// assignment compatibility sections should be rolled into one. + +type Type interface { + // compat returns whether this type is compatible with another + // type. If conv is false, this is normal compatibility, + // where two named types are compatible only if they are the + // same named type. If conv if true, this is conversion + // compatibility, where two named types are conversion + // compatible if their definitions are conversion compatible. + // + // TODO(austin) Deal with recursive types + compat(o Type, conv bool) bool; + // lit returns this type's literal. If this is a named type, + // this is the unnamed underlying type. Otherwise, this is an + // identity operation. + lit() Type; + // isBoolean returns true if this is a boolean type. + isBoolean() bool; + // isInteger returns true if this is an integer type. + isInteger() bool; + // isFloat returns true if this is a floating type. + isFloat() bool; + // isIdeal returns true if this is an ideal int or float. + isIdeal() bool; + // Zero returns a new zero value of this type. + Zero() Value; + // String returns the string representation of this type. + String() string; + // The position where this type was defined, if any. + Pos() token.Position; +} + +type BoundedType interface { + Type; + // minVal returns the smallest value of this type. + minVal() *bignum.Rational; + // maxVal returns the largest value of this type. + maxVal() *bignum.Rational; +} + +var universePos = token.Position{"<universe>", 0, 0, 0}; + +/* + * Type array maps. These are used to memoize composite types. + */ + +type typeArrayMapEntry struct { + key []Type; + v interface {}; + next *typeArrayMapEntry; +} + +type typeArrayMap map[uintptr] *typeArrayMapEntry + +func hashTypeArray(key []Type) uintptr { + hash := uintptr(0); + for _, t := range key { + hash = hash * 33; + if t == nil { + continue; + } + addr := reflect.NewValue(t).(*reflect.PtrValue).Get(); + hash ^= addr; + } + return hash; +} + +func newTypeArrayMap() typeArrayMap { + return make(map[uintptr] *typeArrayMapEntry); +} + +func (m typeArrayMap) Get(key []Type) (interface{}) { + ent, ok := m[hashTypeArray(key)]; + if !ok { + return nil; + } + +nextEnt: + for ; ent != nil; ent = ent.next { + if len(key) != len(ent.key) { + continue; + } + for i := 0; i < len(key); i++ { + if key[i] != ent.key[i] { + continue nextEnt; + } + } + // Found it + return ent.v; + } + + return nil; +} + +func (m typeArrayMap) Put(key []Type, v interface{}) interface{} { + hash := hashTypeArray(key); + ent, _ := m[hash]; + + new := &typeArrayMapEntry{key, v, ent}; + m[hash] = new; + return v; +} + +/* + * Common type + */ + +type commonType struct { +} + +func (commonType) isBoolean() bool { + return false; +} + +func (commonType) isInteger() bool { + return false; +} + +func (commonType) isFloat() bool { + return false; +} + +func (commonType) isIdeal() bool { + return false; +} + +func (commonType) Pos() token.Position { + return token.Position{}; +} + +/* + * Bool + */ + +type boolType struct { + commonType; +} + +var BoolType = universe.DefineType("bool", universePos, &boolType{}) + +func (t *boolType) compat(o Type, conv bool) bool { + _, ok := o.lit().(*boolType); + return ok; +} + +func (t *boolType) lit() Type { + return t; +} + +func (t *boolType) isBoolean() bool { + return true; +} + +func (boolType) String() string { + // Use angle brackets as a convention for printing the + // underlying, unnamed type. This should only show up in + // debug output. + return "<bool>"; +} + +func (t *boolType) Zero() Value { + res := boolV(false); + return &res; +} + +/* + * Uint + */ + +type uintType struct { + commonType; + + // 0 for architecture-dependent types + Bits uint; + // true for uintptr, false for all others + Ptr bool; + name string; +} + +var ( + Uint8Type = universe.DefineType("uint8", universePos, &uintType{commonType{}, 8, false, "uint8"}); + Uint16Type = universe.DefineType("uint16", universePos, &uintType{commonType{}, 16, false, "uint16"}); + Uint32Type = universe.DefineType("uint32", universePos, &uintType{commonType{}, 32, false, "uint32"}); + Uint64Type = universe.DefineType("uint64", universePos, &uintType{commonType{}, 64, false, "uint64"}); + + UintType = universe.DefineType("uint", universePos, &uintType{commonType{}, 0, false, "uint"}); + UintptrType = universe.DefineType("uintptr", universePos, &uintType{commonType{}, 0, true, "uintptr"}); +) + +func (t *uintType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*uintType); + return ok && t == t2;; +} + +func (t *uintType) lit() Type { + return t; +} + +func (t *uintType) isInteger() bool { + return true; +} + +func (t *uintType) String() string { + return "<" + t.name + ">"; +} + +func (t *uintType) Zero() Value { + switch t.Bits { + case 0: + if t.Ptr { + res := uintptrV(0); + return &res; + } else { + res := uintV(0); + return &res; + } + case 8: + res := uint8V(0); + return &res; + case 16: + res := uint16V(0); + return &res; + case 32: + res := uint32V(0); + return &res; + case 64: + res := uint64V(0); + return &res; + } + panic("unexpected uint bit count: ", t.Bits); +} + +func (t *uintType) minVal() *bignum.Rational { + return bignum.Rat(0, 1); +} + +func (t *uintType) maxVal() *bignum.Rational { + bits := t.Bits; + if bits == 0 { + if t.Ptr { + bits = uint(8 * unsafe.Sizeof(uintptr(0))); + } else { + bits = uint(8 * unsafe.Sizeof(uint(0))); + } + } + return bignum.MakeRat(bignum.Int(1).Shl(bits).Add(bignum.Int(-1)), bignum.Nat(1)); +} + +/* + * Int + */ + +type intType struct { + commonType; + + // XXX(Spec) Numeric types: "There is also a set of + // architecture-independent basic numeric types whose size + // depends on the architecture." Should that be + // architecture-dependent? + + // 0 for architecture-dependent types + Bits uint; + name string; +} + +var ( + Int8Type = universe.DefineType("int8", universePos, &intType{commonType{}, 8, "int8"}); + Int16Type = universe.DefineType("int16", universePos, &intType{commonType{}, 16, "int16"}); + Int32Type = universe.DefineType("int32", universePos, &intType{commonType{}, 32, "int32"}); + Int64Type = universe.DefineType("int64", universePos, &intType{commonType{}, 64, "int64"}); + + IntType = universe.DefineType("int", universePos, &intType{commonType{}, 0, "int"}); +) + +func (t *intType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*intType); + return ok && t == t2; +} + +func (t *intType) lit() Type { + return t; +} + +func (t *intType) isInteger() bool { + return true; +} + +func (t *intType) String() string { + return "<" + t.name + ">"; +} + +func (t *intType) Zero() Value { + switch t.Bits { + case 8: + res := int8V(0); + return &res; + case 16: + res := int16V(0); + return &res; + case 32: + res := int32V(0); + return &res; + case 64: + res := int64V(0); + return &res; + + case 0: + res := intV(0); + return &res; + } + panic("unexpected int bit count: ", t.Bits); +} + +func (t *intType) minVal() *bignum.Rational { + bits := t.Bits; + if bits == 0 { + bits = uint(8 * unsafe.Sizeof(int(0))); + } + return bignum.MakeRat(bignum.Int(-1).Shl(bits - 1), bignum.Nat(1)); +} + +func (t *intType) maxVal() *bignum.Rational { + bits := t.Bits; + if bits == 0 { + bits = uint(8 * unsafe.Sizeof(int(0))); + } + return bignum.MakeRat(bignum.Int(1).Shl(bits - 1).Add(bignum.Int(-1)), bignum.Nat(1)); +} + +/* + * Ideal int + */ + +type idealIntType struct { + commonType; +} + +var IdealIntType Type = &idealIntType{} + +func (t *idealIntType) compat(o Type, conv bool) bool { + _, ok := o.lit().(*idealIntType); + return ok; +} + +func (t *idealIntType) lit() Type { + return t; +} + +func (t *idealIntType) isInteger() bool { + return true; +} + +func (t *idealIntType) isIdeal() bool { + return true; +} + +func (t *idealIntType) String() string { + return "ideal integer"; +} + +func (t *idealIntType) Zero() Value { + return &idealIntV{bignum.Int(0)}; +} + +/* + * Float + */ + +type floatType struct { + commonType; + + // 0 for architecture-dependent type + Bits uint; + + name string; +} + +var ( + Float32Type = universe.DefineType("float32", universePos, &floatType{commonType{}, 32, "float32"}); + Float64Type = universe.DefineType("float64", universePos, &floatType{commonType{}, 64, "float64"}); + FloatType = universe.DefineType("float", universePos, &floatType{commonType{}, 0, "float"}); +) + +func (t *floatType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*floatType); + return ok && t == t2; +} + +func (t *floatType) lit() Type { + return t; +} + +func (t *floatType) isFloat() bool { + return true; +} + +func (t *floatType) String() string { + return "<" + t.name + ">"; +} + +func (t *floatType) Zero() Value { + switch t.Bits { + case 32: + res := float32V(0); + return &res; + case 64: + res := float64V(0); + return &res; + case 0: + res := floatV(0); + return &res; + } + panic("unexpected float bit count: ", t.Bits); +} + +var maxFloat32Val = bignum.MakeRat(bignum.Int(0xffffff).Shl(127-23), bignum.Nat(1)) +var maxFloat64Val = bignum.MakeRat(bignum.Int(0x1fffffffffffff).Shl(1023-52), bignum.Nat(1)) +var minFloat32Val = maxFloat32Val.Neg() +var minFloat64Val = maxFloat64Val.Neg() + +func (t *floatType) minVal() *bignum.Rational { + bits := t.Bits; + if bits == 0 { + bits = uint(8 * unsafe.Sizeof(float(0))); + } + switch bits { + case 32: + return minFloat32Val; + case 64: + return minFloat64Val; + } + log.Crashf("unexpected floating point bit count: %d", bits); + panic(); +} + +func (t *floatType) maxVal() *bignum.Rational { + bits := t.Bits; + if bits == 0 { + bits = uint(8 * unsafe.Sizeof(float(0))); + } + switch bits { + case 32: + return maxFloat32Val; + case 64: + return maxFloat64Val; + } + log.Crashf("unexpected floating point bit count: %d", bits); + panic(); +} + +/* + * Ideal float + */ + +type idealFloatType struct { + commonType; +} + +var IdealFloatType Type = &idealFloatType{}; + +func (t *idealFloatType) compat(o Type, conv bool) bool { + _, ok := o.lit().(*idealFloatType); + return ok; +} + +func (t *idealFloatType) lit() Type { + return t; +} + +func (t *idealFloatType) isFloat() bool { + return true; +} + +func (t *idealFloatType) isIdeal() bool { + return true; +} + +func (t *idealFloatType) String() string { + return "ideal float"; +} + +func (t *idealFloatType) Zero() Value { + return &idealFloatV{bignum.Rat(1, 0)}; +} + +/* + * String + */ + +type stringType struct { + commonType; +} + +var StringType = universe.DefineType("string", universePos, &stringType{}) + +func (t *stringType) compat(o Type, conv bool) bool { + _, ok := o.lit().(*stringType); + return ok; +} + +func (t *stringType) lit() Type { + return t; +} + +func (t *stringType) String() string { + return "<string>"; +} + +func (t *stringType) Zero() Value { + res := stringV(""); + return &res; +} + +/* + * Array + */ + +type ArrayType struct { + commonType; + Len int64; + Elem Type; +} + +var arrayTypes = make(map[int64] map[Type] *ArrayType) + +// Two array types are identical if they have identical element types +// and the same array length. + +func NewArrayType(len int64, elem Type) *ArrayType { + ts, ok := arrayTypes[len]; + if !ok { + ts = make(map[Type] *ArrayType); + arrayTypes[len] = ts; + } + t, ok := ts[elem]; + if !ok { + t = &ArrayType{commonType{}, len, elem}; + ts[elem] = t; + } + return t; +} + +func (t *ArrayType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*ArrayType); + if !ok { + return false; + } + return t.Len == t2.Len && t.Elem.compat(t2.Elem, conv); +} + +func (t *ArrayType) lit() Type { + return t; +} + +func (t *ArrayType) String() string { + return "[]" + t.Elem.String(); +} + +func (t *ArrayType) Zero() Value { + res := arrayV(make([]Value, t.Len)); + // TODO(austin) It's unfortunate that each element is + // separately heap allocated. We could add ZeroArray to + // everything, though that doesn't help with multidimensional + // arrays. Or we could do something unsafe. We'll have this + // same problem with structs. + for i := int64(0); i < t.Len; i++ { + res[i] = t.Elem.Zero(); + } + return &res; +} + +/* + * Struct + */ + +type StructField struct { + Name string; + Type Type; + Anonymous bool; +} + +type StructType struct { + commonType; + Elems []StructField; +} + +var structTypes = newTypeArrayMap() + +// Two struct types are identical if they have the same sequence of +// fields, and if corresponding fields have the same names and +// identical types. Two anonymous fields are considered to have the +// same name. + +func NewStructType(fields []StructField) *StructType { + // Start by looking up just the types + fts := make([]Type, len(fields)); + for i, f := range fields { + fts[i] = f.Type; + } + tMapI := structTypes.Get(fts); + if tMapI == nil { + tMapI = structTypes.Put(fts, make(map[string] *StructType)); + } + tMap := tMapI.(map[string] *StructType); + + // Construct key for field names + key := ""; + for _, f := range fields { + // XXX(Spec) It's not clear if struct { T } and struct + // { T T } are either identical or compatible. The + // "Struct Types" section says that the name of that + // field is "T", which suggests that they are + // identical, but it really means that it's the name + // for the purpose of selector expressions and nothing + // else. We decided that they should be neither + // identical or compatible. + if f.Anonymous { + key += "!"; + } + key += f.Name + " "; + } + + // XXX(Spec) Do the tags also have to be identical for the + // types to be identical? I certainly hope so, because + // otherwise, this is the only case where two distinct type + // objects can represent identical types. + + t, ok := tMap[key]; + if !ok { + // Create new struct type + t = &StructType{commonType{}, fields}; + tMap[key] = t; + } + return t; +} + +func (t *StructType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*StructType); + if !ok { + return false; + } + if len(t.Elems) != len(t2.Elems) { + return false; + } + for i, e := range t.Elems { + e2 := t2.Elems[i]; + // XXX(Spec) An anonymous and a non-anonymous field + // are neither identical nor compatible. + if (e.Anonymous != e2.Anonymous || + (!e.Anonymous && e.Name != e2.Name) || + !e.Type.compat(e2.Type, conv)) { + return false; + } + } + return true; +} + +func (t *StructType) lit() Type { + return t; +} + +func (t *StructType) String() string { + s := "struct {"; + for i, f := range t.Elems { + if i > 0 { + s += "; "; + } + if !f.Anonymous { + s += f.Name + " "; + } + s += f.Type.String(); + } + return s + "}"; +} + +func (t *StructType) Zero() Value { + res := structV(make([]Value, len(t.Elems))); + for i, f := range t.Elems { + res[i] = f.Type.Zero(); + } + return &res; +} + +/* + * Pointer + */ + +type PtrType struct { + commonType; + Elem Type; +} + +var ptrTypes = make(map[Type] *PtrType) + +// Two pointer types are identical if they have identical base types. + +func NewPtrType(elem Type) *PtrType { + t, ok := ptrTypes[elem]; + if !ok { + t = &PtrType{commonType{}, elem}; + ptrTypes[elem] = t; + } + return t; +} + +func (t *PtrType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*PtrType); + if !ok { + return false; + } + return t.Elem.compat(t2.Elem, conv); +} + +func (t *PtrType) lit() Type { + return t; +} + +func (t *PtrType) String() string { + return "*" + t.Elem.String(); +} + +func (t *PtrType) Zero() Value { + return &ptrV{nil}; +} + +/* + * Function + */ + +type FuncType struct { + commonType; + // TODO(austin) Separate receiver Type for methods? + In []Type; + Variadic bool; + Out []Type; + builtin string; +} + +var funcTypes = newTypeArrayMap() +var variadicFuncTypes = newTypeArrayMap() + +// Create singleton function types for magic built-in functions +var ( + capType = &FuncType{builtin: "cap"}; + closeType = &FuncType{builtin: "close"}; + closedType = &FuncType{builtin: "closed"}; + lenType = &FuncType{builtin: "len"}; + makeType = &FuncType{builtin: "make"}; + newType = &FuncType{builtin: "new"}; + panicType = &FuncType{builtin: "panic"}; + paniclnType = &FuncType{builtin: "panicln"}; + printType = &FuncType{builtin: "print"}; + printlnType = &FuncType{builtin: "println"}; +) + +// Two function types are identical if they have the same number of +// parameters and result values and if corresponding parameter and +// result types are identical. All "..." parameters have identical +// type. Parameter and result names are not required to match. + +func NewFuncType(in []Type, variadic bool, out []Type) *FuncType { + inMap := funcTypes; + if variadic { + inMap = variadicFuncTypes; + } + + outMapI := inMap.Get(in); + if outMapI == nil { + outMapI = inMap.Put(in, newTypeArrayMap()); + } + outMap := outMapI.(typeArrayMap); + + tI := outMap.Get(out); + if tI != nil { + return tI.(*FuncType); + } + + t := &FuncType{commonType{}, in, variadic, out, ""}; + outMap.Put(out, t); + return t; +} + +func (t *FuncType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*FuncType); + if !ok { + return false; + } + if len(t.In) != len(t2.In) || t.Variadic != t2.Variadic || len(t.Out) != len(t2.Out) { + return false; + } + for i := range t.In { + if !t.In[i].compat(t2.In[i], conv) { + return false; + } + } + for i := range t.Out { + if !t.Out[i].compat(t2.Out[i], conv) { + return false; + } + } + return true; +} + +func (t *FuncType) lit() Type { + return t; +} + +func typeListString(ts []Type, ns []*ast.Ident) string { + s := ""; + for i, t := range ts { + if i > 0 { + s += ", "; + } + if ns != nil && ns[i] != nil { + s += ns[i].Value + " "; + } + if t == nil { + // Some places use nil types to represent errors + s += "<none>"; + } else { + s += t.String(); + } + } + return s; +} + +func (t *FuncType) String() string { + if t.builtin != "" { + return "built-in function " + t.builtin; + } + args := typeListString(t.In, nil); + if t.Variadic { + if len(args) > 0 { + args += ", "; + } + args += "..."; + } + s := "func(" + args + ")"; + if len(t.Out) > 0 { + s += " (" + typeListString(t.Out, nil) + ")"; + } + return s; +} + +func (t *FuncType) Zero() Value { + return &funcV{nil}; +} + +type FuncDecl struct { + Type *FuncType; + Name *ast.Ident; // nil for function literals + // InNames will be one longer than Type.In if this function is + // variadic. + InNames []*ast.Ident; + OutNames []*ast.Ident; +} + +func (t *FuncDecl) String() string { + args := typeListString(t.Type.In, t.InNames); + if t.Type.Variadic { + if len(args) > 0 { + args += ", "; + } + args += "..."; + } + s := "func"; + if t.Name != nil { + s += " " + t.Name.Value; + } + s += "(" + args + ")"; + if len(t.Type.Out) > 0 { + s += " (" + typeListString(t.Type.Out, t.OutNames) + ")"; + } + return s; +} + +/* +type InterfaceType struct { + // TODO(austin) +} +*/ + +type SliceType struct { + commonType; + Elem Type; +} + +var sliceTypes = make(map[Type] *SliceType) + +// Two slice types are identical if they have identical element types. + +func NewSliceType(elem Type) *SliceType { + t, ok := sliceTypes[elem]; + if !ok { + t = &SliceType{commonType{}, elem}; + sliceTypes[elem] = t; + } + return t; +} + +func (t *SliceType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*SliceType); + if !ok { + return false; + } + return t.Elem.compat(t2.Elem, conv); +} + +func (t *SliceType) lit() Type { + return t; +} + +func (t *SliceType) String() string { + return "[]" + t.Elem.String(); +} + +func (t *SliceType) Zero() Value { + // The value of an uninitialized slice is nil. The length and + // capacity of a nil slice are 0. + return &sliceV{Slice{nil, 0, 0}}; +} + +/* + * Map type + */ + +type MapType struct { + commonType; + Key Type; + Elem Type; +} + +var mapTypes = make(map[Type] map[Type] *MapType) + +func NewMapType(key Type, elem Type) *MapType { + ts, ok := mapTypes[key]; + if !ok { + ts = make(map[Type] *MapType); + mapTypes[key] = ts; + } + t, ok := ts[elem]; + if !ok { + t = &MapType{commonType{}, key, elem}; + ts[elem] = t; + } + return t; +} + +func (t *MapType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*MapType); + if !ok { + return false; + } + return t.Elem.compat(t2.Elem, conv) && t.Key.compat(t2.Key, conv); +} + +func (t *MapType) lit() Type { + return t; +} + +func (t *MapType) String() string { + return "map[" + t.Key.String() + "] " + t.Elem.String(); +} + +func (t *MapType) Zero() Value { + // The value of an uninitialized map is nil. + return &mapV{nil}; +} + +/* +type ChanType struct { + // TODO(austin) +} +*/ + +/* + * Named types + */ + +type Method struct { + decl *FuncDecl; + fn Func; +} + +type NamedType struct { + token.Position; + Name string; + // Underlying type. If incomplete is true, this will be nil. + // If incomplete is false and this is still nil, then this is + // a placeholder type representing an error. + Def Type; + // True while this type is being defined. + incomplete bool; + methods map[string] Method; +} + +// TODO(austin) This is temporarily needed by the debugger's remote +// type parser. This should only be possible with block.DefineType. +func NewNamedType(name string) *NamedType { + return &NamedType{token.Position{}, name, nil, true, make(map[string] Method)}; +} + +func (t *NamedType) Complete(def Type) { + if !t.incomplete { + log.Crashf("cannot complete already completed NamedType %+v", *t); + } + // We strip the name from def because multiple levels of + // naming are useless. + if ndef, ok := def.(*NamedType); ok { + def = ndef.Def; + } + t.Def = def; + t.incomplete = false; +} + +func (t *NamedType) compat(o Type, conv bool) bool { + t2, ok := o.(*NamedType); + if ok { + if conv { + // Two named types are conversion compatible + // if their literals are conversion + // compatible. + return t.Def.compat(t2.Def, conv); + } else { + // Two named types are compatible if their + // type names originate in the same type + // declaration. + return t == t2; + } + } + // A named and an unnamed type are compatible if the + // respective type literals are compatible. + return o.compat(t.Def, conv); +} + +func (t *NamedType) lit() Type { + return t.Def.lit(); +} + +func (t *NamedType) isBoolean() bool { + return t.Def.isBoolean(); +} + +func (t *NamedType) isInteger() bool { + return t.Def.isInteger(); +} + +func (t *NamedType) isFloat() bool { + return t.Def.isFloat(); +} + +func (t *NamedType) isIdeal() bool { + return false; +} + +func (t *NamedType) String() string { + return t.Name; +} + +func (t *NamedType) Zero() Value { + return t.Def.Zero(); +} + +/* + * Multi-valued type + */ + +// MultiType is a special type used for multi-valued expressions, akin +// to a tuple type. It's not generally accessible within the +// language. +type MultiType struct { + commonType; + Elems []Type; +} + +var multiTypes = newTypeArrayMap() + +func NewMultiType(elems []Type) *MultiType { + if t := multiTypes.Get(elems); t != nil { + return t.(*MultiType); + } + + t := &MultiType{commonType{}, elems}; + multiTypes.Put(elems, t); + return t; +} + +func (t *MultiType) compat(o Type, conv bool) bool { + t2, ok := o.lit().(*MultiType); + if !ok { + return false; + } + if len(t.Elems) != len(t2.Elems) { + return false; + } + for i := range t.Elems { + if !t.Elems[i].compat(t2.Elems[i], conv) { + return false; + } + } + return true; +} + +var EmptyType Type = NewMultiType([]Type{}) + +func (t *MultiType) lit() Type { + return t; +} + +func (t *MultiType) String() string { + if len(t.Elems) == 0 { + return "<none>"; + } + return typeListString(t.Elems, nil); +} + +func (t *MultiType) Zero() Value { + res := make([]Value, len(t.Elems)); + for i, t := range t.Elems { + res[i] = t.Zero(); + } + return multiV(res); +} + +/* + * Initialize the universe + */ + +func init() { + // To avoid portability issues all numeric types are distinct + // except byte, which is an alias for uint8. + + // Make byte an alias for the named type uint8. Type aliases + // are otherwise impossible in Go, so just hack it here. + universe.defs["byte"] = universe.defs["uint8"]; + + // Built-in functions + universe.DefineConst("cap", universePos, capType, nil); + universe.DefineConst("close", universePos, closeType, nil); + universe.DefineConst("closed", universePos, closedType, nil); + universe.DefineConst("len", universePos, lenType, nil); + universe.DefineConst("make", universePos, makeType, nil); + universe.DefineConst("new", universePos, newType, nil); + universe.DefineConst("panic", universePos, panicType, nil); + universe.DefineConst("panicln", universePos, paniclnType, nil); + universe.DefineConst("print", universePos, printType, nil); + universe.DefineConst("println", universePos, printlnType, nil); +} diff --git a/src/pkg/exp/eval/typec.go b/src/pkg/exp/eval/typec.go new file mode 100644 index 000000000..bdbe98c4c --- /dev/null +++ b/src/pkg/exp/eval/typec.go @@ -0,0 +1,366 @@ +// Copyright 2009 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 eval + +import ( + "go/ast"; + "go/token"; + "log"; +) + + +/* + * Type compiler + */ + +type typeCompiler struct { + *compiler; + block *block; + // Check to be performed after a type declaration is compiled. + // + // TODO(austin) This will probably have to change after we + // eliminate forward declarations. + lateCheck func() bool +} + +func (a *typeCompiler) compileIdent(x *ast.Ident, allowRec bool) Type { + _, _, def := a.block.Lookup(x.Value); + if def == nil { + a.diagAt(x, "%s: undefined", x.Value); + return nil; + } + switch def := def.(type) { + case *Constant: + a.diagAt(x, "constant %v used as type", x.Value); + return nil; + case *Variable: + a.diagAt(x, "variable %v used as type", x.Value); + return nil; + case *NamedType: + if !allowRec && def.incomplete { + a.diagAt(x, "illegal recursive type"); + return nil; + } + if !def.incomplete && def.Def == nil { + // Placeholder type from an earlier error + return nil; + } + return def; + case Type: + return def; + } + log.Crashf("name %s has unknown type %T", x.Value, def); + return nil; +} + +func (a *typeCompiler) compileArrayType(x *ast.ArrayType, allowRec bool) Type { + // Compile element type + elem := a.compileType(x.Elt, allowRec); + + // Compile length expression + if x.Len == nil { + if elem == nil { + return nil; + } + return NewSliceType(elem); + } + + if _, ok := x.Len.(*ast.Ellipsis); ok { + a.diagAt(x.Len, "... array initailizers not implemented"); + return nil; + } + l, ok := a.compileArrayLen(a.block, x.Len); + if !ok { + return nil; + } + if l < 0 { + a.diagAt(x.Len, "array length must be non-negative"); + return nil; + } + if elem == nil { + return nil; + } + + return NewArrayType(l, elem); +} + +func countFields(fs []*ast.Field) int { + n := 0; + for _, f := range fs { + if f.Names == nil { + n++; + } else { + n += len(f.Names); + } + } + return n; +} + +func (a *typeCompiler) compileFields(fs []*ast.Field, allowRec bool) ([]Type, []*ast.Ident, []token.Position, bool) { + n := countFields(fs); + ts := make([]Type, n); + ns := make([]*ast.Ident, n); + ps := make([]token.Position, n); + + bad := false; + i := 0; + for _, f := range fs { + t := a.compileType(f.Type, allowRec); + if t == nil { + bad = true; + } + if f.Names == nil { + ns[i] = nil; + ts[i] = t; + ps[i] = f.Type.Pos(); + i++; + continue; + } + for _, n := range f.Names { + ns[i] = n; + ts[i] = t; + ps[i] = n.Pos(); + i++; + } + } + + return ts, ns, ps, bad; +} + +func (a *typeCompiler) compileStructType(x *ast.StructType, allowRec bool) Type { + ts, names, poss, bad := a.compileFields(x.Fields, allowRec); + + // XXX(Spec) The spec claims that field identifiers must be + // unique, but 6g only checks this when they are accessed. I + // think the spec is better in this regard: if I write two + // fields with the same name in the same struct type, clearly + // that's a mistake. This definition does *not* descend into + // anonymous fields, so it doesn't matter if those change. + // There's separate language in the spec about checking + // uniqueness of field names inherited from anonymous fields + // at use time. + fields := make([]StructField, len(ts)); + nameSet := make(map[string] token.Position, len(ts)); + for i := range fields { + // Compute field name and check anonymous fields + var name string; + if names[i] != nil { + name = names[i].Value; + } else { + if ts[i] == nil { + continue; + } + + var nt *NamedType; + // [For anonymous fields,] the unqualified + // type name acts as the field identifier. + switch t := ts[i].(type) { + case *NamedType: + name = t.Name; + nt = t; + case *PtrType: + switch t := t.Elem.(type) { + case *NamedType: + name = t.Name; + nt = t; + } + } + // [An anonymous field] must be specified as a + // type name T or as a pointer to a type name + // *T, and T itself, may not be a pointer or + // interface type. + if nt == nil { + a.diagAt(&poss[i], "embedded type must T or *T, where T is a named type"); + bad = true; + continue; + } + // The check for embedded pointer types must + // be deferred because of things like + // type T *struct { T } + lateCheck := a.lateCheck; + a.lateCheck = func() bool { + if _, ok := nt.lit().(*PtrType); ok { + a.diagAt(&poss[i], "embedded type %v is a pointer type", nt); + return false; + } + return lateCheck(); + }; + } + + // Check name uniqueness + if prev, ok := nameSet[name]; ok { + a.diagAt(&poss[i], "field %s redeclared\n\tprevious declaration at %s", name, &prev); + bad = true; + continue; + } + nameSet[name] = poss[i]; + + // Create field + fields[i].Name = name; + fields[i].Type = ts[i]; + fields[i].Anonymous = (names[i] == nil); + } + + if bad { + return nil; + } + + return NewStructType(fields); +} + +func (a *typeCompiler) compilePtrType(x *ast.StarExpr) Type { + elem := a.compileType(x.X, true); + if elem == nil { + return nil; + } + return NewPtrType(elem); +} + +func (a *typeCompiler) compileFuncType(x *ast.FuncType, allowRec bool) *FuncDecl { + // TODO(austin) Variadic function types + + // The types of parameters and results must be complete. + // + // TODO(austin) It's not clear they actually have to be complete. + in, inNames, _, inBad := a.compileFields(x.Params, allowRec); + out, outNames, _, outBad := a.compileFields(x.Results, allowRec); + + if inBad || outBad { + return nil; + } + return &FuncDecl{NewFuncType(in, false, out), nil, inNames, outNames}; +} + +func (a *typeCompiler) compileMapType(x *ast.MapType) Type { + key := a.compileType(x.Key, true); + val := a.compileType(x.Value, true); + if key == nil || val == nil { + return nil; + } + // XXX(Spec) The Map types section explicitly lists all types + // that can be map keys except for function types. + switch key.lit().(type) { + case *StructType: + a.diagAt(x, "map key cannot be a struct type"); + return nil; + case *ArrayType: + a.diagAt(x, "map key cannot be an array type"); + return nil; + case *SliceType: + a.diagAt(x, "map key cannot be a slice type"); + return nil; + } + return NewMapType(key, val); +} + +func (a *typeCompiler) compileType(x ast.Expr, allowRec bool) Type { + switch x := x.(type) { + case *ast.BadExpr: + // Error already reported by parser + a.silentErrors++; + return nil; + + case *ast.Ident: + return a.compileIdent(x, allowRec); + + case *ast.ArrayType: + return a.compileArrayType(x, allowRec); + + case *ast.StructType: + return a.compileStructType(x, allowRec); + + case *ast.StarExpr: + return a.compilePtrType(x); + + case *ast.FuncType: + fd := a.compileFuncType(x, allowRec); + if fd == nil { + return nil; + } + return fd.Type; + + case *ast.InterfaceType: + goto notimpl; + + case *ast.MapType: + return a.compileMapType(x); + + case *ast.ChanType: + goto notimpl; + + case *ast.ParenExpr: + return a.compileType(x.X, allowRec); + + case *ast.Ellipsis: + a.diagAt(x, "illegal use of ellipsis"); + return nil; + } + a.diagAt(x, "expression used as type"); + return nil; + +notimpl: + a.diagAt(x, "compileType: %T not implemented", x); + return nil; +} + +/* + * Type compiler interface + */ + +func noLateCheck() bool { + return true; +} + +func (a *compiler) compileType(b *block, typ ast.Expr) Type { + tc := &typeCompiler{a, b, noLateCheck}; + t := tc.compileType(typ, false); + if !tc.lateCheck() { + t = nil; + } + return t; +} + +func (a *compiler) compileTypeDecl(b *block, decl *ast.GenDecl) bool { + ok := true; + for _, spec := range decl.Specs { + spec := spec.(*ast.TypeSpec); + // Create incomplete type for this type + nt := b.DefineType(spec.Name.Value, spec.Name.Pos(), nil); + if nt != nil { + nt.(*NamedType).incomplete = true; + } + // Compile type + tc := &typeCompiler{a, b, noLateCheck}; + t := tc.compileType(spec.Type, false); + if t == nil { + // Create a placeholder type + ok = false; + } + // Fill incomplete type + if nt != nil { + nt.(*NamedType).Complete(t); + } + // Perform late type checking with complete type + if !tc.lateCheck() { + ok = false; + if nt != nil { + // Make the type a placeholder + nt.(*NamedType).Def = nil; + } + } + } + return ok; +} + +func (a *compiler) compileFuncType(b *block, typ *ast.FuncType) *FuncDecl { + tc := &typeCompiler{a, b, noLateCheck}; + res := tc.compileFuncType(typ, false); + if res != nil { + if !tc.lateCheck() { + res = nil; + } + } + return res; +} diff --git a/src/pkg/exp/eval/util.go b/src/pkg/exp/eval/util.go new file mode 100644 index 000000000..9cdf23722 --- /dev/null +++ b/src/pkg/exp/eval/util.go @@ -0,0 +1,39 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; +) + +// TODO(austin): Maybe add to bignum in more general form +func ratToString(rat *bignum.Rational) string { + n, dnat := rat.Value(); + d := bignum.MakeInt(false, dnat); + w, frac := n.QuoRem(d); + out := w.String(); + if frac.IsZero() { + return out; + } + + r := frac.Abs(); + r = r.Mul(bignum.Nat(1e6)); + dec, tail := r.DivMod(dnat); + // Round last digit + if tail.Cmp(dnat.Div(bignum.Nat(2))) >= 0 { + dec = dec.Add(bignum.Nat(1)); + } + // Strip zeros + ten := bignum.Nat(10); + for !dec.IsZero() { + dec2, r2 := dec.DivMod(ten); + if !r2.IsZero() { + break; + } + dec = dec2; + } + out += "." + dec.String(); + return out; +} diff --git a/src/pkg/exp/eval/value.go b/src/pkg/exp/eval/value.go new file mode 100644 index 000000000..1a64a6d96 --- /dev/null +++ b/src/pkg/exp/eval/value.go @@ -0,0 +1,731 @@ +// Copyright 2009 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 eval + +import ( + "bignum"; + "fmt"; +) + +type Value interface { + String() string; + // Assign copies another value into this one. It should + // assume that the other value satisfies the same specific + // value interface (BoolValue, etc.), but must not assume + // anything about its specific type. + Assign(t *Thread, o Value); +} + +type BoolValue interface { + Value; + Get(*Thread) bool; + Set(*Thread, bool); +} + +type UintValue interface { + Value; + Get(*Thread) uint64; + Set(*Thread, uint64); +} + +type IntValue interface { + Value; + Get(*Thread) int64; + Set(*Thread, int64); +} + +// TODO(austin) IdealIntValue and IdealFloatValue should not exist +// because ideals are not l-values. +type IdealIntValue interface { + Value; + Get() *bignum.Integer; +} + +type FloatValue interface { + Value; + Get(*Thread) float64; + Set(*Thread, float64); +} + +type IdealFloatValue interface { + Value; + Get() *bignum.Rational; +} + +type StringValue interface { + Value; + Get(*Thread) string; + Set(*Thread, string); +} + +type ArrayValue interface { + Value; + // TODO(austin) Get() is here for uniformity, but is + // completely useless. If a lot of other types have similarly + // useless Get methods, just special-case these uses. + Get(*Thread) ArrayValue; + Elem(*Thread, int64) Value; + // Sub returns an ArrayValue backed by the same array that + // starts from element i and has length len. + Sub(i int64, len int64) ArrayValue; +} + +type StructValue interface { + Value; + // TODO(austin) This is another useless Get() + Get(*Thread) StructValue; + Field(*Thread, int) Value; +} + +type PtrValue interface { + Value; + Get(*Thread) Value; + Set(*Thread, Value); +} + +type Func interface { + NewFrame() *Frame; + Call(*Thread); +} + +type FuncValue interface { + Value; + Get(*Thread) Func; + Set(*Thread, Func); +} + +type Slice struct { + Base ArrayValue; + Len, Cap int64; +} + +type SliceValue interface { + Value; + Get(*Thread) Slice; + Set(*Thread, Slice); +} + +type Map interface { + Len(*Thread) int64; + // Retrieve an element from the map, returning nil if it does + // not exist. + Elem(t *Thread, key interface{}) Value; + // Set an entry in the map. If val is nil, delete the entry. + SetElem(t *Thread, key interface{}, val Value); + // TODO(austin) Perhaps there should be an iterator interface instead. + Iter(func(key interface{}, val Value) bool); +} + +type MapValue interface { + Value; + Get(*Thread) Map; + Set(*Thread, Map); +} + +/* + * Bool + */ + +type boolV bool + +func (v *boolV) String() string { + return fmt.Sprint(*v); +} + +func (v *boolV) Assign(t *Thread, o Value) { + *v = boolV(o.(BoolValue).Get(t)); +} + +func (v *boolV) Get(*Thread) bool { + return bool(*v); +} + +func (v *boolV) Set(t *Thread, x bool) { + *v = boolV(x); +} + +/* + * Uint + */ + +type uint8V uint8 + +func (v *uint8V) String() string { + return fmt.Sprint(*v); +} + +func (v *uint8V) Assign(t *Thread, o Value) { + *v = uint8V(o.(UintValue).Get(t)); +} + +func (v *uint8V) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uint8V) Set(t *Thread, x uint64) { + *v = uint8V(x); +} + +type uint16V uint16 + +func (v *uint16V) String() string { + return fmt.Sprint(*v); +} + +func (v *uint16V) Assign(t *Thread, o Value) { + *v = uint16V(o.(UintValue).Get(t)); +} + +func (v *uint16V) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uint16V) Set(t *Thread, x uint64) { + *v = uint16V(x); +} + +type uint32V uint32 + +func (v *uint32V) String() string { + return fmt.Sprint(*v); +} + +func (v *uint32V) Assign(t *Thread, o Value) { + *v = uint32V(o.(UintValue).Get(t)); +} + +func (v *uint32V) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uint32V) Set(t *Thread, x uint64) { + *v = uint32V(x); +} + +type uint64V uint64 + +func (v *uint64V) String() string { + return fmt.Sprint(*v); +} + +func (v *uint64V) Assign(t *Thread, o Value) { + *v = uint64V(o.(UintValue).Get(t)); +} + +func (v *uint64V) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uint64V) Set(t *Thread, x uint64) { + *v = uint64V(x); +} + +type uintV uint + +func (v *uintV) String() string { + return fmt.Sprint(*v); +} + +func (v *uintV) Assign(t *Thread, o Value) { + *v = uintV(o.(UintValue).Get(t)); +} + +func (v *uintV) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uintV) Set(t *Thread, x uint64) { + *v = uintV(x); +} + +type uintptrV uintptr + +func (v *uintptrV) String() string { + return fmt.Sprint(*v); +} + +func (v *uintptrV) Assign(t *Thread, o Value) { + *v = uintptrV(o.(UintValue).Get(t)); +} + +func (v *uintptrV) Get(*Thread) uint64 { + return uint64(*v); +} + +func (v *uintptrV) Set(t *Thread, x uint64) { + *v = uintptrV(x); +} + +/* + * Int + */ + +type int8V int8 + +func (v *int8V) String() string { + return fmt.Sprint(*v); +} + +func (v *int8V) Assign(t *Thread, o Value) { + *v = int8V(o.(IntValue).Get(t)); +} + +func (v *int8V) Get(*Thread) int64 { + return int64(*v); +} + +func (v *int8V) Set(t *Thread, x int64) { + *v = int8V(x); +} + +type int16V int16 + +func (v *int16V) String() string { + return fmt.Sprint(*v); +} + +func (v *int16V) Assign(t *Thread, o Value) { + *v = int16V(o.(IntValue).Get(t)); +} + +func (v *int16V) Get(*Thread) int64 { + return int64(*v); +} + +func (v *int16V) Set(t *Thread, x int64) { + *v = int16V(x); +} + +type int32V int32 + +func (v *int32V) String() string { + return fmt.Sprint(*v); +} + +func (v *int32V) Assign(t *Thread, o Value) { + *v = int32V(o.(IntValue).Get(t)); +} + +func (v *int32V) Get(*Thread) int64 { + return int64(*v); +} + +func (v *int32V) Set(t *Thread, x int64) { + *v = int32V(x); +} + +type int64V int64 + +func (v *int64V) String() string { + return fmt.Sprint(*v); +} + +func (v *int64V) Assign(t *Thread, o Value) { + *v = int64V(o.(IntValue).Get(t)); +} + +func (v *int64V) Get(*Thread) int64 { + return int64(*v); +} + +func (v *int64V) Set(t *Thread, x int64) { + *v = int64V(x); +} + +type intV int + +func (v *intV) String() string { + return fmt.Sprint(*v); +} + +func (v *intV) Assign(t *Thread, o Value) { + *v = intV(o.(IntValue).Get(t)); +} + +func (v *intV) Get(*Thread) int64 { + return int64(*v); +} + +func (v *intV) Set(t *Thread, x int64) { + *v = intV(x); +} + +/* + * Ideal int + */ + +type idealIntV struct { + V *bignum.Integer; +} + +func (v *idealIntV) String() string { + return v.V.String(); +} + +func (v *idealIntV) Assign(t *Thread, o Value) { + v.V = o.(IdealIntValue).Get(); +} + +func (v *idealIntV) Get() *bignum.Integer { + return v.V; +} + +/* + * Float + */ + +type float32V float32 + +func (v *float32V) String() string { + return fmt.Sprint(*v); +} + +func (v *float32V) Assign(t *Thread, o Value) { + *v = float32V(o.(FloatValue).Get(t)); +} + +func (v *float32V) Get(*Thread) float64 { + return float64(*v); +} + +func (v *float32V) Set(t *Thread, x float64) { + *v = float32V(x); +} + +type float64V float64 + +func (v *float64V) String() string { + return fmt.Sprint(*v); +} + +func (v *float64V) Assign(t *Thread, o Value) { + *v = float64V(o.(FloatValue).Get(t)); +} + +func (v *float64V) Get(*Thread) float64 { + return float64(*v); +} + +func (v *float64V) Set(t *Thread, x float64) { + *v = float64V(x); +} + +type floatV float + +func (v *floatV) String() string { + return fmt.Sprint(*v); +} + +func (v *floatV) Assign(t *Thread, o Value) { + *v = floatV(o.(FloatValue).Get(t)); +} + +func (v *floatV) Get(*Thread) float64 { + return float64(*v); +} + +func (v *floatV) Set(t *Thread, x float64) { + *v = floatV(x); +} + +/* + * Ideal float + */ + +type idealFloatV struct { + V *bignum.Rational; +} + +func (v *idealFloatV) String() string { + return ratToString(v.V); +} + +func (v *idealFloatV) Assign(t *Thread, o Value) { + v.V = o.(IdealFloatValue).Get(); +} + +func (v *idealFloatV) Get() *bignum.Rational { + return v.V; +} + +/* + * String + */ + +type stringV string + +func (v *stringV) String() string { + return fmt.Sprint(*v); +} + +func (v *stringV) Assign(t *Thread, o Value) { + *v = stringV(o.(StringValue).Get(t)); +} + +func (v *stringV) Get(*Thread) string { + return string(*v); +} + +func (v *stringV) Set(t *Thread, x string) { + *v = stringV(x); +} + +/* + * Array + */ + +type arrayV []Value + +func (v *arrayV) String() string { + res := "{"; + for i, e := range *v { + if i > 0 { + res += ", "; + } + res += e.String(); + } + return res + "}"; +} + +func (v *arrayV) Assign(t *Thread, o Value) { + oa := o.(ArrayValue); + l := int64(len(*v)); + for i := int64(0); i < l; i++ { + (*v)[i].Assign(t, oa.Elem(t, i)); + } +} + +func (v *arrayV) Get(*Thread) ArrayValue { + return v; +} + +func (v *arrayV) Elem(t *Thread, i int64) Value { + return (*v)[i]; +} + +func (v *arrayV) Sub(i int64, len int64) ArrayValue { + res := (*v)[i:i+len]; + return &res; +} + +/* + * Struct + */ + +type structV []Value + +// TODO(austin) Should these methods (and arrayV's) be on structV +// instead of *structV? +func (v *structV) String() string { + res := "{"; + for i, v := range *v { + if i > 0 { + res += ", "; + } + res += v.String(); + } + return res + "}"; +} + +func (v *structV) Assign(t *Thread, o Value) { + oa := o.(StructValue); + l := len(*v); + for i := 0; i < l; i++ { + (*v)[i].Assign(t, oa.Field(t, i)); + } +} + +func (v *structV) Get(*Thread) StructValue { + return v; +} + +func (v *structV) Field(t *Thread, i int) Value { + return (*v)[i]; +} + +/* + * Pointer + */ + +type ptrV struct { + // nil if the pointer is nil + target Value; +} + +func (v *ptrV) String() string { + if v.target == nil { + return "<nil>"; + } + return "&" + v.target.String(); +} + +func (v *ptrV) Assign(t *Thread, o Value) { + v.target = o.(PtrValue).Get(t); +} + +func (v *ptrV) Get(*Thread) Value { + return v.target; +} + +func (v *ptrV) Set(t *Thread, x Value) { + v.target = x; +} + +/* + * Functions + */ + +type funcV struct { + target Func; +} + +func (v *funcV) String() string { + // TODO(austin) Rob wants to see the definition + return "func {...}"; +} + +func (v *funcV) Assign(t *Thread, o Value) { + v.target = o.(FuncValue).Get(t); +} + +func (v *funcV) Get(*Thread) Func { + return v.target; +} + +func (v *funcV) Set(t *Thread, x Func) { + v.target = x; +} + +/* + * Slices + */ + +type sliceV struct { + Slice; +} + +func (v *sliceV) String() string { + if v.Base == nil { + return "<nil>"; + } + return v.Base.Sub(0, v.Len).String(); +} + +func (v *sliceV) Assign(t *Thread, o Value) { + v.Slice = o.(SliceValue).Get(t); +} + +func (v *sliceV) Get(*Thread) Slice { + return v.Slice; +} + +func (v *sliceV) Set(t *Thread, x Slice) { + v.Slice = x; +} + +/* + * Maps + */ + +type mapV struct { + target Map; +} + +func (v *mapV) String() string { + if v.target == nil { + return "<nil>"; + } + res := "map["; + i := 0; + v.target.Iter(func(key interface{}, val Value) bool { + if i > 0 { + res += ", "; + } + i++; + res += fmt.Sprint(key) + ":" + val.String(); + return true; + }); + return res + "]"; +} + +func (v *mapV) Assign(t *Thread, o Value) { + v.target = o.(MapValue).Get(t); +} + +func (v *mapV) Get(*Thread) Map { + return v.target; +} + +func (v *mapV) Set(t *Thread, x Map) { + v.target = x; +} + +type evalMap map[interface{}] Value + +func (m evalMap) Len(t *Thread) int64 { + return int64(len(m)); +} + +func (m evalMap) Elem(t *Thread, key interface{}) Value { + if v, ok := m[key]; ok { + return v; + } + return nil; +} + +func (m evalMap) SetElem(t *Thread, key interface{}, val Value) { + if val == nil { + m[key] = nil, false; + } else { + m[key] = val; + } +} + +func (m evalMap) Iter(cb func(key interface{}, val Value) bool) { + for k, v := range m { + if !cb(k, v) { + break; + } + } +} + +/* + * Multi-values + */ + +type multiV []Value + +func (v multiV) String() string { + res := "("; + for i, v := range v { + if i > 0 { + res += ", "; + } + res += v.String(); + } + return res + ")"; +} + +func (v multiV) Assign(t *Thread, o Value) { + omv := o.(multiV); + for i := range v { + v[i].Assign(t, omv[i]); + } +} + +/* + * Universal constants + */ + +// TODO(austin) Nothing complains if I accidentally define init with +// arguments. Is this intentional? +func init() { + s := universe; + + true := boolV(true); + s.DefineConst("true", universePos, BoolType, &true); + false := boolV(false); + s.DefineConst("false", universePos, BoolType, &false); +} diff --git a/src/pkg/exp/eval/world.go b/src/pkg/exp/eval/world.go new file mode 100644 index 000000000..a5e4e6092 --- /dev/null +++ b/src/pkg/exp/eval/world.go @@ -0,0 +1,192 @@ +// Copyright 2009 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. + +// This package is the beginning of an interpreter for Go. +// It can run simple Go programs but does not implement +// interface values or packages. +package eval + +import ( + "go/ast"; + "go/parser"; + "go/scanner"; + "go/token"; + "os"; +) + +type World struct { + scope *Scope; + frame *Frame; +} + +func NewWorld() (*World) { + w := new(World); + w.scope = universe.ChildScope(); + w.scope.global = true; // this block's vars allocate directly + return w; +} + +type Code interface { + // The type of the value Run returns, or nil if Run returns nil. + Type() Type; + + // Run runs the code; if the code is a single expression + // with a value, it returns the value; otherwise it returns nil. + Run() (Value, os.Error); +} + +type stmtCode struct { + w *World; + code code; +} + +func (w *World) CompileStmtList(stmts []ast.Stmt) (Code, os.Error) { + if len(stmts) == 1 { + if s, ok := stmts[0].(*ast.ExprStmt); ok { + return w.CompileExpr(s.X); + } + } + errors := scanner.NewErrorVector(); + cc := &compiler{errors, 0, 0}; + cb := newCodeBuf(); + fc := &funcCompiler{ + compiler: cc, + fnType: nil, + outVarsNamed: false, + codeBuf: cb, + flow: newFlowBuf(cb), + labels: make(map[string] *label), + }; + bc := &blockCompiler{ + funcCompiler: fc, + block: w.scope.block, + }; + nerr := cc.numError(); + for _, stmt := range stmts { + bc.compileStmt(stmt); + } + fc.checkLabels(); + if nerr != cc.numError() { + return nil, errors.GetError(scanner.Sorted); + } + return &stmtCode{w, fc.get()}, nil; +} + +func (w *World) CompileDeclList(decls []ast.Decl) (Code, os.Error) { + stmts := make([]ast.Stmt, len(decls)); + for i, d := range decls { + stmts[i] = &ast.DeclStmt{d}; + } + return w.CompileStmtList(stmts); +} + +func (s *stmtCode) Type() Type { + return nil; +} + +func (s *stmtCode) Run() (Value, os.Error) { + t := new(Thread); + t.f = s.w.scope.NewFrame(nil); + return nil, t.Try(func(t *Thread){s.code.exec(t)}); +} + +type exprCode struct { + w *World; + e *expr; + eval func(Value, *Thread); +} + +func (w *World) CompileExpr(e ast.Expr) (Code, os.Error) { + errors := scanner.NewErrorVector(); + cc := &compiler{errors, 0, 0}; + + ec := cc.compileExpr(w.scope.block, false, e); + if ec == nil { + return nil, errors.GetError(scanner.Sorted); + } + var eval func(Value, *Thread); + switch t := ec.t.(type) { + case *idealIntType: + // nothing + case *idealFloatType: + // nothing + case *MultiType: + if len(t.Elems) == 0 { + return &stmtCode{w, code{ec.exec}}, nil; + } + fallthrough; + default: + eval = genAssign(ec.t, ec); + } + return &exprCode{w, ec, eval}, nil; +} + +func (e *exprCode) Type() Type { + return e.e.t; +} + +func (e *exprCode) Run() (Value, os.Error) { + t := new(Thread); + t.f = e.w.scope.NewFrame(nil); + switch e.e.t.(type) { + case *idealIntType: + return &idealIntV{e.e.asIdealInt()()}, nil; + case *idealFloatType: + return &idealFloatV{e.e.asIdealFloat()()}, nil; + } + v := e.e.t.Zero(); + eval := e.eval; + err := t.Try(func(t *Thread){eval(v, t)}); + return v, err; +} + +func (w *World) Compile(text string) (Code, os.Error) { + stmts, err := parser.ParseStmtList("input", text); + if err == nil { + return w.CompileStmtList(stmts); + } + + // Otherwise try as DeclList. + decls, err1 := parser.ParseDeclList("input", text); + if err1 == nil { + return w.CompileDeclList(decls); + } + + // Have to pick an error. + // Parsing as statement list admits more forms, + // its error is more likely to be useful. + return nil, err; +} + +type RedefinitionError struct { + Name string; + Prev Def; +} + +func (e *RedefinitionError) String() string { + res := "identifier " + e.Name + " redeclared"; + pos := e.Prev.Pos(); + if pos.IsValid() { + res += "; previous declaration at " + pos.String(); + } + return res; +} + +func (w *World) DefineConst(name string, t Type, val Value) os.Error { + _, prev := w.scope.DefineConst(name, token.Position{}, t, val); + if prev != nil { + return &RedefinitionError{name, prev}; + } + return nil; +} + +func (w *World) DefineVar(name string, t Type, val Value) os.Error { + v, prev := w.scope.DefineVar(name, token.Position{}, t); + if prev != nil { + return &RedefinitionError{name, prev}; + } + v.Init = val; + return nil; +} + diff --git a/src/pkg/exp/ogle/Makefile b/src/pkg/exp/ogle/Makefile new file mode 100644 index 000000000..31bb9df5c --- /dev/null +++ b/src/pkg/exp/ogle/Makefile @@ -0,0 +1,29 @@ +# Copyright 2009 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. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=exp/ogle +GOFILES=\ + abort.go\ + arch.go\ + cmd.go\ + event.go\ + frame.go\ + goroutine.go\ + rruntime.go\ + rtype.go\ + rvalue.go\ + process.go\ + vars.go\ + +CLEANFILES+=ogle + +include $(GOROOT)/src/Make.pkg + +main.$O: main.go package + $(GC) -I_obj $< + +ogle: main.$O + $(LD) -L_obj -o $@ $< diff --git a/src/pkg/exp/ogle/abort.go b/src/pkg/exp/ogle/abort.go new file mode 100644 index 000000000..087c57b5f --- /dev/null +++ b/src/pkg/exp/ogle/abort.go @@ -0,0 +1,35 @@ +// Copyright 2009 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 ogle + +import ( + "os"; + "runtime"; +) + +// An aborter aborts the thread's current compututation, usually +// passing the error to a waiting thread. +type aborter interface { + Abort(err os.Error); +} + +type ogleAborter chan os.Error; + +func (a ogleAborter) Abort(err os.Error) { + a <- err; + runtime.Goexit(); +} + +// try executes a computation; if the computation Aborts, try returns +// the error passed to abort. +func try(f func(a aborter)) os.Error { + a := make(ogleAborter); + go func() { + f(a); + a <- nil; + }(); + err := <-a; + return err; +} diff --git a/src/pkg/exp/ogle/arch.go b/src/pkg/exp/ogle/arch.go new file mode 100644 index 000000000..30a2bcf58 --- /dev/null +++ b/src/pkg/exp/ogle/arch.go @@ -0,0 +1,139 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "math"; +) + +type Arch interface { + // ToWord converts an array of up to 8 bytes in memory order + // to a word. + ToWord(data []byte) proc.Word; + // FromWord converts a word to an array of up to 8 bytes in + // memory order. + FromWord(v proc.Word, out []byte); + // ToFloat32 converts a word to a float. The order of this + // word will be the order returned by ToWord on the memory + // representation of a float, and thus may require reversing. + ToFloat32(bits uint32) float32; + // FromFloat32 converts a float to a word. This should return + // a word that can be passed to FromWord to get the memory + // representation of a float on this architecture. + FromFloat32(f float32) uint32; + // ToFloat64 is to float64 as ToFloat32 is to float32. + ToFloat64(bits uint64) float64; + // FromFloat64 is to float64 as FromFloat32 is to float32. + FromFloat64(f float64) uint64; + + // IntSize returns the number of bytes in an 'int'. + IntSize() int; + // PtrSize returns the number of bytes in a 'uintptr'. + PtrSize() int; + // FloatSize returns the number of bytes in a 'float'. + FloatSize() int; + // Align rounds offset up to the appropriate offset for a + // basic type with the given width. + Align(offset, width int) int; + + // G returns the current G pointer. + G(regs proc.Regs) proc.Word; + + // ClosureSize returns the number of bytes expected by + // ParseClosure. + ClosureSize() int; + // ParseClosure takes ClosureSize bytes read from a return PC + // in a remote process, determines if the code is a closure, + // and returns the frame size of the closure if it is. + ParseClosure(data []byte) (frame int, ok bool); +} + +type ArchLSB struct {} + +func (ArchLSB) ToWord(data []byte) proc.Word { + var v proc.Word; + for i, b := range data { + v |= proc.Word(b) << (uint(i)*8); + } + return v; +} + +func (ArchLSB) FromWord(v proc.Word, out []byte) { + for i := range out { + out[i] = byte(v); + v >>= 8; + } +} + +func (ArchLSB) ToFloat32(bits uint32) float32 { + // TODO(austin) Do these definitions depend on my current + // architecture? + return math.Float32frombits(bits); +} + +func (ArchLSB) FromFloat32(f float32) uint32 { + return math.Float32bits(f); +} + +func (ArchLSB) ToFloat64(bits uint64) float64 { + return math.Float64frombits(bits); +} + +func (ArchLSB) FromFloat64(f float64) uint64 { + return math.Float64bits(f); +} + +type ArchAlignedMultiple struct {} + +func (ArchAlignedMultiple) Align(offset, width int) int { + return ((offset - 1) | (width - 1)) + 1; +} + +type amd64 struct { + ArchLSB; + ArchAlignedMultiple; + gReg int; +} + +func (a *amd64) IntSize() int { + return 4; +} + +func (a *amd64) PtrSize() int { + return 8; +} + +func (a *amd64) FloatSize() int { + return 4; +} + +func (a *amd64) G(regs proc.Regs) proc.Word { + // See src/pkg/runtime/mkasmh + if a.gReg == -1 { + ns := regs.Names(); + for i, n := range ns { + if n == "r15" { + a.gReg = i; + break; + } + } + } + + return regs.Get(a.gReg); +} + +func (a *amd64) ClosureSize() int { + return 8; +} + +func (a *amd64) ParseClosure(data []byte) (int, bool) { + if data[0] == 0x48 && data[1] == 0x81 && data[2] == 0xc4 && data[7] == 0xc3 { + return int(a.ToWord(data[3:7]) + 8), true; + } + return 0, false; +} + +var Amd64 = &amd64{gReg: -1}; diff --git a/src/pkg/exp/ogle/cmd.go b/src/pkg/exp/ogle/cmd.go new file mode 100644 index 000000000..f60621343 --- /dev/null +++ b/src/pkg/exp/ogle/cmd.go @@ -0,0 +1,375 @@ +// Copyright 2009 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. + +// Ogle is the beginning of a debugger for Go. +package ogle + +import ( + "bufio"; + "debug/elf"; + "debug/proc"; + "exp/eval"; + "fmt"; + "go/scanner"; + "go/token"; + "os"; + "strconv"; + "strings"; +) + +var world *eval.World; +var curProc *Process + +func Main() { + world = eval.NewWorld(); + defineFuncs(); + r := bufio.NewReader(os.Stdin); + for { + print("; "); + line, err := r.ReadSlice('\n'); + if err != nil { + break; + } + + // Try line as a command + cmd, rest := getCmd(line); + if cmd != nil { + err := cmd.handler(rest); + if err != nil { + scanner.PrintError(os.Stderr, err); + } + continue; + } + + // Try line as code + code, err := world.Compile(string(line)); + if err != nil { + scanner.PrintError(os.Stderr, err); + continue; + } + v, err := code.Run(); + if err != nil { + fmt.Fprintf(os.Stderr, err.String()); + continue; + } + if v != nil { + println(v.String()); + } + } +} + +// newScanner creates a new scanner that scans that given input bytes. +func newScanner(input []byte) (*scanner.Scanner, *scanner.ErrorVector) { + sc := new(scanner.Scanner); + ev := new(scanner.ErrorVector); + ev.Init(); + sc.Init("input", input, ev, 0); + + return sc, ev; +} + +/* + * Commands + */ + +// A UsageError occurs when a command is called with illegal arguments. +type UsageError string; + +func (e UsageError) String() string { + return string(e); +} + +// A cmd represents a single command with a handler. +type cmd struct { + cmd string; + handler func([]byte) os.Error; +} + +var cmds = []cmd { + cmd{"load", cmdLoad}, + cmd{"bt", cmdBt}, +} + +// getCmd attempts to parse an input line as a registered command. If +// successful, it returns the command and the bytes remaining after +// the command, which should be passed to the command. +func getCmd(line []byte) (*cmd, []byte) { + sc, _ := newScanner(line); + pos, tok, lit := sc.Scan(); + if sc.ErrorCount != 0 || tok != token.IDENT { + return nil, nil; + } + + slit := string(lit); + for i := range cmds { + if cmds[i].cmd == slit { + return &cmds[i], line[pos.Offset + len(lit):len(line)]; + } + } + return nil, nil; +} + +// cmdLoad starts or attaches to a process. Its form is similar to +// import: +// +// load [sym] "path" [;] +// +// sym specifies the name to give to the process. If not given, the +// name is derived from the path of the process. If ".", then the +// packages from the remote process are defined into the current +// namespace. If given, this symbol is defined as a package +// containing the process' packages. +// +// path gives the path of the process to start or attach to. If it is +// "pid:<num>", then attach to the given PID. Otherwise, treat it as +// a file path and space-separated arguments and start a new process. +// +// load always sets the current process to the loaded process. +func cmdLoad(args []byte) os.Error { + ident, path, err := parseLoad(args); + if err != nil { + return err; + } + if curProc != nil { + return UsageError("multiple processes not implemented"); + } + if ident != "." { + return UsageError("process identifiers not implemented"); + } + + // Parse argument and start or attach to process + var fname string; + var tproc proc.Process; + if len(path) >= 4 && path[0:4] == "pid:" { + pid, err := strconv.Atoi(path[4:len(path)]); + if err != nil { + return err; + } + fname, err = os.Readlink(fmt.Sprintf("/proc/%d/exe", pid)); + if err != nil { + return err; + } + tproc, err = proc.Attach(pid); + if err != nil { + return err; + } + println("Attached to", pid); + } else { + parts := strings.Split(path, " ", 0); + if len(parts) == 0 { + fname = ""; + } else { + fname = parts[0]; + } + tproc, err = proc.ForkExec(fname, parts, os.Environ(), "", []*os.File{os.Stdin, os.Stdout, os.Stderr}); + if err != nil { + return err; + } + println("Started", path); + // TODO(austin) If we fail after this point, kill tproc + // before detaching. + } + + // Get symbols + f, err := os.Open(fname, os.O_RDONLY, 0); + if err != nil { + tproc.Detach(); + return err; + } + defer f.Close(); + elf, err := elf.NewFile(f); + if err != nil { + tproc.Detach(); + return err; + } + curProc, err = NewProcessElf(tproc, elf); + if err != nil { + tproc.Detach(); + return err; + } + + // Prepare new process + curProc.OnGoroutineCreate().AddHandler(EventPrint); + curProc.OnGoroutineExit().AddHandler(EventPrint); + + err = curProc.populateWorld(world); + if err != nil { + tproc.Detach(); + return err; + } + + return nil; +} + +func parseLoad(args []byte) (ident string, path string, err os.Error) { + err = UsageError("Usage: load [sym] \"path\""); + sc, ev := newScanner(args); + + var toks [4]token.Token; + var lits [4][]byte; + for i := range toks { + _, toks[i], lits[i] = sc.Scan(); + } + if sc.ErrorCount != 0 { + err = ev.GetError(scanner.NoMultiples); + return; + } + + i := 0; + switch toks[i] { + case token.PERIOD, token.IDENT: + ident = string(lits[i]); + i++; + } + + if toks[i] != token.STRING { + return; + } + path, uerr := strconv.Unquote(string(lits[i])); + if uerr != nil { + err = uerr; + return; + } + i++; + + if toks[i] == token.SEMICOLON { + i++; + } + if toks[i] != token.EOF { + return; + } + + return ident, path, nil; +} + +// cmdBt prints a backtrace for the current goroutine. It takes no +// arguments. +func cmdBt(args []byte) os.Error { + err := parseNoArgs(args, "Usage: bt"); + if err != nil { + return err; + } + + if curProc == nil || curProc.curGoroutine == nil { + return NoCurrentGoroutine{}; + } + + f := curProc.curGoroutine.frame; + if f == nil { + fmt.Println("No frames on stack"); + return nil; + } + + for f.Inner() != nil { + f = f.Inner(); + } + + for i := 0; i < 100; i++ { + if f == curProc.curGoroutine.frame { + fmt.Printf("=> "); + } else { + fmt.Printf(" "); + } + fmt.Printf("%8x %v\n", f.pc, f); + f, err = f.Outer(); + if err != nil { + return err; + } + if f == nil { + return nil; + } + } + + fmt.Println("..."); + return nil; +} + +func parseNoArgs(args []byte, usage string) os.Error { + sc, ev := newScanner(args); + _, tok, _ := sc.Scan(); + if sc.ErrorCount != 0 { + return ev.GetError(scanner.NoMultiples); + } + if tok != token.EOF { + return UsageError(usage); + } + return nil; +} + +/* + * Functions + */ + +// defineFuncs populates world with the built-in functions. +func defineFuncs() { + t, v := eval.FuncFromNativeTyped(fnOut, fnOutSig); + world.DefineConst("Out", t, v); + t, v = eval.FuncFromNativeTyped(fnContWait, fnContWaitSig); + world.DefineConst("ContWait", t, v); + t, v = eval.FuncFromNativeTyped(fnBpSet, fnBpSetSig); + world.DefineConst("BpSet", t, v); +} + +// printCurFrame prints the current stack frame, as it would appear in +// a backtrace. +func printCurFrame() { + if curProc == nil || curProc.curGoroutine == nil { + return; + } + f := curProc.curGoroutine.frame; + if f == nil { + return; + } + fmt.Printf("=> %8x %v\n", f.pc, f); +} + +// fnOut moves the current frame to the caller of the current frame. +func fnOutSig() {} +func fnOut(t *eval.Thread, args []eval.Value, res []eval.Value) { + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + err := curProc.Out(); + if err != nil { + t.Abort(err); + } + // TODO(austin) Only in the command form + printCurFrame(); +} + +// fnContWait continues the current process and waits for a stopping event. +func fnContWaitSig() {} +func fnContWait(t *eval.Thread, args []eval.Value, res []eval.Value) { + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + err := curProc.ContWait(); + if err != nil { + t.Abort(err); + } + // TODO(austin) Only in the command form + ev := curProc.Event(); + if ev != nil { + fmt.Printf("%v\n", ev); + } + printCurFrame(); +} + +// fnBpSet sets a breakpoint at the entry to the named function. +func fnBpSetSig(string) {} +func fnBpSet(t *eval.Thread, args []eval.Value, res []eval.Value) { + // TODO(austin) This probably shouldn't take a symbol name. + // Perhaps it should take an interface that provides PC's. + // Functions and instructions can implement that interface and + // we can have something to translate file:line pairs. + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + name := args[0].(eval.StringValue).Get(t); + fn := curProc.syms.LookupFunc(name); + if fn == nil { + t.Abort(UsageError("no such function " + name)); + } + curProc.OnBreakpoint(proc.Word(fn.Entry)).AddHandler(EventStop); +} diff --git a/src/pkg/exp/ogle/event.go b/src/pkg/exp/ogle/event.go new file mode 100644 index 000000000..9dc7a8445 --- /dev/null +++ b/src/pkg/exp/ogle/event.go @@ -0,0 +1,294 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "fmt"; + "os"; +) + +/* + * Hooks and events + */ + +// An EventHandler is a function that takes an event and returns a +// response to that event and possibly an error. If an event handler +// returns an error, the process stops and no other handlers for that +// event are executed. +type EventHandler func(e Event) (EventAction, os.Error) + +// An EventAction is an event handler's response to an event. If all +// of an event's handlers execute without returning errors, their +// results are combined as follows: If any handler returned +// EAContinue, then the process resumes (without returning from +// WaitStop); otherwise, if any handler returned EAStop, the process +// remains stopped; otherwise, if all handlers returned EADefault, the +// process resumes. A handler may return EARemoveSelf bit-wise or'd +// with any other action to indicate that the handler should be +// removed from the hook. +type EventAction int + +const ( + EARemoveSelf EventAction = 0x100; + EADefault EventAction = iota; + EAStop; + EAContinue; +) + +// A EventHook allows event handlers to be added and removed. +type EventHook interface { + AddHandler(EventHandler); + RemoveHandler(EventHandler); + NumHandler() int; + handle(e Event) (EventAction, os.Error); + String() string; +} + +// EventHook is almost, but not quite, suitable for user-defined +// events. If we want user-defined events, make EventHook a struct, +// special-case adding and removing handlers in breakpoint hooks, and +// provide a public interface for posting events to hooks. + +type Event interface { + Process() *Process; + Goroutine() *Goroutine; + String() string; +} + +type commonHook struct { + // Head of handler chain + head *handler; + // Number of non-internal handlers + len int; +} + +type handler struct { + eh EventHandler; + // True if this handler must be run before user-defined + // handlers in order to ensure correctness. + internal bool; + // True if this handler has been removed from the chain. + removed bool; + next *handler; +} + +func (h *commonHook) AddHandler(eh EventHandler) { + h.addHandler(eh, false); +} + +func (h *commonHook) addHandler(eh EventHandler, internal bool) { + // Ensure uniqueness of handlers + h.RemoveHandler(eh); + + if !internal { + h.len++; + } + // Add internal handlers to the beginning + if internal || h.head == nil { + h.head = &handler{eh, internal, false, h.head}; + return; + } + // Add handler after internal handlers + // TODO(austin) This should probably go on the end instead + prev := h.head; + for prev.next != nil && prev.internal { + prev = prev.next; + } + prev.next = &handler{eh, internal, false, prev.next}; +} + +func (h *commonHook) RemoveHandler(eh EventHandler) { + plink := &h.head; + for l := *plink; l != nil; plink, l = &l.next, l.next { + if l.eh == eh { + if !l.internal { + h.len--; + } + l.removed = true; + *plink = l.next; + break; + } + } +} + +func (h *commonHook) NumHandler() int { + return h.len; +} + +func (h *commonHook) handle(e Event) (EventAction, os.Error) { + action := EADefault; + plink := &h.head; + for l := *plink; l != nil; plink, l = &l.next, l.next { + if l.removed { + continue; + } + a, err := l.eh(e); + if a & EARemoveSelf == EARemoveSelf { + if !l.internal { + h.len--; + } + l.removed = true; + *plink = l.next; + a &^= EARemoveSelf; + } + if err != nil { + return EAStop, err; + } + if a > action { + action = a; + } + } + return action, nil; +} + +type commonEvent struct { + // The process of this event + p *Process; + // The goroutine of this event. + t *Goroutine; +} + +func (e *commonEvent) Process() *Process { + return e.p; +} + +func (e *commonEvent) Goroutine() *Goroutine { + return e.t; +} + +/* + * Standard event handlers + */ + +// EventPrint is a standard event handler that prints events as they +// occur. It will not cause the process to stop. +func EventPrint(ev Event) (EventAction, os.Error) { + // TODO(austin) Include process name here? + fmt.Fprintf(os.Stderr, "*** %v\n", ev.String()); + return EADefault, nil; +} + +// EventStop is a standard event handler that causes the process to stop. +func EventStop(ev Event) (EventAction, os.Error) { + return EAStop, nil; +} + +/* + * Breakpoints + */ + +type breakpointHook struct { + commonHook; + p *Process; + pc proc.Word; +} + +// A Breakpoint event occurs when a process reaches a particular +// program counter. When this event is handled, the current goroutine +// will be the goroutine that reached the program counter. +type Breakpoint struct { + commonEvent; + osThread proc.Thread; + pc proc.Word; +} + +func (h *breakpointHook) AddHandler(eh EventHandler) { + h.addHandler(eh, false); +} + +func (h *breakpointHook) addHandler(eh EventHandler, internal bool) { + // We register breakpoint events lazily to avoid holding + // references to breakpoints without handlers. Be sure to use + // the "canonical" breakpoint if there is one. + if cur, ok := h.p.breakpointHooks[h.pc]; ok { + h = cur; + } + oldhead := h.head; + h.commonHook.addHandler(eh, internal); + if oldhead == nil && h.head != nil { + h.p.proc.AddBreakpoint(h.pc); + h.p.breakpointHooks[h.pc] = h; + } +} + +func (h *breakpointHook) RemoveHandler(eh EventHandler) { + oldhead := h.head; + h.commonHook.RemoveHandler(eh); + if oldhead != nil && h.head == nil { + h.p.proc.RemoveBreakpoint(h.pc); + h.p.breakpointHooks[h.pc] = nil, false; + } +} + +func (h *breakpointHook) String() string { + // TODO(austin) Include process name? + // TODO(austin) Use line:pc or at least sym+%#x + return fmt.Sprintf("breakpoint at %#x", h.pc); +} + +func (b *Breakpoint) PC() proc.Word { + return b.pc; +} + +func (b *Breakpoint) String() string { + // TODO(austin) Include process name and goroutine + // TODO(austin) Use line:pc or at least sym+%#x + return fmt.Sprintf("breakpoint at %#x", b.pc); +} + +/* + * Goroutine create/exit + */ + +type goroutineCreateHook struct { + commonHook; +} + +func (h *goroutineCreateHook) String() string { + return "goroutine create"; +} + +// A GoroutineCreate event occurs when a process creates a new +// goroutine. When this event is handled, the current goroutine will +// be the newly created goroutine. +type GoroutineCreate struct { + commonEvent; + parent *Goroutine; +} + +// Parent returns the goroutine that created this goroutine. May be +// nil if this event is the creation of the first goroutine. +func (e *GoroutineCreate) Parent() *Goroutine { + return e.parent; +} + +func (e *GoroutineCreate) String() string { + // TODO(austin) Include process name + if e.parent == nil { + return fmt.Sprintf("%v created", e.t); + } + return fmt.Sprintf("%v created by %v", e.t, e.parent); +} + +type goroutineExitHook struct { + commonHook; +} + +func (h *goroutineExitHook) String() string { + return "goroutine exit"; +} + +// A GoroutineExit event occurs when a Go goroutine exits. +type GoroutineExit struct { + commonEvent; +} + +func (e *GoroutineExit) String() string { + // TODO(austin) Include process name + //return fmt.Sprintf("%v exited", e.t); + // For debugging purposes + return fmt.Sprintf("goroutine %#x exited", e.t.g.addr().base); +} diff --git a/src/pkg/exp/ogle/frame.go b/src/pkg/exp/ogle/frame.go new file mode 100644 index 000000000..bf2b39134 --- /dev/null +++ b/src/pkg/exp/ogle/frame.go @@ -0,0 +1,214 @@ +// Copyright 2009 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 ogle + +import ( + "debug/gosym"; + "debug/proc"; + "fmt"; + "os"; +) + +// A Frame represents a single frame on a remote call stack. +type Frame struct { + // pc is the PC of the next instruction that will execute in + // this frame. For lower frames, this is the instruction + // following the CALL instruction. + pc, sp, fp proc.Word; + // The runtime.Stktop of the active stack segment + stk remoteStruct; + // The function this stack frame is in + fn *gosym.Func; + // The path and line of the CALL or current instruction. Note + // that this differs slightly from the meaning of Frame.pc. + path string; + line int; + // The inner and outer frames of this frame. outer is filled + // in lazily. + inner, outer *Frame; +} + +// newFrame returns the top-most Frame of the given g's thread. +func newFrame(g remoteStruct) (*Frame, os.Error) { + var f *Frame; + err := try(func(a aborter) { f = aNewFrame(a, g) }); + return f, err; +} + +func aNewFrame(a aborter, g remoteStruct) *Frame { + p := g.r.p; + var pc, sp proc.Word; + + // Is this G alive? + switch g.field(p.f.G.Status).(remoteInt).aGet(a) { + case p.runtime.Gidle, p.runtime.Gmoribund, p.runtime.Gdead: + return nil; + } + + // Find the OS thread for this G + + // TODO(austin) Ideally, we could look at the G's state and + // figure out if it's on an OS thread or not. However, this + // is difficult because the state isn't updated atomically + // with scheduling changes. + for _, t := range p.proc.Threads() { + regs, err := t.Regs(); + if err != nil { + // TODO(austin) What to do? + continue; + } + thisg := p.G(regs); + if thisg == g.addr().base { + // Found this G's OS thread + pc = regs.PC(); + sp = regs.SP(); + + // If this thread crashed, try to recover it + if pc == 0 { + pc = p.peekUintptr(a, pc); + sp += 8; + } + + break; + } + } + + if pc == 0 && sp == 0 { + // G is not mapped to an OS thread. Use the + // scheduler's stored PC and SP. + sched := g.field(p.f.G.Sched).(remoteStruct); + pc = proc.Word(sched.field(p.f.Gobuf.Pc).(remoteUint).aGet(a)); + sp = proc.Word(sched.field(p.f.Gobuf.Sp).(remoteUint).aGet(a)); + } + + // Get Stktop + stk := g.field(p.f.G.Stackbase).(remotePtr).aGet(a).(remoteStruct); + + return prepareFrame(a, pc, sp, stk, nil); +} + +// prepareFrame creates a Frame from the PC and SP within that frame, +// as well as the active stack segment. This function takes care of +// traversing stack breaks and unwinding closures. +func prepareFrame(a aborter, pc, sp proc.Word, stk remoteStruct, inner *Frame) *Frame { + // Based on src/pkg/runtime/amd64/traceback.c:traceback + p := stk.r.p; + top := inner == nil; + + // Get function + var path string; + var line int; + var fn *gosym.Func; + + for i := 0; i < 100; i++ { + // Traverse segmented stack breaks + if p.sys.lessstack != nil && pc == proc.Word(p.sys.lessstack.Value) { + // Get stk->gobuf.pc + pc = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Pc).(remoteUint).aGet(a)); + // Get stk->gobuf.sp + sp = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Sp).(remoteUint).aGet(a)); + // Get stk->stackbase + stk = stk.field(p.f.Stktop.Stackbase).(remotePtr).aGet(a).(remoteStruct); + continue; + } + + // Get the PC of the call instruction + callpc := pc; + if !top && (p.sys.goexit == nil || pc != proc.Word(p.sys.goexit.Value)) { + callpc--; + } + + // Look up function + path, line, fn = p.syms.PCToLine(uint64(callpc)); + if fn != nil { + break; + } + + // Closure? + var buf = make([]byte, p.ClosureSize()); + if _, err := p.Peek(pc, buf); err != nil { + break; + } + spdelta, ok := p.ParseClosure(buf); + if ok { + sp += proc.Word(spdelta); + pc = p.peekUintptr(a, sp - proc.Word(p.PtrSize())); + } + } + if fn == nil { + return nil; + } + + // Compute frame pointer + var fp proc.Word; + if fn.FrameSize < p.PtrSize() { + fp = sp + proc.Word(p.PtrSize()); + } else { + fp = sp + proc.Word(fn.FrameSize); + } + // TODO(austin) To really figure out if we're in the prologue, + // we need to disassemble the function and look for the call + // to morestack. For now, just special case the entry point. + // + // TODO(austin) What if we're in the call to morestack in the + // prologue? Then top == false. + if top && pc == proc.Word(fn.Entry) { + // We're in the function prologue, before SP + // has been adjusted for the frame. + fp -= proc.Word(fn.FrameSize - p.PtrSize()); + } + + return &Frame{pc, sp, fp, stk, fn, path, line, inner, nil}; +} + +// Outer returns the Frame that called this Frame, or nil if this is +// the outermost frame. +func (f *Frame) Outer() (*Frame, os.Error) { + var fr *Frame; + err := try(func(a aborter) { fr = f.aOuter(a) }); + return fr, err; +} + +func (f *Frame) aOuter(a aborter) *Frame { + // Is there a cached outer frame + if f.outer != nil { + return f.outer; + } + + p := f.stk.r.p; + + sp := f.fp; + if f.fn == p.sys.newproc && f.fn == p.sys.deferproc { + // TODO(rsc) The compiler inserts two push/pop's + // around calls to go and defer. Russ says this + // should get fixed in the compiler, but we account + // for it for now. + sp += proc.Word(2 * p.PtrSize()); + } + + pc := p.peekUintptr(a, f.fp - proc.Word(p.PtrSize())); + if pc < 0x1000 { + return nil; + } + + // TODO(austin) Register this frame for shoot-down. + + f.outer = prepareFrame(a, pc, sp, f.stk, f); + return f.outer; +} + +// Inner returns the Frame called by this Frame, or nil if this is the +// innermost frame. +func (f *Frame) Inner() *Frame { + return f.inner; +} + +func (f *Frame) String() string { + res := f.fn.Name; + if f.pc > proc.Word(f.fn.Value) { + res += fmt.Sprintf("+%#x", f.pc - proc.Word(f.fn.Entry)); + } + return res + fmt.Sprintf(" %s:%d", f.path, f.line); +} diff --git a/src/pkg/exp/ogle/goroutine.go b/src/pkg/exp/ogle/goroutine.go new file mode 100644 index 000000000..4d458c561 --- /dev/null +++ b/src/pkg/exp/ogle/goroutine.go @@ -0,0 +1,119 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "exp/eval"; + "fmt"; + "os"; +) + +// A Goroutine represents a goroutine in a remote process. +type Goroutine struct { + g remoteStruct; + frame *Frame; + dead bool; +} + +func (t *Goroutine) String() string { + if t.dead { + return "<dead thread>"; + } + // TODO(austin) Give threads friendly ID's, possibly including + // the name of the entry function. + return fmt.Sprintf("thread %#x", t.g.addr().base); +} + +// isG0 returns true if this thread if the internal idle thread +func (t *Goroutine) isG0() bool { + return t.g.addr().base == t.g.r.p.sys.g0.addr().base; +} + +func (t *Goroutine) resetFrame() (err os.Error) { + // TODO(austin) Reuse any live part of the current frame stack + // so existing references to Frame's keep working. + t.frame, err = newFrame(t.g); + return; +} + +// Out selects the caller frame of the current frame. +func (t *Goroutine) Out() os.Error { + f, err := t.frame.Outer(); + if f != nil { + t.frame = f; + } + return err; +} + +// In selects the frame called by the current frame. +func (t *Goroutine) In() os.Error { + f := t.frame.Inner(); + if f != nil { + t.frame = f; + } + return nil; +} + +func readylockedBP(ev Event) (EventAction, os.Error) { + b := ev.(*Breakpoint); + p := b.Process(); + + // The new g is the only argument to this function, so the + // stack will have the return address, then the G*. + regs, err := b.osThread.Regs(); + if err != nil { + return EAStop, err; + } + sp := regs.SP(); + addr := sp + proc.Word(p.PtrSize()); + arg := remotePtr{remote{addr, p}, p.runtime.G}; + var gp eval.Value; + err = try(func(a aborter) { gp = arg.aGet(a) }); + if err != nil { + return EAStop, err; + } + if gp == nil { + return EAStop, UnknownGoroutine{b.osThread, 0}; + } + gs := gp.(remoteStruct); + g := &Goroutine{gs, nil, false}; + p.goroutines[gs.addr().base] = g; + + // Enqueue goroutine creation event + parent := b.Goroutine(); + if parent.isG0() { + parent = nil; + } + p.postEvent(&GoroutineCreate{commonEvent{p, g}, parent}); + + // If we don't have any thread selected, select this one + if p.curGoroutine == nil { + p.curGoroutine = g; + } + + return EADefault, nil; +} + +func goexitBP(ev Event) (EventAction, os.Error) { + b := ev.(*Breakpoint); + p := b.Process(); + + g := b.Goroutine(); + g.dead = true; + + addr := g.g.addr().base; + p.goroutines[addr] = nil, false; + + // Enqueue thread exit event + p.postEvent(&GoroutineExit{commonEvent{p, g}}); + + // If we just exited our selected goroutine, selected another + if p.curGoroutine == g { + p.selectSomeGoroutine(); + } + + return EADefault, nil; +} diff --git a/src/pkg/exp/ogle/main.go b/src/pkg/exp/ogle/main.go new file mode 100644 index 000000000..88265624a --- /dev/null +++ b/src/pkg/exp/ogle/main.go @@ -0,0 +1,11 @@ +// Copyright 2009 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 main + +import "exp/ogle" + +func main() { + ogle.Main(); +} diff --git a/src/pkg/exp/ogle/process.go b/src/pkg/exp/ogle/process.go new file mode 100644 index 000000000..984364f23 --- /dev/null +++ b/src/pkg/exp/ogle/process.go @@ -0,0 +1,541 @@ +// Copyright 2009 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 ogle + +import ( + "debug/elf"; + "debug/gosym"; + "debug/proc"; + "exp/eval"; + "fmt"; + "log"; + "os"; + "reflect"; +) + +// A FormatError indicates a failure to process information in or +// about a remote process, such as unexpected or missing information +// in the object file or runtime structures. +type FormatError string + +func (e FormatError) String() string { + return string(e); +} + +// An UnknownArchitecture occurs when trying to load an object file +// that indicates an architecture not supported by the debugger. +type UnknownArchitecture elf.Machine + +func (e UnknownArchitecture) String() string { + return "unknown architecture: " + elf.Machine(e).String(); +} + +// A ProcessNotStopped error occurs when attempting to read or write +// memory or registers of a process that is not stopped. +type ProcessNotStopped struct {} + +func (e ProcessNotStopped) String() string { + return "process not stopped"; +} + +// An UnknownGoroutine error is an internal error representing an +// unrecognized G structure pointer. +type UnknownGoroutine struct { + OSThread proc.Thread; + Goroutine proc.Word; +} + +func (e UnknownGoroutine) String() string { + return fmt.Sprintf("internal error: unknown goroutine (G %#x)", e.Goroutine); +} + +// A NoCurrentGoroutine error occurs when no goroutine is currently +// selected in a process (or when there are no goroutines in a +// process). +type NoCurrentGoroutine struct {} + +func (e NoCurrentGoroutine) String() string { + return "no current goroutine"; +} + +// A Process represents a remote attached process. +type Process struct { + Arch; + proc proc.Process; + + // The symbol table of this process + syms *gosym.Table; + + // A possibly-stopped OS thread, or nil + threadCache proc.Thread; + + // Types parsed from the remote process + types map[proc.Word] *remoteType; + + // Types and values from the remote runtime package + runtime runtimeValues; + + // Runtime field indexes + f runtimeIndexes; + + // Globals from the sys package (or from no package) + sys struct { + lessstack, goexit, newproc, deferproc, newprocreadylocked *gosym.Func; + allg remotePtr; + g0 remoteStruct; + }; + + // Event queue + posted []Event; + pending []Event; + event Event; + + // Event hooks + breakpointHooks map[proc.Word] *breakpointHook; + goroutineCreateHook *goroutineCreateHook; + goroutineExitHook *goroutineExitHook; + + // Current goroutine, or nil if there are no goroutines + curGoroutine *Goroutine; + + // Goroutines by the address of their G structure + goroutines map[proc.Word] *Goroutine; +} + +/* + * Process creation + */ + +// NewProcess constructs a new remote process around a traced +// process, an architecture, and a symbol table. +func NewProcess(tproc proc.Process, arch Arch, syms *gosym.Table) (*Process, os.Error) { + p := &Process{ + Arch: arch, + proc: tproc, + syms: syms, + types: make(map[proc.Word] *remoteType), + breakpointHooks: make(map[proc.Word] *breakpointHook), + goroutineCreateHook: new(goroutineCreateHook), + goroutineExitHook: new(goroutineExitHook), + goroutines: make(map[proc.Word] *Goroutine), + }; + + // Fill in remote runtime + p.bootstrap(); + + switch { + case p.sys.allg.addr().base == 0: + return nil, FormatError("failed to find runtime symbol 'allg'"); + case p.sys.g0.addr().base == 0: + return nil, FormatError("failed to find runtime symbol 'g0'"); + case p.sys.newprocreadylocked == nil: + return nil, FormatError("failed to find runtime symbol 'newprocreadylocked'"); + case p.sys.goexit == nil: + return nil, FormatError("failed to find runtime symbol 'sys.goexit'"); + } + + // Get current goroutines + p.goroutines[p.sys.g0.addr().base] = &Goroutine{p.sys.g0, nil, false}; + err := try(func(a aborter) { + g := p.sys.allg.aGet(a); + for g != nil { + gs := g.(remoteStruct); + fmt.Printf("*** Found goroutine at %#x\n", gs.addr().base); + p.goroutines[gs.addr().base] = &Goroutine{gs, nil, false}; + g = gs.field(p.f.G.Alllink).(remotePtr).aGet(a); + } + }); + if err != nil { + return nil, err; + } + + // Create internal breakpoints to catch new and exited goroutines + p.OnBreakpoint(proc.Word(p.sys.newprocreadylocked.Entry)).(*breakpointHook).addHandler(readylockedBP, true); + p.OnBreakpoint(proc.Word(p.sys.goexit.Entry)).(*breakpointHook).addHandler(goexitBP, true); + + // Select current frames + for _, g := range p.goroutines { + g.resetFrame(); + } + + p.selectSomeGoroutine(); + + return p, nil; +} + +func elfGoSyms(f *elf.File) (*gosym.Table, os.Error) { + text := f.Section(".text"); + symtab := f.Section(".gosymtab"); + pclntab := f.Section(".gopclntab"); + if text == nil || symtab == nil || pclntab == nil { + return nil, nil; + } + + symdat, err := symtab.Data(); + if err != nil { + return nil, err; + } + pclndat, err := pclntab.Data(); + if err != nil { + return nil, err; + } + + pcln := gosym.NewLineTable(pclndat, text.Addr); + tab, err := gosym.NewTable(symdat, pcln); + if err != nil { + return nil, err; + } + + return tab, nil; +} + +// NewProcessElf constructs a new remote process around a traced +// process and the process' ELF object. +func NewProcessElf(tproc proc.Process, f *elf.File) (*Process, os.Error) { + syms, err := elfGoSyms(f); + if err != nil { + return nil, err; + } + if syms == nil { + return nil, FormatError("Failed to find symbol table"); + } + var arch Arch; + switch f.Machine { + case elf.EM_X86_64: + arch = Amd64; + default: + return nil, UnknownArchitecture(f.Machine); + } + return NewProcess(tproc, arch, syms); +} + +// bootstrap constructs the runtime structure of a remote process. +func (p *Process) bootstrap() { + // Manually construct runtime types + p.runtime.String = newManualType(eval.TypeOfNative(rt1String{}), p.Arch); + p.runtime.Slice = newManualType(eval.TypeOfNative(rt1Slice{}), p.Arch); + p.runtime.Eface = newManualType(eval.TypeOfNative(rt1Eface{}), p.Arch); + + p.runtime.Type = newManualType(eval.TypeOfNative(rt1Type{}), p.Arch); + p.runtime.CommonType = newManualType(eval.TypeOfNative(rt1CommonType{}), p.Arch); + p.runtime.UncommonType = newManualType(eval.TypeOfNative(rt1UncommonType{}), p.Arch); + p.runtime.StructField = newManualType(eval.TypeOfNative(rt1StructField{}), p.Arch); + p.runtime.StructType = newManualType(eval.TypeOfNative(rt1StructType{}), p.Arch); + p.runtime.PtrType = newManualType(eval.TypeOfNative(rt1PtrType{}), p.Arch); + p.runtime.ArrayType = newManualType(eval.TypeOfNative(rt1ArrayType{}), p.Arch); + p.runtime.SliceType = newManualType(eval.TypeOfNative(rt1SliceType{}), p.Arch); + + p.runtime.Stktop = newManualType(eval.TypeOfNative(rt1Stktop{}), p.Arch); + p.runtime.Gobuf = newManualType(eval.TypeOfNative(rt1Gobuf{}), p.Arch); + p.runtime.G = newManualType(eval.TypeOfNative(rt1G{}), p.Arch); + + // Get addresses of type.*runtime.XType for discrimination. + rtv := reflect.Indirect(reflect.NewValue(&p.runtime)).(*reflect.StructValue); + rtvt := rtv.Type().(*reflect.StructType); + for i := 0; i < rtv.NumField(); i++ { + n := rtvt.Field(i).Name; + if n[0] != 'P' || n[1] < 'A' || n[1] > 'Z' { + continue; + } + sym := p.syms.LookupSym("type.*runtime." + n[1:len(n)]); + if sym == nil { + continue; + } + rtv.Field(i).(*reflect.Uint64Value).Set(sym.Value); + } + + // Get runtime field indexes + fillRuntimeIndexes(&p.runtime, &p.f); + + // Fill G status + p.runtime.runtimeGStatus = rt1GStatus; + + // Get globals + p.sys.lessstack = p.syms.LookupFunc("sys.lessstack"); + p.sys.goexit = p.syms.LookupFunc("goexit"); + p.sys.newproc = p.syms.LookupFunc("sys.newproc"); + p.sys.deferproc = p.syms.LookupFunc("sys.deferproc"); + p.sys.newprocreadylocked = p.syms.LookupFunc("newprocreadylocked"); + if allg := p.syms.LookupSym("allg"); allg != nil { + p.sys.allg = remotePtr{remote{proc.Word(allg.Value), p}, p.runtime.G}; + } + if g0 := p.syms.LookupSym("g0"); g0 != nil { + p.sys.g0 = p.runtime.G.mk(remote{proc.Word(g0.Value), p}).(remoteStruct); + } +} + +func (p *Process) selectSomeGoroutine() { + // Once we have friendly goroutine ID's, there might be a more + // reasonable behavior for this. + p.curGoroutine = nil; + for _, g := range p.goroutines { + if !g.isG0() && g.frame != nil { + p.curGoroutine = g; + return; + } + } +} + +/* + * Process memory + */ + +func (p *Process) someStoppedOSThread() proc.Thread { + if p.threadCache != nil { + if _, err := p.threadCache.Stopped(); err == nil { + return p.threadCache; + } + } + + for _, t := range p.proc.Threads() { + if _, err := t.Stopped(); err == nil { + p.threadCache = t; + return t; + } + } + return nil; +} + +func (p *Process) Peek(addr proc.Word, out []byte) (int, os.Error) { + thr := p.someStoppedOSThread(); + if thr == nil { + return 0, ProcessNotStopped{}; + } + return thr.Peek(addr, out); +} + +func (p *Process) Poke(addr proc.Word, b []byte) (int, os.Error) { + thr := p.someStoppedOSThread(); + if thr == nil { + return 0, ProcessNotStopped{}; + } + return thr.Poke(addr, b); +} + +func (p *Process) peekUintptr(a aborter, addr proc.Word) proc.Word { + return proc.Word(mkUintptr(remote{addr, p}).(remoteUint).aGet(a)); +} + +/* + * Events + */ + +// OnBreakpoint returns the hook that is run when the program reaches +// the given program counter. +func (p *Process) OnBreakpoint(pc proc.Word) EventHook { + if bp, ok := p.breakpointHooks[pc]; ok { + return bp; + } + // The breakpoint will register itself when a handler is added + return &breakpointHook{commonHook{nil, 0}, p, pc}; +} + +// OnGoroutineCreate returns the hook that is run when a goroutine is created. +func (p *Process) OnGoroutineCreate() EventHook { + return p.goroutineCreateHook; +} + +// OnGoroutineExit returns the hook that is run when a goroutine exits. +func (p *Process) OnGoroutineExit() EventHook { + return p.goroutineExitHook; +} + +// osThreadToGoroutine looks up the goroutine running on an OS thread. +func (p *Process) osThreadToGoroutine(t proc.Thread) (*Goroutine, os.Error) { + regs, err := t.Regs(); + if err != nil { + return nil, err; + } + g := p.G(regs); + gt, ok := p.goroutines[g]; + if !ok { + return nil, UnknownGoroutine{t, g}; + } + return gt, nil; +} + +// causesToEvents translates the stop causes of the underlying process +// into an event queue. +func (p *Process) causesToEvents() ([]Event, os.Error) { + // Count causes we're interested in + nev := 0; + for _, t := range p.proc.Threads() { + if c, err := t.Stopped(); err == nil { + switch c := c.(type) { + case proc.Breakpoint: + nev++; + case proc.Signal: + // TODO(austin) + //nev++; + } + } + } + + // Translate causes to events + events := make([]Event, nev); + i := 0; + for _, t := range p.proc.Threads() { + if c, err := t.Stopped(); err == nil { + switch c := c.(type) { + case proc.Breakpoint: + gt, err := p.osThreadToGoroutine(t); + if err != nil { + return nil, err; + } + events[i] = &Breakpoint{commonEvent{p, gt}, t, proc.Word(c)}; + i++; + case proc.Signal: + // TODO(austin) + } + } + } + + return events, nil; +} + +// postEvent appends an event to the posted queue. These events will +// be processed before any currently pending events. +func (p *Process) postEvent(ev Event) { + n := len(p.posted); + m := n*2; + if m == 0 { + m = 4; + } + posted := make([]Event, n+1, m); + for i, p := range p.posted { + posted[i] = p; + } + posted[n] = ev; + p.posted = posted; +} + +// processEvents processes events in the event queue until no events +// remain, a handler returns EAStop, or a handler returns an error. +// It returns either EAStop or EAContinue and possibly an error. +func (p *Process) processEvents() (EventAction, os.Error) { + var ev Event; + for len(p.posted) > 0 { + ev, p.posted = p.posted[0], p.posted[1:len(p.posted)]; + action, err := p.processEvent(ev); + if action == EAStop { + return action, err; + } + } + + for len(p.pending) > 0 { + ev, p.pending = p.pending[0], p.pending[1:len(p.pending)]; + action, err := p.processEvent(ev); + if action == EAStop { + return action, err; + } + } + + return EAContinue, nil; +} + +// processEvent processes a single event, without manipulating the +// event queues. It returns either EAStop or EAContinue and possibly +// an error. +func (p *Process) processEvent(ev Event) (EventAction, os.Error) { + p.event = ev; + + var action EventAction; + var err os.Error; + switch ev := p.event.(type) { + case *Breakpoint: + hook, ok := p.breakpointHooks[ev.pc]; + if !ok { + break; + } + p.curGoroutine = ev.Goroutine(); + action, err = hook.handle(ev); + + case *GoroutineCreate: + p.curGoroutine = ev.Goroutine(); + action, err = p.goroutineCreateHook.handle(ev); + + case *GoroutineExit: + action, err = p.goroutineExitHook.handle(ev); + + default: + log.Crashf("Unknown event type %T in queue", p.event); + } + + if err != nil { + return EAStop, err; + } else if action == EAStop { + return EAStop, nil; + } + return EAContinue, nil; +} + +// Event returns the last event that caused the process to stop. This +// may return nil if the process has never been stopped by an event. +// +// TODO(austin) Return nil if the user calls p.Stop()? +func (p *Process) Event() Event { + return p.event; +} + +/* + * Process control + */ + +// TODO(austin) Cont, WaitStop, and Stop. Need to figure out how +// event handling works with these. Originally I did it only in +// WaitStop, but if you Cont and there are pending events, then you +// have to not actually continue and wait until a WaitStop to process +// them, even if the event handlers will tell you to continue. We +// could handle them in both Cont and WaitStop to avoid this problem, +// but it's still weird if an event happens after the Cont and before +// the WaitStop that the handlers say to continue from. Or we could +// handle them on a separate thread. Then obviously you get weird +// asynchrony things, like prints while the user it typing a command, +// but that's not necessarily a bad thing. + +// ContWait resumes process execution and waits for an event to occur +// that stops the process. +func (p *Process) ContWait() os.Error { + for { + a, err := p.processEvents(); + if err != nil { + return err; + } else if a == EAStop { + break; + } + err = p.proc.Continue(); + if err != nil { + return err; + } + err = p.proc.WaitStop(); + if err != nil { + return err; + } + for _, g := range p.goroutines { + g.resetFrame(); + } + p.pending, err = p.causesToEvents(); + if err != nil { + return err; + } + } + return nil; +} + +// Out selects the caller frame of the current frame. +func (p *Process) Out() os.Error { + if p.curGoroutine == nil { + return NoCurrentGoroutine{}; + } + return p.curGoroutine.Out(); +} + +// In selects the frame called by the current frame. +func (p *Process) In() os.Error { + if p.curGoroutine == nil { + return NoCurrentGoroutine{}; + } + return p.curGoroutine.In(); +} diff --git a/src/pkg/exp/ogle/rruntime.go b/src/pkg/exp/ogle/rruntime.go new file mode 100644 index 000000000..2af636926 --- /dev/null +++ b/src/pkg/exp/ogle/rruntime.go @@ -0,0 +1,271 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "exp/eval"; + "reflect"; +) + +// This file contains remote runtime definitions. Using reflection, +// we convert all of these to interpreter types and layout their +// remote representations using the architecture rules. +// +// We could get most of these definitions from our own runtime +// package; however, some of them differ in convenient ways, some of +// them are not defined or exported by the runtime, and having our own +// definitions makes it easy to support multiple remote runtime +// versions. This may turn out to be overkill. +// +// All of these structures are prefixed with rt1 to indicate the +// runtime version and to mark them as types used only as templates +// for remote types. + +/* + * Runtime data headers + * + * See $GOROOT/src/pkg/runtime/runtime.h + */ + +type rt1String struct { + str uintptr; + len int; +} + +type rt1Slice struct { + array uintptr; + len int; + cap int; +} + +type rt1Eface struct { + typ uintptr; + ptr uintptr; +} + +/* + * Runtime type structures + * + * See $GOROOT/src/pkg/runtime/type.h and $GOROOT/src/pkg/runtime/type.go + */ + +type rt1UncommonType struct { + name *string; + pkgPath *string; + //methods []method; +} + +type rt1CommonType struct { + size uintptr; + hash uint32; + alg, align, fieldAlign uint8; + string *string; + uncommonType *rt1UncommonType; +} + +type rt1Type struct { + // While Type is technically an Eface, treating the + // discriminator as an opaque pointer and taking advantage of + // the commonType prologue on all Type's makes type parsing + // much simpler. + typ uintptr; + ptr *rt1CommonType; +} + +type rt1StructField struct { + name *string; + pkgPath *string; + typ *rt1Type; + tag *string; + offset uintptr; +} + +type rt1StructType struct { + rt1CommonType; + fields []rt1StructField; +} + +type rt1PtrType struct { + rt1CommonType; + elem *rt1Type; +} + +type rt1SliceType struct { + rt1CommonType; + elem *rt1Type; +} + +type rt1ArrayType struct { + rt1CommonType; + elem *rt1Type; + len uintptr; +} + +/* + * Runtime scheduler structures + * + * See $GOROOT/src/pkg/runtime/runtime.h + */ + +// Fields beginning with _ are only for padding + +type rt1Stktop struct { + stackguard uintptr; + stackbase *rt1Stktop; + gobuf rt1Gobuf; + _args uint32; + _fp uintptr; +} + +type rt1Gobuf struct { + sp uintptr; + pc uintptr; + g *rt1G; + r0 uintptr; +} + +type rt1G struct { + _stackguard uintptr; + stackbase *rt1Stktop; + _defer uintptr; + sched rt1Gobuf; + _stack0 uintptr; + _entry uintptr; + alllink *rt1G; + _param uintptr; + status int16; + // Incomplete +} + +var rt1GStatus = runtimeGStatus{ + Gidle: 0, + Grunnable: 1, + Grunning: 2, + Gsyscall: 3, + Gwaiting: 4, + Gmoribund: 5, + Gdead: 6, +}; + +// runtimeIndexes stores the indexes of fields in the runtime +// structures. It is filled in using reflection, so the name of the +// fields must match the names of the remoteType's in runtimeValues +// exactly and the names of the index fields must be the capitalized +// version of the names of the fields in the runtime structures above. +type runtimeIndexes struct { + String struct { + Str, Len int; + }; + Slice struct { + Array, Len, Cap int; + }; + Eface struct { + Typ, Ptr int; + }; + + UncommonType struct { + Name, PkgPath int; + }; + CommonType struct { + Size, Hash, Alg, Align, FieldAlign, String, UncommonType int; + }; + Type struct { + Typ, Ptr int; + }; + StructField struct { + Name, PkgPath, Typ, Tag, Offset int; + }; + StructType struct { + Fields int; + }; + PtrType struct { + Elem int; + }; + SliceType struct { + Elem int; + }; + ArrayType struct { + Elem, Len int; + }; + + Stktop struct { + Stackguard, Stackbase, Gobuf int; + }; + Gobuf struct { + Sp, Pc, G int; + }; + G struct { + Stackbase, Sched, Status, Alllink int; + }; +} + +// Values of G status codes +type runtimeGStatus struct { + Gidle, Grunnable, Grunning, Gsyscall, Gwaiting, Gmoribund, Gdead int64; +} + +// runtimeValues stores the types and values that correspond to those +// in the remote runtime package. +type runtimeValues struct { + // Runtime data headers + String, Slice, Eface *remoteType; + // Runtime type structures + Type, CommonType, UncommonType, StructField, StructType, PtrType, + ArrayType, SliceType *remoteType; + // Runtime scheduler structures + Stktop, Gobuf, G *remoteType; + // Addresses of *runtime.XType types. These are the + // discriminators on the runtime.Type interface. We use local + // reflection to fill these in from the remote symbol table, + // so the names must match the runtime names. + PBoolType, + PUint8Type, PUint16Type, PUint32Type, PUint64Type, PUintType, PUintptrType, + PInt8Type, PInt16Type, PInt32Type, PInt64Type, PIntType, + PFloat32Type, PFloat64Type, PFloatType, + PArrayType, PStringType, PStructType, PPtrType, PFuncType, + PInterfaceType, PSliceType, PMapType, PChanType, + PDotDotDotType, PUnsafePointerType proc.Word; + // G status values + runtimeGStatus; +} + +// fillRuntimeIndexes fills a runtimeIndexes structure will the field +// indexes gathered from the remoteTypes recorded in a runtimeValues +// structure. +func fillRuntimeIndexes(runtime *runtimeValues, out *runtimeIndexes) { + outv := reflect.Indirect(reflect.NewValue(out)).(*reflect.StructValue); + outt := outv.Type().(*reflect.StructType); + runtimev := reflect.Indirect(reflect.NewValue(runtime)).(*reflect.StructValue); + + // out contains fields corresponding to each runtime type + for i := 0; i < outt.NumField(); i++ { + // Find the interpreter type for this runtime type + name := outt.Field(i).Name; + et := runtimev.FieldByName(name).Interface().(*remoteType).Type.(*eval.StructType); + + // Get the field indexes of the interpreter struct type + indexes := make(map[string] int, len(et.Elems)); + for j, f := range et.Elems { + if f.Anonymous { + continue; + } + name := f.Name; + if name[0] >= 'a' && name[0] <= 'z' { + name = string(name[0] + 'A' - 'a') + name[1:len(name)]; + } + indexes[name] = j; + } + + // Fill this field of out + outStructv := outv.Field(i).(*reflect.StructValue); + outStructt := outStructv.Type().(*reflect.StructType); + for j := 0; j < outStructt.NumField(); j++ { + f := outStructv.Field(j).(*reflect.IntValue); + name := outStructt.Field(j).Name; + f.Set(indexes[name]); + } + } +} diff --git a/src/pkg/exp/ogle/rtype.go b/src/pkg/exp/ogle/rtype.go new file mode 100644 index 000000000..f70b918c0 --- /dev/null +++ b/src/pkg/exp/ogle/rtype.go @@ -0,0 +1,307 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "exp/eval"; + "fmt"; + "log"; +) + +const debugParseRemoteType = false + +// A remoteType is the local representation of a type in a remote process. +type remoteType struct { + eval.Type; + // The size of values of this type in bytes. + size int; + // The field alignment of this type. Only used for + // manually-constructed types. + fieldAlign int; + // The maker function to turn a remote address of a value of + // this type into an interpreter Value. + mk maker; +} + +var manualTypes = make(map[Arch] map[eval.Type] *remoteType) + +// newManualType constructs a remote type from an interpreter Type +// using the size and alignment properties of the given architecture. +// Most types are parsed directly out of the remote process, but to do +// so we need to layout the structures that describe those types ourselves. +func newManualType(t eval.Type, arch Arch) *remoteType { + if nt, ok := t.(*eval.NamedType); ok { + t = nt.Def; + } + + // Get the type map for this architecture + typeMap, _ := manualTypes[arch]; + if typeMap == nil { + typeMap = make(map[eval.Type] *remoteType); + manualTypes[arch] = typeMap; + + // Construct basic types for this architecture + basicType := func(t eval.Type, mk maker, size int, fieldAlign int) { + t = t.(*eval.NamedType).Def; + if fieldAlign == 0 { + fieldAlign = size; + } + typeMap[t] = &remoteType{t, size, fieldAlign, mk}; + }; + basicType(eval.Uint8Type, mkUint8, 1, 0); + basicType(eval.Uint32Type, mkUint32, 4, 0); + basicType(eval.UintptrType, mkUintptr, arch.PtrSize(), 0); + basicType(eval.Int16Type, mkInt16, 2, 0); + basicType(eval.Int32Type, mkInt32, 4, 0); + basicType(eval.IntType, mkInt, arch.IntSize(), 0); + basicType(eval.StringType, mkString, arch.PtrSize() + arch.IntSize(), arch.PtrSize()); + } + + if rt, ok := typeMap[t]; ok { + return rt; + } + + var rt *remoteType; + switch t := t.(type) { + case *eval.PtrType: + var elem *remoteType; + mk := func(r remote) eval.Value { + return remotePtr{r, elem}; + }; + rt = &remoteType{t, arch.PtrSize(), arch.PtrSize(), mk}; + // Construct the element type after registering the + // type to break cycles. + typeMap[eval.Type(t)] = rt; + elem = newManualType(t.Elem, arch); + + case *eval.ArrayType: + elem := newManualType(t.Elem, arch); + mk := func(r remote) eval.Value { + return remoteArray{r, t.Len, elem}; + }; + rt = &remoteType{t, elem.size*int(t.Len), elem.fieldAlign, mk}; + + case *eval.SliceType: + elem := newManualType(t.Elem, arch); + mk := func(r remote) eval.Value { + return remoteSlice{r, elem}; + }; + rt = &remoteType{t, arch.PtrSize() + 2*arch.IntSize(), arch.PtrSize(), mk}; + + case *eval.StructType: + layout := make([]remoteStructField, len(t.Elems)); + offset := 0; + fieldAlign := 0; + for i, f := range t.Elems { + elem := newManualType(f.Type, arch); + if fieldAlign == 0 { + fieldAlign = elem.fieldAlign; + } + offset = arch.Align(offset, elem.fieldAlign); + layout[i].offset = offset; + layout[i].fieldType = elem; + offset += elem.size; + } + mk := func(r remote) eval.Value { + return remoteStruct{r, layout}; + }; + rt = &remoteType{t, offset, fieldAlign, mk}; + + default: + log.Crashf("cannot manually construct type %T", t); + } + + typeMap[t] = rt; + return rt; +} + +var prtIndent = ""; + +// parseRemoteType parses a Type structure in a remote process to +// construct the corresponding interpreter type and remote type. +func parseRemoteType(a aborter, rs remoteStruct) *remoteType { + addr := rs.addr().base; + p := rs.addr().p; + + // We deal with circular types by discovering cycles at + // NamedTypes. If a type cycles back to something other than + // a named type, we're guaranteed that there will be a named + // type somewhere in that cycle. Thus, we continue down, + // re-parsing types until we reach the named type in the + // cycle. In order to still create one remoteType per remote + // type, we insert an empty remoteType in the type map the + // first time we encounter the type and re-use that structure + // the second time we encounter it. + + rt, ok := p.types[addr]; + if ok && rt.Type != nil { + return rt; + } else if !ok { + rt = &remoteType{}; + p.types[addr] = rt; + } + + if debugParseRemoteType { + sym := p.syms.SymByAddr(uint64(addr)); + name := "<unknown>"; + if sym != nil { + name = sym.Name; + } + log.Stderrf("%sParsing type at %#x (%s)", prtIndent, addr, name); + prtIndent += " "; + defer func() { prtIndent = prtIndent[0:len(prtIndent)-1] }(); + } + + // Get Type header + itype := proc.Word(rs.field(p.f.Type.Typ).(remoteUint).aGet(a)); + typ := rs.field(p.f.Type.Ptr).(remotePtr).aGet(a).(remoteStruct); + + // Is this a named type? + var nt *eval.NamedType; + uncommon := typ.field(p.f.CommonType.UncommonType).(remotePtr).aGet(a); + if uncommon != nil { + name := uncommon.(remoteStruct).field(p.f.UncommonType.Name).(remotePtr).aGet(a); + if name != nil { + // TODO(austin) Declare type in appropriate remote package + nt = eval.NewNamedType(name.(remoteString).aGet(a)); + rt.Type = nt; + } + } + + // Create type + var t eval.Type; + var mk maker; + switch itype { + case p.runtime.PBoolType: + t = eval.BoolType; + mk = mkBool; + case p.runtime.PUint8Type: + t = eval.Uint8Type; + mk = mkUint8; + case p.runtime.PUint16Type: + t = eval.Uint16Type; + mk = mkUint16; + case p.runtime.PUint32Type: + t = eval.Uint32Type; + mk = mkUint32; + case p.runtime.PUint64Type: + t = eval.Uint64Type; + mk = mkUint64; + case p.runtime.PUintType: + t = eval.UintType; + mk = mkUint; + case p.runtime.PUintptrType: + t = eval.UintptrType; + mk = mkUintptr; + case p.runtime.PInt8Type: + t = eval.Int8Type; + mk = mkInt8; + case p.runtime.PInt16Type: + t = eval.Int16Type; + mk = mkInt16; + case p.runtime.PInt32Type: + t = eval.Int32Type; + mk = mkInt32; + case p.runtime.PInt64Type: + t = eval.Int64Type; + mk = mkInt64; + case p.runtime.PIntType: + t = eval.IntType; + mk = mkInt; + case p.runtime.PFloat32Type: + t = eval.Float32Type; + mk = mkFloat32; + case p.runtime.PFloat64Type: + t = eval.Float64Type; + mk = mkFloat64; + case p.runtime.PFloatType: + t = eval.FloatType; + mk = mkFloat; + case p.runtime.PStringType: + t = eval.StringType; + mk = mkString; + + case p.runtime.PArrayType: + // Cast to an ArrayType + typ := p.runtime.ArrayType.mk(typ.addr()).(remoteStruct); + len := int64(typ.field(p.f.ArrayType.Len).(remoteUint).aGet(a)); + elem := parseRemoteType(a, typ.field(p.f.ArrayType.Elem).(remotePtr).aGet(a).(remoteStruct)); + t = eval.NewArrayType(len, elem.Type); + mk = func(r remote) eval.Value { + return remoteArray{r, len, elem}; + }; + + case p.runtime.PStructType: + // Cast to a StructType + typ := p.runtime.StructType.mk(typ.addr()).(remoteStruct); + fs := typ.field(p.f.StructType.Fields).(remoteSlice).aGet(a); + + fields := make([]eval.StructField, fs.Len); + layout := make([]remoteStructField, fs.Len); + for i := range fields { + f := fs.Base.(remoteArray).elem(int64(i)).(remoteStruct); + elemrs := f.field(p.f.StructField.Typ).(remotePtr).aGet(a).(remoteStruct); + elem := parseRemoteType(a, elemrs); + fields[i].Type = elem.Type; + name := f.field(p.f.StructField.Name).(remotePtr).aGet(a); + if name == nil { + fields[i].Anonymous = true; + } else { + fields[i].Name = name.(remoteString).aGet(a); + } + layout[i].offset = int(f.field(p.f.StructField.Offset).(remoteUint).aGet(a)); + layout[i].fieldType = elem; + } + + t = eval.NewStructType(fields); + mk = func(r remote) eval.Value { + return remoteStruct{r, layout}; + }; + + case p.runtime.PPtrType: + // Cast to a PtrType + typ := p.runtime.PtrType.mk(typ.addr()).(remoteStruct); + elem := parseRemoteType(a, typ.field(p.f.PtrType.Elem).(remotePtr).aGet(a).(remoteStruct)); + t = eval.NewPtrType(elem.Type); + mk = func(r remote) eval.Value { + return remotePtr{r, elem}; + }; + + case p.runtime.PSliceType: + // Cast to a SliceType + typ := p.runtime.SliceType.mk(typ.addr()).(remoteStruct); + elem := parseRemoteType(a, typ.field(p.f.SliceType.Elem).(remotePtr).aGet(a).(remoteStruct)); + t = eval.NewSliceType(elem.Type); + mk = func(r remote) eval.Value { + return remoteSlice{r, elem}; + }; + + case p.runtime.PMapType, p.runtime.PChanType, p.runtime.PFuncType, p.runtime.PInterfaceType, p.runtime.PUnsafePointerType, p.runtime.PDotDotDotType: + // TODO(austin) + t = eval.UintptrType; + mk = mkUintptr; + + default: + sym := p.syms.SymByAddr(uint64(itype)); + name := "<unknown symbol>"; + if sym != nil { + name = sym.Name; + } + err := fmt.Sprintf("runtime type at %#x has unexpected type %#x (%s)", addr, itype, name); + a.Abort(FormatError(err)); + } + + // Fill in the remote type + if nt != nil { + nt.Complete(t); + } else { + rt.Type = t; + } + rt.size = int(typ.field(p.f.CommonType.Size).(remoteUint).aGet(a)); + rt.mk = mk; + + return rt; +} diff --git a/src/pkg/exp/ogle/rvalue.go b/src/pkg/exp/ogle/rvalue.go new file mode 100644 index 000000000..9077e238b --- /dev/null +++ b/src/pkg/exp/ogle/rvalue.go @@ -0,0 +1,579 @@ +// Copyright 2009 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 ogle + +import ( + "debug/proc"; + "exp/eval"; + "fmt"; +) + +// A RemoteMismatchError occurs when an operation that requires two +// identical remote processes is given different process. For +// example, this occurs when trying to set a pointer in one process to +// point to something in another process. +type RemoteMismatchError string + +func (e RemoteMismatchError) String() string { + return string(e); +} + +// A ReadOnlyError occurs when attempting to set or assign to a +// read-only value. +type ReadOnlyError string + +func (e ReadOnlyError) String() string { + return string(e); +} + +// A maker is a function that converts a remote address into an +// interpreter Value. +type maker func(remote) eval.Value + +type remoteValue interface { + addr() remote; +} + +// remote represents an address in a remote process. +type remote struct { + base proc.Word; + p *Process; +} + +func (v remote) Get(a aborter, size int) uint64 { + // TODO(austin) This variable might temporarily be in a + // register. We could trace the assembly back from the + // current PC, looking for the beginning of the function or a + // call (both of which guarantee that the variable is in + // memory), or an instruction that loads the variable into a + // register. + // + // TODO(austin) If this is a local variable, it might not be + // live at this PC. In fact, because the compiler reuses + // slots, there might even be a different local variable at + // this location right now. A simple solution to both + // problems is to include the range of PC's over which a local + // variable is live in the symbol table. + // + // TODO(austin) We need to prevent the remote garbage + // collector from collecting objects out from under us. + var arr [8]byte; + buf := arr[0:size]; + _, err := v.p.Peek(v.base, buf); + if err != nil { + a.Abort(err); + } + return uint64(v.p.ToWord(buf)); +} + +func (v remote) Set(a aborter, size int, x uint64) { + var arr [8]byte; + buf := arr[0:size]; + v.p.FromWord(proc.Word(x), buf); + _, err := v.p.Poke(v.base, buf); + if err != nil { + a.Abort(err); + } +} + +func (v remote) plus(x proc.Word) remote { + return remote{v.base + x, v.p}; +} + +func tryRVString(f func(a aborter) string) string { + var s string; + err := try(func(a aborter) { s = f(a) }); + if err != nil { + return fmt.Sprintf("<error: %v>", err); + } + return s; +} + +/* + * Bool + */ + +type remoteBool struct { + r remote; +} + +func (v remoteBool) String() string { + return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) }); +} + +func (v remoteBool) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.BoolValue).Get(t)); +} + +func (v remoteBool) Get(t *eval.Thread) bool { + return v.aGet(t); +} + +func (v remoteBool) aGet(a aborter) bool { + return v.r.Get(a, 1) != 0; +} + +func (v remoteBool) Set(t *eval.Thread, x bool) { + v.aSet(t, x); +} + +func (v remoteBool) aSet(a aborter, x bool) { + if x { + v.r.Set(a, 1, 1); + } else { + v.r.Set(a, 1, 0); + } +} + +func (v remoteBool) addr() remote { + return v.r; +} + +func mkBool(r remote) eval.Value { + return remoteBool{r}; +} + +/* + * Uint + */ + +type remoteUint struct { + r remote; + size int; +} + +func (v remoteUint) String() string { + return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) }); +} + +func (v remoteUint) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.UintValue).Get(t)); +} + +func (v remoteUint) Get(t *eval.Thread) uint64 { + return v.aGet(t); +} + +func (v remoteUint) aGet(a aborter) uint64 { + return v.r.Get(a, v.size); +} + +func (v remoteUint) Set(t *eval.Thread, x uint64) { + v.aSet(t, x); +} + +func (v remoteUint) aSet(a aborter, x uint64) { + v.r.Set(a, v.size, x); +} + +func (v remoteUint) addr() remote { + return v.r; +} + +func mkUint8(r remote) eval.Value { + return remoteUint{r, 1}; +} + +func mkUint16(r remote) eval.Value { + return remoteUint{r, 2}; +} + +func mkUint32(r remote) eval.Value { + return remoteUint{r, 4}; +} + +func mkUint64(r remote) eval.Value { + return remoteUint{r, 8}; +} + +func mkUint(r remote) eval.Value { + return remoteUint{r, r.p.IntSize()}; +} + +func mkUintptr(r remote) eval.Value { + return remoteUint{r, r.p.PtrSize()}; +} + +/* + * Int + */ + +type remoteInt struct { + r remote; + size int; +} + +func (v remoteInt) String() string { + return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) }); +} + +func (v remoteInt) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.IntValue).Get(t)); +} + +func (v remoteInt) Get(t *eval.Thread) int64 { + return v.aGet(t); +} + +func (v remoteInt) aGet(a aborter) int64 { + return int64(v.r.Get(a, v.size)); +} + +func (v remoteInt) Set(t *eval.Thread, x int64) { + v.aSet(t, x); +} + +func (v remoteInt) aSet(a aborter, x int64) { + v.r.Set(a, v.size, uint64(x)); +} + +func (v remoteInt) addr() remote { + return v.r; +} + +func mkInt8(r remote) eval.Value { + return remoteInt{r, 1}; +} + +func mkInt16(r remote) eval.Value { + return remoteInt{r, 2}; +} + +func mkInt32(r remote) eval.Value { + return remoteInt{r, 4}; +} + +func mkInt64(r remote) eval.Value { + return remoteInt{r, 8}; +} + +func mkInt(r remote) eval.Value { + return remoteInt{r, r.p.IntSize()}; +} + +/* + * Float + */ + +type remoteFloat struct { + r remote; + size int; +} + +func (v remoteFloat) String() string { + return tryRVString(func(a aborter) string { return fmt.Sprintf("%v", v.aGet(a)) }); +} + +func (v remoteFloat) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.FloatValue).Get(t)); +} + +func (v remoteFloat) Get(t *eval.Thread) float64 { + return v.aGet(t); +} + +func (v remoteFloat) aGet(a aborter) float64 { + bits := v.r.Get(a, v.size); + switch v.size { + case 4: + return float64(v.r.p.ToFloat32(uint32(bits))); + case 8: + return v.r.p.ToFloat64(bits); + } + panic("Unexpected float size ", v.size); +} + +func (v remoteFloat) Set(t *eval.Thread, x float64) { + v.aSet(t, x); +} + +func (v remoteFloat) aSet(a aborter, x float64) { + var bits uint64; + switch v.size{ + case 4: + bits = uint64(v.r.p.FromFloat32(float32(x))); + case 8: + bits = v.r.p.FromFloat64(x); + default: + panic("Unexpected float size ", v.size); + } + v.r.Set(a, v.size, bits); +} + +func (v remoteFloat) addr() remote { + return v.r; +} + +func mkFloat32(r remote) eval.Value { + return remoteFloat{r, 4}; +} + +func mkFloat64(r remote) eval.Value { + return remoteFloat{r, 8}; +} + +func mkFloat(r remote) eval.Value { + return remoteFloat{r, r.p.FloatSize()}; +} + +/* + * String + */ + +type remoteString struct { + r remote; +} + +func (v remoteString) String() string { + return tryRVString(func(a aborter) string { return v.aGet(a) }); +} + +func (v remoteString) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.StringValue).Get(t)); +} + +func (v remoteString) Get(t *eval.Thread) string { + return v.aGet(t); +} + +func (v remoteString) aGet(a aborter) string { + rs := v.r.p.runtime.String.mk(v.r).(remoteStruct); + str := proc.Word(rs.field(v.r.p.f.String.Str).(remoteUint).aGet(a)); + len := rs.field(v.r.p.f.String.Len).(remoteInt).aGet(a); + + bytes := make([]uint8, len); + _, err := v.r.p.Peek(str, bytes); + if err != nil { + a.Abort(err); + } + return string(bytes); +} + +func (v remoteString) Set(t *eval.Thread, x string) { + v.aSet(t, x); +} + +func (v remoteString) aSet(a aborter, x string) { + // TODO(austin) This isn't generally possible without the + // ability to allocate remote memory. + a.Abort(ReadOnlyError("remote strings cannot be assigned to")); +} + +func mkString(r remote) eval.Value { + return remoteString{r}; +} + +/* + * Array + */ + +type remoteArray struct { + r remote; + len int64; + elemType *remoteType; +} + +func (v remoteArray) String() string { + res := "{"; + for i := int64(0); i < v.len; i++ { + if i > 0 { + res += ", "; + } + res += v.elem(i).String(); + } + return res + "}"; +} + +func (v remoteArray) Assign(t *eval.Thread, o eval.Value) { + // TODO(austin) Could do a bigger memcpy if o is a + // remoteArray in the same Process. + oa := o.(eval.ArrayValue); + for i := int64(0); i < v.len; i++ { + v.Elem(t, i).Assign(t, oa.Elem(t, i)); + } +} + +func (v remoteArray) Get(t *eval.Thread) eval.ArrayValue { + return v; +} + +func (v remoteArray) Elem(t *eval.Thread, i int64) eval.Value { + return v.elem(i); +} + +func (v remoteArray) elem(i int64) eval.Value { + return v.elemType.mk(v.r.plus(proc.Word(int64(v.elemType.size) * i))); +} + +func (v remoteArray) Sub(i int64, len int64) eval.ArrayValue { + return remoteArray{v.r.plus(proc.Word(int64(v.elemType.size) * i)), len, v.elemType}; +} + +/* + * Struct + */ + +type remoteStruct struct { + r remote; + layout []remoteStructField; +} + +type remoteStructField struct { + offset int; + fieldType *remoteType; +} + +func (v remoteStruct) String() string { + res := "{"; + for i := range v.layout { + if i > 0 { + res += ", "; + } + res += v.field(i).String(); + } + return res + "}"; +} + +func (v remoteStruct) Assign(t *eval.Thread, o eval.Value) { + // TODO(austin) Could do a bigger memcpy. + oa := o.(eval.StructValue); + l := len(v.layout); + for i := 0; i < l; i++ { + v.Field(t, i).Assign(t, oa.Field(t, i)); + } +} + +func (v remoteStruct) Get(t *eval.Thread) eval.StructValue { + return v; +} + +func (v remoteStruct) Field(t *eval.Thread, i int) eval.Value { + return v.field(i); +} + +func (v remoteStruct) field(i int) eval.Value { + f := &v.layout[i]; + return f.fieldType.mk(v.r.plus(proc.Word(f.offset))); +} + +func (v remoteStruct) addr() remote { + return v.r; +} + +/* + * Pointer + */ + +// TODO(austin) Comparing two remote pointers for equality in the +// interpreter will crash it because the Value's returned from +// remotePtr.Get() will be structs. + +type remotePtr struct { + r remote; + elemType *remoteType; +} + +func (v remotePtr) String() string { + return tryRVString(func(a aborter) string { + e := v.aGet(a); + if e == nil { + return "<nil>"; + } + return "&" + e.String(); + }); +} + +func (v remotePtr) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.PtrValue).Get(t)); +} + +func (v remotePtr) Get(t *eval.Thread) eval.Value { + return v.aGet(t); +} + +func (v remotePtr) aGet(a aborter) eval.Value { + addr := proc.Word(v.r.Get(a, v.r.p.PtrSize())); + if addr == 0 { + return nil; + } + return v.elemType.mk(remote{addr, v.r.p}); +} + +func (v remotePtr) Set(t *eval.Thread, x eval.Value) { + v.aSet(t, x); +} + +func (v remotePtr) aSet(a aborter, x eval.Value) { + if x == nil { + v.r.Set(a, v.r.p.PtrSize(), 0); + return; + } + xr, ok := x.(remoteValue); + if !ok || v.r.p != xr.addr().p { + a.Abort(RemoteMismatchError("remote pointer must point within the same process")); + } + v.r.Set(a, v.r.p.PtrSize(), uint64(xr.addr().base)); +} + +func (v remotePtr) addr() remote { + return v.r; +} + +/* + * Slice + */ + +type remoteSlice struct { + r remote; + elemType *remoteType; +} + +func (v remoteSlice) String() string { + return tryRVString(func(a aborter) string { + b := v.aGet(a).Base; + if b == nil { + return "<nil>"; + } + return b.String(); + }); +} + +func (v remoteSlice) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.SliceValue).Get(t)); +} + +func (v remoteSlice) Get(t *eval.Thread) eval.Slice { + return v.aGet(t); +} + +func (v remoteSlice) aGet(a aborter) eval.Slice { + rs := v.r.p.runtime.Slice.mk(v.r).(remoteStruct); + base := proc.Word(rs.field(v.r.p.f.Slice.Array).(remoteUint).aGet(a)); + nel := rs.field(v.r.p.f.Slice.Len).(remoteInt).aGet(a); + cap := rs.field(v.r.p.f.Slice.Cap).(remoteInt).aGet(a); + if base == 0 { + return eval.Slice{nil, nel, cap}; + } + return eval.Slice{remoteArray{remote{base, v.r.p}, nel, v.elemType}, nel, cap}; +} + +func (v remoteSlice) Set(t *eval.Thread, x eval.Slice) { + v.aSet(t, x); +} + +func (v remoteSlice) aSet(a aborter, x eval.Slice) { + rs := v.r.p.runtime.Slice.mk(v.r).(remoteStruct); + if x.Base == nil { + rs.field(v.r.p.f.Slice.Array).(remoteUint).aSet(a, 0); + } else { + ar, ok := x.Base.(remoteArray); + if !ok || v.r.p != ar.r.p { + a.Abort(RemoteMismatchError("remote slice must point within the same process")); + } + rs.field(v.r.p.f.Slice.Array).(remoteUint).aSet(a, uint64(ar.r.base)); + } + rs.field(v.r.p.f.Slice.Len).(remoteInt).aSet(a, x.Len); + rs.field(v.r.p.f.Slice.Cap).(remoteInt).aSet(a, x.Cap); +} diff --git a/src/pkg/exp/ogle/vars.go b/src/pkg/exp/ogle/vars.go new file mode 100644 index 000000000..539b5b345 --- /dev/null +++ b/src/pkg/exp/ogle/vars.go @@ -0,0 +1,276 @@ +// Copyright 2009 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 ogle + +import ( + "debug/gosym"; + "debug/proc"; + "exp/eval"; + "log"; + "os"; +) + +/* + * Remote frame pointers + */ + +// A NotOnStack error occurs when attempting to access a variable in a +// remote frame where that remote frame is not on the current stack. +type NotOnStack struct { + Fn *gosym.Func; + Goroutine *Goroutine; +} + +func (e NotOnStack) String() string { + return "function " + e.Fn.Name + " not on " + e.Goroutine.String() + "'s stack"; +} + +// A remoteFramePtr is an implementation of eval.PtrValue that +// represents a pointer to a function frame in a remote process. When +// accessed, this locates the function on the current goroutine's +// stack and returns a structure containing the local variables of +// that function. +type remoteFramePtr struct { + p *Process; + fn *gosym.Func; + rt *remoteType; +} + +func (v remoteFramePtr) String() string { + // TODO(austin): This could be a really awesome string method + return "<remote frame>"; +} + +func (v remoteFramePtr) Assign(t *eval.Thread, o eval.Value) { + v.Set(t, o.(eval.PtrValue).Get(t)); +} + +func (v remoteFramePtr) Get(t *eval.Thread) eval.Value { + g := v.p.curGoroutine; + if g == nil || g.frame == nil { + t.Abort(NoCurrentGoroutine{}); + } + + for f := g.frame; f != nil; f = f.aOuter(t) { + if f.fn != v.fn { + continue; + } + + // TODO(austin): Register for shootdown with f + return v.rt.mk(remote{f.fp, v.p}); + } + + t.Abort(NotOnStack{v.fn, g}); + panic(); +} + +func (v remoteFramePtr) Set(t *eval.Thread, x eval.Value) { + // Theoretically this could be a static error. If remote + // packages were packages, remote frames could just be defined + // as constants. + t.Abort(ReadOnlyError("remote frames cannot be assigned to")); +} + +/* + * Remote packages + */ + +// TODO(austin): Remote packages are implemented as structs right now, +// which has some weird consequences. You can attempt to assign to a +// remote package. It also produces terrible error messages. +// Ideally, these would actually be packages, but somehow first-class +// so they could be assigned to other names. + +// A remotePackage is an implementation of eval.StructValue that +// represents a package in a remote process. It's essentially a +// regular struct, except it cannot be assigned to. +type remotePackage struct { + defs []eval.Value; +} + +func (v remotePackage) String() string { + return "<remote package>"; +} + +func (v remotePackage) Assign(t *eval.Thread, o eval.Value) { + t.Abort(ReadOnlyError("remote packages cannot be assigned to")); +} + +func (v remotePackage) Get(t *eval.Thread) eval.StructValue { + return v; +} + +func (v remotePackage) Field(t *eval.Thread, i int) eval.Value { + return v.defs[i]; +} + +/* + * Remote variables + */ + +// populateWorld defines constants in the given world for each package +// in this process. These packages are structs that, in turn, contain +// fields for each global and function in that package. +func (p *Process) populateWorld(w *eval.World) os.Error { + type def struct { + t eval.Type; + v eval.Value; + } + packages := make(map[string] map[string] def); + + for _, s := range p.syms.Syms { + if s.ReceiverName() != "" { + // TODO(austin) + continue; + } + + // Package + pkgName := s.PackageName(); + switch pkgName { + case "", "type", "extratype", "string", "go": + // "go" is really "go.string" + continue; + } + pkg, ok := packages[pkgName]; + if !ok { + pkg = make(map[string] def); + packages[pkgName] = pkg; + } + + // Symbol name + name := s.BaseName(); + if _, ok := pkg[name]; ok { + log.Stderrf("Multiple definitions of symbol %s", s.Name); + continue; + } + + // Symbol type + rt, err := p.typeOfSym(&s); + if err != nil { + return err; + } + + // Definition + switch s.Type { + case 'D', 'd', 'B', 'b': + // Global variable + if rt == nil { + continue; + } + pkg[name] = def{rt.Type, rt.mk(remote{proc.Word(s.Value), p})}; + + case 'T', 't', 'L', 'l': + // Function + s := s.Func; + // TODO(austin): Ideally, this would *also* be + // callable. How does that interact with type + // conversion syntax? + rt, err := p.makeFrameType(s); + if err != nil { + return err; + } + pkg[name] = def{eval.NewPtrType(rt.Type), remoteFramePtr{p, s, rt}}; + } + } + + // TODO(austin): Define remote types + + // Define packages + for pkgName, defs := range packages { + fields := make([]eval.StructField, len(defs)); + vals := make([]eval.Value, len(defs)); + i := 0; + for name, def := range defs { + fields[i].Name = name; + fields[i].Type = def.t; + vals[i] = def.v; + i++; + } + pkgType := eval.NewStructType(fields); + pkgVal := remotePackage{vals}; + + err := w.DefineConst(pkgName, pkgType, pkgVal); + if err != nil { + log.Stderrf("while defining package %s: %v", pkgName, err); + } + } + + return nil; +} + +// typeOfSym returns the type associated with a symbol. If the symbol +// has no type, returns nil. +func (p *Process) typeOfSym(s *gosym.Sym) (*remoteType, os.Error) { + if s.GoType == 0 { + return nil, nil; + } + addr := proc.Word(s.GoType); + var rt *remoteType; + err := try(func(a aborter) { + rt = parseRemoteType(a, p.runtime.Type.mk(remote{addr, p}).(remoteStruct)); + }); + if err != nil { + return nil, err; + } + return rt, nil; +} + +// makeFrameType constructs a struct type for the frame of a function. +// The offsets in this struct type are such that the struct can be +// instantiated at this function's frame pointer. +func (p *Process) makeFrameType(s *gosym.Func) (*remoteType, os.Error) { + n := len(s.Params) + len(s.Locals); + fields := make([]eval.StructField, n); + layout := make([]remoteStructField, n); + i := 0; + + // TODO(austin): There can be multiple locals/parameters with + // the same name. We probably need liveness information to do + // anything about this. Once we have that, perhaps we give + // such fields interface{} type? Or perhaps we disambiguate + // the names with numbers. Disambiguation is annoying for + // things like "i", where there's an obvious right answer. + + for _, param := range s.Params { + rt, err := p.typeOfSym(param); + if err != nil { + return nil, err; + } + if rt == nil { + //fmt.Printf(" (no type)\n"); + continue; + } + // TODO(austin): Why do local variables carry their + // package name? + fields[i].Name = param.BaseName(); + fields[i].Type = rt.Type; + // Parameters have positive offsets from FP + layout[i].offset = int(param.Value); + layout[i].fieldType = rt; + i++; + } + + for _, local := range s.Locals { + rt, err := p.typeOfSym(local); + if err != nil { + return nil, err; + } + if rt == nil { + continue; + } + fields[i].Name = local.BaseName(); + fields[i].Type = rt.Type; + // Locals have negative offsets from FP - PtrSize + layout[i].offset = -int(local.Value) - p.PtrSize(); + layout[i].fieldType = rt; + i++; + } + + fields = fields[0:i]; + layout = layout[0:i]; + t := eval.NewStructType(fields); + mk := func(r remote) eval.Value { return remoteStruct{r, layout} }; + return &remoteType{t, 0, 0, mk}, nil; +} diff --git a/src/run.bash b/src/run.bash index 07f2774a1..619ba9b7d 100755 --- a/src/run.bash +++ b/src/run.bash @@ -26,12 +26,10 @@ maketest() { maketest \ pkg \ - ../usr/austin/eval \ # all of these are subtly different # from what maketest does. - (xcd pkg/sync; make clean; time make @@ -55,7 +53,18 @@ make clean ./test.bash ) || exit $? -(xcd ../usr/austin/ogle +(xcd ../usr/r/rpc +make clean +time make +./chanrun +) || exit $? + +(xcd ../usr/dsymonds/iterable +make clean +time make test +) || exit $? + +(xcd pkg/exp/ogle make clean time make ogle ) || exit $? |