mirror of
https://github.com/golang/go
synced 2024-11-18 16:14:46 -07:00
go.tools/ssa/interp: fixes to equivalence relations.
- This change implements the correct type-based equivalence relation for aggregate types. e.g. comparison of struct types no longer compares the anonymous fields. We do analogous things for hash(). - equals() and eqnil() have been separated: the former panics for uncomparable types, the latter permits comparisons of slice/map/func types against a literal nil and is intended for use only by "static" ssa.BinOp(EQL), not "dynamic" slice comparisons encountered during (e.g.) interface comparisons, which should panic regardless of operand nilness. - we use a (global) typemap.Hasher to compute type hashes; hashing the Type.String() value was not sound. + tests. NB, this change unearthed a bug in defer/recover within init(); it will be fixed in a followup change. R=gri, crawshaw CC=golang-dev https://golang.org/cl/13719043
This commit is contained in:
parent
3b5de067a1
commit
5db6491e90
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
53
ssa/interp/testdata/coverage.go
vendored
53
ssa/interp/testdata/coverage.go
vendored
@ -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!
|
||||
|
@ -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))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user