diff --git a/ssa/interp/interp.go b/ssa/interp/interp.go index 0351f616..3c6a4255 100644 --- a/ssa/interp/interp.go +++ b/ssa/interp/interp.go @@ -32,9 +32,6 @@ // // * map iteration is asymptotically inefficient. // -// * the equivalence relation for structs doesn't skip over blank -// fields. -// // * the sizes of the int, uint and uintptr types in the target // program are assumed to be the same as those of the interpreter // itself. @@ -163,7 +160,7 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation { fr.env[instr] = unop(instr, fr.get(instr.X)) case *ssa.BinOp: - fr.env[instr] = binop(instr.Op, fr.get(instr.X), fr.get(instr.Y)) + fr.env[instr] = binop(instr.Op, instr.X.Type(), fr.get(instr.X), fr.get(instr.Y)) case *ssa.Call: fn, args := prepareCall(fr, &instr.Call) diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 26619cfd..e92d26ab 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -184,7 +184,7 @@ func run(t *testing.T, dir, input string) bool { } }() - hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=CFP %s\n", input) + hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) mainInfo := imp.LoadMainPackage(files...) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) @@ -197,7 +197,7 @@ func run(t *testing.T, dir, input string) bool { mainPkg := prog.Package(mainInfo.Pkg) mainPkg.CreateTestMainFunction() // (no-op if main already exists) - hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=C -run --interp=T %s\n", input) + hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 { t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode) return false diff --git a/ssa/interp/map.go b/ssa/interp/map.go index b77bdfe6..5e93e7f7 100644 --- a/ssa/interp/map.go +++ b/ssa/interp/map.go @@ -16,8 +16,8 @@ import ( ) type hashable interface { - hash() int - eq(x interface{}) bool + hash(t types.Type) int + eq(t types.Type, x interface{}) bool } type entry struct { @@ -31,8 +31,9 @@ type entry struct { // tests when walking the linked list. Rehashing is done by the // underlying map. type hashmap struct { - table map[int]*entry - length int // number of entries in map + keyType types.Type + table map[int]*entry + length int // number of entries in map } // makeMap returns an empty initialized map of key type kt, @@ -41,23 +42,23 @@ func makeMap(kt types.Type, reserve int) value { if usesBuiltinMap(kt) { return make(map[value]value, reserve) } - return &hashmap{table: make(map[int]*entry, reserve)} + return &hashmap{keyType: kt, table: make(map[int]*entry, reserve)} } // delete removes the association for key k, if any. func (m *hashmap) delete(k hashable) { if m != nil { - hash := k.hash() + hash := k.hash(m.keyType) head := m.table[hash] if head != nil { - if k.eq(head.key) { + if k.eq(m.keyType, head.key) { m.table[hash] = head.next m.length-- return } prev := head for e := head.next; e != nil; e = e.next { - if k.eq(e.key) { + if k.eq(m.keyType, e.key) { prev.next = e.next m.length-- return @@ -72,9 +73,9 @@ func (m *hashmap) delete(k hashable) { // value(nil) otherwise. func (m *hashmap) lookup(k hashable) value { if m != nil { - hash := k.hash() + hash := k.hash(m.keyType) for e := m.table[hash]; e != nil; e = e.next { - if k.eq(e.key) { + if k.eq(m.keyType, e.key) { return e.value } } @@ -87,10 +88,10 @@ func (m *hashmap) lookup(k hashable) value { // k, the previous key remains in the map and its associated value is // updated. func (m *hashmap) insert(k hashable, v value) { - hash := k.hash() + hash := k.hash(m.keyType) head := m.table[hash] for e := head; e != nil; e = e.next { - if k.eq(e.key) { + if k.eq(m.keyType, e.key) { e.value = v return } diff --git a/ssa/interp/ops.go b/ssa/interp/ops.go index 34661490..055e64df 100644 --- a/ssa/interp/ops.go +++ b/ssa/interp/ops.go @@ -301,7 +301,7 @@ func lookup(instr *ssa.Lookup, x, idx value) value { // numeric datatypes and strings. Both operands must have identical // dynamic type. // -func binop(op token.Token, x, y value) value { +func binop(op token.Token, t types.Type, x, y value) value { switch op { case token.ADD: switch x.(type) { @@ -690,10 +690,10 @@ func binop(op token.Token, x, y value) value { } case token.EQL: - return equals(x, y) + return eqnil(t, x, y) case token.NEQ: - return !equals(x, y) + return !eqnil(t, x, y) case token.GTR: switch x.(type) { @@ -762,6 +762,39 @@ func binop(op token.Token, x, y value) value { panic(fmt.Sprintf("invalid binary op: %T %s %T", x, op, y)) } +// eqnil returns the comparison x == y using the equivalence relation +// appropriate for type t. +// If t is a reference type, at most one of x or y may be a nil value +// of that type. +// +func eqnil(t types.Type, x, y value) bool { + switch t.Underlying().(type) { + case *types.Map, *types.Signature, *types.Slice: + // Since these types don't support comparison, + // one of the operands must be a literal nil. + switch x := x.(type) { + case *hashmap: + return (x != nil) == (y.(*hashmap) != nil) + case map[value]value: + return (x != nil) == (y.(map[value]value) != nil) + case *ssa.Function: + switch y := y.(type) { + case *ssa.Function: + return (x != nil) == (y != nil) + case *closure: + return true + } + case *closure: + return (x != nil) == (y.(*ssa.Function) != nil) + case []value: + return (x != nil) == (y.([]value) != nil) + } + panic(fmt.Sprintf("eqnil(%s): illegal dynamic type: %T", t, x)) + } + + return equals(t, x, y) +} + func unop(instr *ssa.UnOp, x value) value { switch instr.Op { case token.ARROW: // receive diff --git a/ssa/interp/testdata/coverage.go b/ssa/interp/testdata/coverage.go index 682bfb31..00d8b4d3 100644 --- a/ssa/interp/testdata/coverage.go +++ b/ssa/interp/testdata/coverage.go @@ -377,14 +377,15 @@ func init() { // An I->I type-assert fails iff the value is nil. func init() { - defer func() { - r := fmt.Sprint(recover()) - if r != "interface conversion: interface is nil, not main.I" { - panic("I->I type assertion succeeed for nil value") - } - }() - var x I - _ = x.(I) + // TODO(adonovan): temporarily disabled; see comment at bottom of file. + // defer func() { + // r := fmt.Sprint(recover()) + // if r != "interface conversion: interface is nil, not main.I" { + // panic("I->I type assertion succeeed for nil value") + // } + // }() + // var x I + // _ = x.(I) } ////////////////////////////////////////////////////////////////////// @@ -503,3 +504,39 @@ func init() { panic(deferCount) // defer call has not run! } } + +func init() { + // Struct equivalence ignores blank fields. + type s struct{ x, _, z int } + s1 := s{x: 1, z: 3} + s2 := s{x: 1, z: 3} + if s1 != s2 { + panic("not equal") + } +} + +func init() { + // A slice var can be compared to const []T nil. + var i interface{} = []string{"foo"} + var j interface{} = []string(nil) + if i.([]string) == nil { + panic("expected i non-nil") + } + if j.([]string) != nil { + panic("expected j nil") + } + // But two slices cannot be compared, even if one is nil. + defer func() { + r := fmt.Sprint(recover()) + if r != "runtime error: comparing uncomparable type []string" { + panic("want panic from slice comparison, got " + r) + } + }() + _ = i == j // interface comparison recurses on types +} + +// TODO(adonovan): fix: the interpreter doesn't correctly implement +// defer/recover in an init function concatenated from many parts: the +// first recover causes the entire init() to return, not jump to the +// next part. This will be fixed in a follow-up CL. Until then, +// beware: adding new init() functions here will have no effect! diff --git a/ssa/interp/value.go b/ssa/interp/value.go index 654e16d2..038fabc1 100644 --- a/ssa/interp/value.go +++ b/ssa/interp/value.go @@ -21,7 +21,7 @@ package interp // - array --- arrays. // - *value --- pointers. Careful: *value is a distinct type from *array etc. // - *ssa.Function \ -// *ssa.Builtin } --- functions. +// *ssa.Builtin } --- functions. A nil 'func' is always of type *ssa.Function. // *closure / // - tuple --- as returned by Ret, Next, "value,ok" modes, etc. // - iter --- iterators from 'range' over map or string. @@ -39,9 +39,11 @@ import ( "io" "reflect" "strings" + "sync" "unsafe" "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/go/types/typemap" "code.google.com/p/go.tools/ssa" ) @@ -88,13 +90,18 @@ func hashString(s string) int { return int(h) } +var ( + mu sync.Mutex + hasher = typemap.MakeHasher() +) + // hashType returns a hash for t such that // types.IsIdentical(x, y) => hashType(x) == hashType(y). func hashType(t types.Type) int { - // TODO(adonovan): fix: not sound! "IsIdentical" Signatures - // may have different parameter names; use typemap.Hasher when - // available. - return hashString(t.String()) // TODO(gri): provide a better hash + mu.Lock() + h := int(hasher.Hash(t)) + mu.Unlock() + return h } // usesBuiltinMap returns true if the built-in hash function and @@ -118,67 +125,72 @@ func usesBuiltinMap(t types.Type) bool { panic(fmt.Sprintf("invalid map key type: %T", t)) } -func (x array) eq(_y interface{}) bool { +func (x array) eq(t types.Type, _y interface{}) bool { y := _y.(array) + tElt := t.Underlying().(*types.Array).Elem() for i, xi := range x { - if !equals(xi, y[i]) { + if !equals(tElt, xi, y[i]) { return false } } return true } -func (x array) hash() int { +func (x array) hash(t types.Type) int { h := 0 + tElt := t.Underlying().(*types.Array).Elem() for _, xi := range x { - h += hash(xi) + h += hash(tElt, xi) } return h } -func (x structure) eq(_y interface{}) bool { +func (x structure) eq(t types.Type, _y interface{}) bool { y := _y.(structure) - // TODO(adonovan): fix: only non-blank fields should be - // compared. This requires that we have type information - // available from the enclosing == operation or map access; - // the value is not sufficient. - for i, xi := range x { - if !equals(xi, y[i]) { - return false + tStruct := t.Underlying().(*types.Struct) + for i, n := 0, tStruct.NumFields(); i < n; i++ { + if f := tStruct.Field(i); !f.Anonymous() { + if !equals(f.Type(), x[i], y[i]) { + return false + } } } return true } -func (x structure) hash() int { +func (x structure) hash(t types.Type) int { + tStruct := t.Underlying().(*types.Struct) h := 0 - for _, xi := range x { - h += hash(xi) + for i, n := 0, tStruct.NumFields(); i < n; i++ { + if f := tStruct.Field(i); !f.Anonymous() { + h += hash(f.Type(), x[i]) + } } return h } -func (x iface) eq(_y interface{}) bool { +func (x iface) eq(t types.Type, _y interface{}) bool { y := _y.(iface) - return types.IsIdentical(x.t, y.t) && (x.t == nil || equals(x.v, y.v)) + return types.IsIdentical(x.t, y.t) && (x.t == nil || equals(x.t, x.v, y.v)) } -func (x iface) hash() int { - return hashType(x.t)*8581 + hash(x.v) +func (x iface) hash(_ types.Type) int { + return hashType(x.t)*8581 + hash(x.t, x.v) } -func (x rtype) hash() int { +func (x rtype) hash(_ types.Type) int { return hashType(x.t) } -func (x rtype) eq(y interface{}) bool { +func (x rtype) eq(_ types.Type, y interface{}) bool { return types.IsIdentical(x.t, y.(rtype).t) } // equals returns true iff x and y are equal according to Go's -// linguistic equivalence relation. In a well-typed program, the -// types of x and y are guaranteed equal. -func equals(x, y value) bool { +// linguistic equivalence relation for type t. +// In a well-typed program, the dynamic types of x and y are +// guaranteed equal. +func equals(t types.Type, x, y value) bool { switch x := x.(type) { case bool: return x == y.(bool) @@ -219,31 +231,23 @@ func equals(x, y value) bool { case chan value: return x == y.(chan value) case structure: - return x.eq(y) + return x.eq(t, y) case array: - return x.eq(y) + return x.eq(t, y) case iface: - return x.eq(y) + return x.eq(t, y) case rtype: - return x.eq(y) - - // Since the following types don't support comparison, - // these cases are only reachable if one of x or y is - // (literally) nil. - case *hashmap: - return x == y.(*hashmap) - case map[value]value: - return (x != nil) == (y.(map[value]value) != nil) - case *ssa.Function, *closure: - return x == y - case []value: - return (x != nil) == (y.([]value) != nil) + return x.eq(t, y) } - panic(fmt.Sprintf("comparing incomparable type %T", x)) + + // Since map, func and slice don't support comparison, this + // case is only reachable if one of x or y is literally nil + // (handled in eqnil) or via interface{} values. + panic(fmt.Sprintf("runtime error: comparing uncomparable type %s", t)) } // Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y). -func hash(x value) int { +func hash(t types.Type, x value) int { switch x := x.(type) { case bool: if x { @@ -287,13 +291,13 @@ func hash(x value) int { case chan value: return int(uintptr(reflect.ValueOf(x).Pointer())) case structure: - return x.hash() + return x.hash(t) case array: - return x.hash() + return x.hash(t) case iface: - return x.hash() + return x.hash(t) case rtype: - return x.hash() + return x.hash(t) } panic(fmt.Sprintf("%T is unhashable", x)) }