1
0
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:
Alan Donovan 2013-09-16 15:22:19 -04:00
parent 3b5de067a1
commit 5db6491e90
6 changed files with 152 additions and 80 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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!

View File

@ -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))
}