Commit 077902d1 authored by Matthew Dempsky's avatar Matthew Dempsky

cmd/compile: cleanup algtype code

Add AlgKind enum type to represent AFOO values.

Add IsComparable, IsRegularMemory, IncomparableField helper methods to
codify common higher-level idioms.

Passes toolstash -cmp.

Change-Id: I54c544953997a8ccc72396b3058897edcbbea392
Reviewed-on: https://go-review.googlesource.com/21420
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 58394fd7
...@@ -6,9 +6,13 @@ package gc ...@@ -6,9 +6,13 @@ package gc
import "fmt" import "fmt"
// AlgKind describes the kind of algorithms used for comparing and
// hashing a Type.
type AlgKind int
const ( const (
// These values are known by runtime. // These values are known by runtime.
ANOEQ = iota ANOEQ AlgKind = iota
AMEM0 AMEM0
AMEM8 AMEM8
AMEM16 AMEM16
...@@ -22,11 +26,40 @@ const ( ...@@ -22,11 +26,40 @@ const (
AFLOAT64 AFLOAT64
ACPLX64 ACPLX64
ACPLX128 ACPLX128
AMEM = 100
// Type can be compared/hashed as regular memory.
AMEM AlgKind = 100
// Type needs special comparison/hashing functions.
ASPECIAL AlgKind = -1
) )
func algtype(t *Type) int { // IsComparable reports whether t is a comparable type.
a := algtype1(t, nil) func (t *Type) IsComparable() bool {
a, _ := algtype1(t)
return a != ANOEQ
}
// IsRegularMemory reports whether t can be compared/hashed as regular memory.
func (t *Type) IsRegularMemory() bool {
a, _ := algtype1(t)
return a == AMEM
}
// IncomparableField returns an incomparable Field of struct Type t, if any.
func (t *Type) IncomparableField() *Field {
for _, f := range t.FieldSlice() {
if !f.Type.IsComparable() {
return f
}
}
return nil
}
// algtype is like algtype1, except it returns the fixed-width AMEMxx variants
// instead of the general AMEM kind when possible.
func algtype(t *Type) AlgKind {
a, _ := algtype1(t)
if a == AMEM { if a == AMEM {
switch t.Width { switch t.Width {
case 0: case 0:
...@@ -47,115 +80,105 @@ func algtype(t *Type) int { ...@@ -47,115 +80,105 @@ func algtype(t *Type) int {
return a return a
} }
func algtype1(t *Type, bad **Type) int { // algtype1 returns the AlgKind used for comparing and hashing Type t.
if bad != nil { // If it returns ANOEQ, it also returns the component type of t that
*bad = nil // makes it incomparable.
} func algtype1(t *Type) (AlgKind, *Type) {
if t.Broke { if t.Broke {
return AMEM return AMEM, nil
} }
if t.Noalg { if t.Noalg {
return ANOEQ return ANOEQ, t
} }
switch t.Etype { switch t.Etype {
case TANY, TFORW: case TANY, TFORW:
// will be defined later. // will be defined later.
*bad = t return ANOEQ, t
return -1
case TINT8, TUINT8, TINT16, TUINT16, case TINT8, TUINT8, TINT16, TUINT16,
TINT32, TUINT32, TINT64, TUINT64, TINT32, TUINT32, TINT64, TUINT64,
TINT, TUINT, TUINTPTR, TINT, TUINT, TUINTPTR,
TBOOL, TPTR32, TPTR64, TBOOL, TPTR32, TPTR64,
TCHAN, TUNSAFEPTR: TCHAN, TUNSAFEPTR:
return AMEM return AMEM, nil
case TFUNC, TMAP: case TFUNC, TMAP:
if bad != nil { return ANOEQ, t
*bad = t
}
return ANOEQ
case TFLOAT32: case TFLOAT32:
return AFLOAT32 return AFLOAT32, nil
case TFLOAT64: case TFLOAT64:
return AFLOAT64 return AFLOAT64, nil
case TCOMPLEX64: case TCOMPLEX64:
return ACPLX64 return ACPLX64, nil
case TCOMPLEX128: case TCOMPLEX128:
return ACPLX128 return ACPLX128, nil
case TSTRING: case TSTRING:
return ASTRING return ASTRING, nil
case TINTER: case TINTER:
if isnilinter(t) { if isnilinter(t) {
return ANILINTER return ANILINTER, nil
} }
return AINTER return AINTER, nil
case TARRAY: case TARRAY:
if t.IsSlice() { if t.IsSlice() {
if bad != nil { return ANOEQ, t
*bad = t
}
return ANOEQ
} }
a := algtype1(t.Elem(), bad) a, bad := algtype1(t.Elem())
switch a { switch a {
case AMEM: case AMEM:
return AMEM return AMEM, nil
case ANOEQ: case ANOEQ:
if bad != nil { return ANOEQ, bad
*bad = t
}
return ANOEQ
} }
switch t.Bound { switch t.Bound {
case 0: case 0:
// We checked above that the element type is comparable. // We checked above that the element type is comparable.
return AMEM return AMEM, nil
case 1: case 1:
// Single-element array is same as its lone element. // Single-element array is same as its lone element.
return a return a, nil
} }
return -1 // needs special compare return ASPECIAL, nil
case TSTRUCT: case TSTRUCT:
fields := t.FieldSlice() fields := t.FieldSlice()
// One-field struct is same as that one field alone. // One-field struct is same as that one field alone.
if len(fields) == 1 && !isblanksym(fields[0].Sym) { if len(fields) == 1 && !isblanksym(fields[0].Sym) {
return algtype1(fields[0].Type, bad) return algtype1(fields[0].Type)
} }
ret := AMEM ret := AMEM
for i, f := range fields { for i, f := range fields {
// All fields must be comparable. // All fields must be comparable.
a := algtype1(f.Type, bad) a, bad := algtype1(f.Type)
if a == ANOEQ { if a == ANOEQ {
return ANOEQ return ANOEQ, bad
} }
// Blank fields, padded fields, fields with non-memory // Blank fields, padded fields, fields with non-memory
// equality need special compare. // equality need special compare.
if a != AMEM || isblanksym(f.Sym) || ispaddedfield(t, i) { if a != AMEM || isblanksym(f.Sym) || ispaddedfield(t, i) {
ret = -1 ret = ASPECIAL
} }
} }
return ret return ret, nil
} }
Fatalf("algtype1: unexpected type %v", t) Fatalf("algtype1: unexpected type %v", t)
return 0 return 0, nil
} }
// Generate a helper function to compute the hash of a value of type t. // Generate a helper function to compute the hash of a value of type t.
...@@ -239,7 +262,7 @@ func genhash(sym *Sym, t *Type) { ...@@ -239,7 +262,7 @@ func genhash(sym *Sym, t *Type) {
} }
// Hash non-memory fields with appropriate hash function. // Hash non-memory fields with appropriate hash function.
if algtype1(f.Type, nil) != AMEM { if !f.Type.IsRegularMemory() {
hashel := hashfor(f.Type) hashel := hashfor(f.Type)
call := Nod(OCALL, hashel, nil) call := Nod(OCALL, hashel, nil)
nx := NodSym(OXDOT, np, f.Sym) // TODO: fields from other packages? nx := NodSym(OXDOT, np, f.Sym) // TODO: fields from other packages?
...@@ -304,7 +327,7 @@ func genhash(sym *Sym, t *Type) { ...@@ -304,7 +327,7 @@ func genhash(sym *Sym, t *Type) {
func hashfor(t *Type) *Node { func hashfor(t *Type) *Node {
var sym *Sym var sym *Sym
switch algtype1(t, nil) { switch a, _ := algtype1(t); a {
case AMEM: case AMEM:
Fatalf("hashfor with AMEM type") Fatalf("hashfor with AMEM type")
case AINTER: case AINTER:
...@@ -435,7 +458,7 @@ func geneq(sym *Sym, t *Type) { ...@@ -435,7 +458,7 @@ func geneq(sym *Sym, t *Type) {
} }
// Compare non-memory fields with field equality. // Compare non-memory fields with field equality.
if algtype1(f.Type, nil) != AMEM { if !f.Type.IsRegularMemory() {
and(eqfield(np, nq, f.Sym)) and(eqfield(np, nq, f.Sym))
i++ i++
continue continue
...@@ -560,7 +583,7 @@ func memrun(t *Type, start int) (size int64, next int) { ...@@ -560,7 +583,7 @@ func memrun(t *Type, start int) (size int64, next int) {
break break
} }
// Also, stop before a blank or non-memory field. // Also, stop before a blank or non-memory field.
if f := t.Field(next); isblanksym(f.Sym) || algtype1(f.Type, nil) != AMEM { if f := t.Field(next); isblanksym(f.Sym) || !f.Type.IsRegularMemory() {
break break
} }
} }
......
...@@ -786,7 +786,7 @@ func dcommontype(s *Sym, ot int, t *Type) int { ...@@ -786,7 +786,7 @@ func dcommontype(s *Sym, ot int, t *Type) int {
dowidth(t) dowidth(t)
alg := algtype(t) alg := algtype(t)
var algsym *Sym var algsym *Sym
if alg < 0 || alg == AMEM { if alg == ASPECIAL || alg == AMEM {
algsym = dalgsym(t) algsym = dalgsym(t)
} }
...@@ -854,7 +854,7 @@ func dcommontype(s *Sym, ot int, t *Type) int { ...@@ -854,7 +854,7 @@ func dcommontype(s *Sym, ot int, t *Type) int {
} }
ot = duint8(s, ot, uint8(i)) // kind ot = duint8(s, ot, uint8(i)) // kind
if algsym == nil { if algsym == nil {
ot = dsymptr(s, ot, dcommontype_algarray, alg*sizeofAlg) ot = dsymptr(s, ot, dcommontype_algarray, int(alg)*sizeofAlg)
} else { } else {
ot = dsymptr(s, ot, algsym, 0) ot = dsymptr(s, ot, algsym, 0)
} }
......
...@@ -374,24 +374,15 @@ func saveorignode(n *Node) { ...@@ -374,24 +374,15 @@ func saveorignode(n *Node) {
// checkMapKeyType checks that Type key is valid for use as a map key. // checkMapKeyType checks that Type key is valid for use as a map key.
func checkMapKeyType(key *Type) { func checkMapKeyType(key *Type) {
var bad *Type alg, bad := algtype1(key)
atype := algtype1(key, &bad) if alg != ANOEQ {
var mtype EType return
if bad == nil {
mtype = key.Etype
} else {
mtype = bad.Etype
} }
switch mtype { switch bad.Etype {
default: default:
if atype == ANOEQ {
Yyerror("invalid map key type %v", key) Yyerror("invalid map key type %v", key)
}
case TANY: case TANY:
// will be resolved later. // Will be resolved later.
break
case TFORW: case TFORW:
// map[key] used during definition of key. // map[key] used during definition of key.
// postpone check until key is fully defined. // postpone check until key is fully defined.
......
...@@ -83,16 +83,17 @@ func typecheckswitch(n *Node) { ...@@ -83,16 +83,17 @@ func typecheckswitch(n *Node) {
t = Types[TBOOL] t = Types[TBOOL]
} }
if t != nil { if t != nil {
var badtype *Type
switch { switch {
case !okforeq[t.Etype]: case !okforeq[t.Etype]:
Yyerror("cannot switch on %v", Nconv(n.Left, FmtLong)) Yyerror("cannot switch on %v", Nconv(n.Left, FmtLong))
case t.Etype == TARRAY && !t.IsArray(): case t.IsSlice():
nilonly = "slice" nilonly = "slice"
case t.Etype == TARRAY && t.IsArray() && algtype1(t, nil) == ANOEQ: case t.IsArray() && !t.IsComparable():
Yyerror("cannot switch on %v", Nconv(n.Left, FmtLong)) Yyerror("cannot switch on %v", Nconv(n.Left, FmtLong))
case t.IsStruct() && algtype1(t, &badtype) == ANOEQ: case t.IsStruct():
Yyerror("cannot switch on %v (struct containing %v cannot be compared)", Nconv(n.Left, FmtLong), badtype) if f := t.IncomparableField(); f != nil {
Yyerror("cannot switch on %v (struct containing %v cannot be compared)", Nconv(n.Left, FmtLong), f.Type)
}
case t.Etype == TFUNC: case t.Etype == TFUNC:
nilonly = "func" nilonly = "func"
case t.IsMap(): case t.IsMap():
...@@ -139,7 +140,7 @@ func typecheckswitch(n *Node) { ...@@ -139,7 +140,7 @@ func typecheckswitch(n *Node) {
} }
case nilonly != "" && !isnil(n1): case nilonly != "" && !isnil(n1):
Yyerror("invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left) Yyerror("invalid case %v in switch (can only compare %s %v to nil)", n1, nilonly, n.Left)
case t.IsInterface() && !n1.Type.IsInterface() && algtype1(n1.Type, nil) == ANOEQ: case t.IsInterface() && !n1.Type.IsInterface() && !n1.Type.IsComparable():
Yyerror("invalid case %v in switch (incomparable type)", Nconv(n1, FmtLong)) Yyerror("invalid case %v in switch (incomparable type)", Nconv(n1, FmtLong))
} }
......
...@@ -596,7 +596,7 @@ OpSwitch: ...@@ -596,7 +596,7 @@ OpSwitch:
if r.Type.Etype != TBLANK { if r.Type.Etype != TBLANK {
aop = assignop(l.Type, r.Type, nil) aop = assignop(l.Type, r.Type, nil)
if aop != 0 { if aop != 0 {
if r.Type.IsInterface() && !l.Type.IsInterface() && algtype1(l.Type, nil) == ANOEQ { if r.Type.IsInterface() && !l.Type.IsInterface() && !l.Type.IsComparable() {
Yyerror("invalid operation: %v (operator %v not defined on %s)", n, Oconv(op, 0), typekind(l.Type)) Yyerror("invalid operation: %v (operator %v not defined on %s)", n, Oconv(op, 0), typekind(l.Type))
n.Type = nil n.Type = nil
return n return n
...@@ -618,7 +618,7 @@ OpSwitch: ...@@ -618,7 +618,7 @@ OpSwitch:
if l.Type.Etype != TBLANK { if l.Type.Etype != TBLANK {
aop = assignop(r.Type, l.Type, nil) aop = assignop(r.Type, l.Type, nil)
if aop != 0 { if aop != 0 {
if l.Type.IsInterface() && !r.Type.IsInterface() && algtype1(r.Type, nil) == ANOEQ { if l.Type.IsInterface() && !r.Type.IsInterface() && !r.Type.IsComparable() {
Yyerror("invalid operation: %v (operator %v not defined on %s)", n, Oconv(op, 0), typekind(r.Type)) Yyerror("invalid operation: %v (operator %v not defined on %s)", n, Oconv(op, 0), typekind(r.Type))
n.Type = nil n.Type = nil
return n return n
...@@ -657,7 +657,7 @@ OpSwitch: ...@@ -657,7 +657,7 @@ OpSwitch:
// okfor allows any array == array, map == map, func == func. // okfor allows any array == array, map == map, func == func.
// restrict to slice/map/func == nil and nil == slice/map/func. // restrict to slice/map/func == nil and nil == slice/map/func.
if l.Type.IsArray() && algtype1(l.Type, nil) == ANOEQ { if l.Type.IsArray() && !l.Type.IsComparable() {
Yyerror("invalid operation: %v (%v cannot be compared)", n, l.Type) Yyerror("invalid operation: %v (%v cannot be compared)", n, l.Type)
n.Type = nil n.Type = nil
return n return n
...@@ -681,12 +681,13 @@ OpSwitch: ...@@ -681,12 +681,13 @@ OpSwitch:
return n return n
} }
var badtype *Type if l.Type.IsStruct() {
if l.Type.IsStruct() && algtype1(l.Type, &badtype) == ANOEQ { if f := l.Type.IncomparableField(); f != nil {
Yyerror("invalid operation: %v (struct containing %v cannot be compared)", n, badtype) Yyerror("invalid operation: %v (struct containing %v cannot be compared)", n, f.Type)
n.Type = nil n.Type = nil
return n return n
} }
}
t = l.Type t = l.Type
if iscmp[n.Op] { if iscmp[n.Op] {
......
...@@ -3020,19 +3020,13 @@ func eqfor(t *Type, needsize *int) *Node { ...@@ -3020,19 +3020,13 @@ func eqfor(t *Type, needsize *int) *Node {
// a struct/array containing a non-memory field/element. // a struct/array containing a non-memory field/element.
// Small memory is handled inline, and single non-memory // Small memory is handled inline, and single non-memory
// is handled during type check (OCMPSTR etc). // is handled during type check (OCMPSTR etc).
a := algtype1(t, nil) switch a, _ := algtype1(t); a {
case AMEM:
if a != AMEM && a != -1 {
Fatalf("eqfor %v", t)
}
if a == AMEM {
n := syslook("memequal") n := syslook("memequal")
n = substArgTypes(n, t, t) n = substArgTypes(n, t, t)
*needsize = 1 *needsize = 1
return n return n
} case ASPECIAL:
sym := typesymprefix(".eq", t) sym := typesymprefix(".eq", t)
n := newname(sym) n := newname(sym)
n.Class = PFUNC n.Class = PFUNC
...@@ -3044,6 +3038,9 @@ func eqfor(t *Type, needsize *int) *Node { ...@@ -3044,6 +3038,9 @@ func eqfor(t *Type, needsize *int) *Node {
n.Type = ntype.Type n.Type = ntype.Type
*needsize = 0 *needsize = 0
return n return n
}
Fatalf("eqfor %v", t)
return nil
} }
// The result of walkcompare MUST be assigned back to n, e.g. // The result of walkcompare MUST be assigned back to n, e.g.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment