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: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go
index de26237..43876d8 100644
--- a/src/cmd/compile/internal/gc/alg.go
+++ b/src/cmd/compile/internal/gc/alg.go
@@ -6,9 +6,13 @@
 
 import "fmt"
 
+// AlgKind describes the kind of algorithms used for comparing and
+// hashing a Type.
+type AlgKind int
+
 const (
 	// These values are known by runtime.
-	ANOEQ = iota
+	ANOEQ AlgKind = iota
 	AMEM0
 	AMEM8
 	AMEM16
@@ -22,11 +26,40 @@
 	AFLOAT64
 	ACPLX64
 	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 {
-	a := algtype1(t, nil)
+// IsComparable reports whether t is a comparable type.
+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 {
 		switch t.Width {
 		case 0:
@@ -47,115 +80,105 @@
 	return a
 }
 
-func algtype1(t *Type, bad **Type) int {
-	if bad != nil {
-		*bad = nil
-	}
+// algtype1 returns the AlgKind used for comparing and hashing Type t.
+// If it returns ANOEQ, it also returns the component type of t that
+// makes it incomparable.
+func algtype1(t *Type) (AlgKind, *Type) {
 	if t.Broke {
-		return AMEM
+		return AMEM, nil
 	}
 	if t.Noalg {
-		return ANOEQ
+		return ANOEQ, t
 	}
 
 	switch t.Etype {
 	case TANY, TFORW:
 		// will be defined later.
-		*bad = t
-		return -1
+		return ANOEQ, t
 
 	case TINT8, TUINT8, TINT16, TUINT16,
 		TINT32, TUINT32, TINT64, TUINT64,
 		TINT, TUINT, TUINTPTR,
 		TBOOL, TPTR32, TPTR64,
 		TCHAN, TUNSAFEPTR:
-		return AMEM
+		return AMEM, nil
 
 	case TFUNC, TMAP:
-		if bad != nil {
-			*bad = t
-		}
-		return ANOEQ
+		return ANOEQ, t
 
 	case TFLOAT32:
-		return AFLOAT32
+		return AFLOAT32, nil
 
 	case TFLOAT64:
-		return AFLOAT64
+		return AFLOAT64, nil
 
 	case TCOMPLEX64:
-		return ACPLX64
+		return ACPLX64, nil
 
 	case TCOMPLEX128:
-		return ACPLX128
+		return ACPLX128, nil
 
 	case TSTRING:
-		return ASTRING
+		return ASTRING, nil
 
 	case TINTER:
 		if isnilinter(t) {
-			return ANILINTER
+			return ANILINTER, nil
 		}
-		return AINTER
+		return AINTER, nil
 
 	case TARRAY:
 		if t.IsSlice() {
-			if bad != nil {
-				*bad = t
-			}
-			return ANOEQ
+			return ANOEQ, t
 		}
 
-		a := algtype1(t.Elem(), bad)
+		a, bad := algtype1(t.Elem())
 		switch a {
 		case AMEM:
-			return AMEM
+			return AMEM, nil
 		case ANOEQ:
-			if bad != nil {
-				*bad = t
-			}
-			return ANOEQ
+			return ANOEQ, bad
 		}
 
 		switch t.Bound {
 		case 0:
 			// We checked above that the element type is comparable.
-			return AMEM
+			return AMEM, nil
 		case 1:
 			// Single-element array is same as its lone element.
-			return a
+			return a, nil
 		}
 
-		return -1 // needs special compare
+		return ASPECIAL, nil
 
 	case TSTRUCT:
 		fields := t.FieldSlice()
 
 		// One-field struct is same as that one field alone.
 		if len(fields) == 1 && !isblanksym(fields[0].Sym) {
-			return algtype1(fields[0].Type, bad)
+			return algtype1(fields[0].Type)
 		}
 
 		ret := AMEM
 		for i, f := range fields {
 			// All fields must be comparable.
-			a := algtype1(f.Type, bad)
+			a, bad := algtype1(f.Type)
 			if a == ANOEQ {
-				return ANOEQ
+				return ANOEQ, bad
 			}
 
 			// Blank fields, padded fields, fields with non-memory
 			// equality need special compare.
 			if a != AMEM || isblanksym(f.Sym) || ispaddedfield(t, i) {
-				ret = -1
+				ret = ASPECIAL
 			}
 		}
 
-		return ret
+		return ret, nil
 	}
 
 	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.
@@ -239,7 +262,7 @@
 			}
 
 			// Hash non-memory fields with appropriate hash function.
-			if algtype1(f.Type, nil) != AMEM {
+			if !f.Type.IsRegularMemory() {
 				hashel := hashfor(f.Type)
 				call := Nod(OCALL, hashel, nil)
 				nx := NodSym(OXDOT, np, f.Sym) // TODO: fields from other packages?
@@ -304,7 +327,7 @@
 func hashfor(t *Type) *Node {
 	var sym *Sym
 
-	switch algtype1(t, nil) {
+	switch a, _ := algtype1(t); a {
 	case AMEM:
 		Fatalf("hashfor with AMEM type")
 	case AINTER:
@@ -435,7 +458,7 @@
 			}
 
 			// Compare non-memory fields with field equality.
-			if algtype1(f.Type, nil) != AMEM {
+			if !f.Type.IsRegularMemory() {
 				and(eqfield(np, nq, f.Sym))
 				i++
 				continue
@@ -560,7 +583,7 @@
 			break
 		}
 		// 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
 		}
 	}
diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go
index a2fdf04..a5c85eb 100644
--- a/src/cmd/compile/internal/gc/reflect.go
+++ b/src/cmd/compile/internal/gc/reflect.go
@@ -786,7 +786,7 @@
 	dowidth(t)
 	alg := algtype(t)
 	var algsym *Sym
-	if alg < 0 || alg == AMEM {
+	if alg == ASPECIAL || alg == AMEM {
 		algsym = dalgsym(t)
 	}
 
@@ -854,7 +854,7 @@
 	}
 	ot = duint8(s, ot, uint8(i)) // kind
 	if algsym == nil {
-		ot = dsymptr(s, ot, dcommontype_algarray, alg*sizeofAlg)
+		ot = dsymptr(s, ot, dcommontype_algarray, int(alg)*sizeofAlg)
 	} else {
 		ot = dsymptr(s, ot, algsym, 0)
 	}
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index c40cda0..b4acb5b 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -374,24 +374,15 @@
 
 // checkMapKeyType checks that Type key is valid for use as a map key.
 func checkMapKeyType(key *Type) {
-	var bad *Type
-	atype := algtype1(key, &bad)
-	var mtype EType
-	if bad == nil {
-		mtype = key.Etype
-	} else {
-		mtype = bad.Etype
+	alg, bad := algtype1(key)
+	if alg != ANOEQ {
+		return
 	}
-	switch mtype {
+	switch bad.Etype {
 	default:
-		if atype == ANOEQ {
-			Yyerror("invalid map key type %v", key)
-		}
-
+		Yyerror("invalid map key type %v", key)
 	case TANY:
-		// will be resolved later.
-		break
-
+		// Will be resolved later.
 	case TFORW:
 		// map[key] used during definition of key.
 		// postpone check until key is fully defined.
diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go
index ae8af76..996bd6911 100644
--- a/src/cmd/compile/internal/gc/swt.go
+++ b/src/cmd/compile/internal/gc/swt.go
@@ -83,16 +83,17 @@
 			t = Types[TBOOL]
 		}
 		if t != nil {
-			var badtype *Type
 			switch {
 			case !okforeq[t.Etype]:
 				Yyerror("cannot switch on %v", Nconv(n.Left, FmtLong))
-			case t.Etype == TARRAY && !t.IsArray():
+			case t.IsSlice():
 				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))
-			case t.IsStruct() && algtype1(t, &badtype) == ANOEQ:
-				Yyerror("cannot switch on %v (struct containing %v cannot be compared)", Nconv(n.Left, FmtLong), badtype)
+			case t.IsStruct():
+				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:
 				nilonly = "func"
 			case t.IsMap():
@@ -139,7 +140,7 @@
 						}
 					case nilonly != "" && !isnil(n1):
 						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))
 					}
 
diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index f0b0f08..68e29b6 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -596,7 +596,7 @@
 			if r.Type.Etype != TBLANK {
 				aop = assignop(l.Type, r.Type, nil)
 				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))
 						n.Type = nil
 						return n
@@ -618,7 +618,7 @@
 			if l.Type.Etype != TBLANK {
 				aop = assignop(r.Type, l.Type, nil)
 				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))
 						n.Type = nil
 						return n
@@ -657,7 +657,7 @@
 
 		// okfor allows any array == array, map == map, func == 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)
 			n.Type = nil
 			return n
@@ -681,11 +681,12 @@
 			return n
 		}
 
-		var badtype *Type
-		if l.Type.IsStruct() && algtype1(l.Type, &badtype) == ANOEQ {
-			Yyerror("invalid operation: %v (struct containing %v cannot be compared)", n, badtype)
-			n.Type = nil
-			return n
+		if l.Type.IsStruct() {
+			if f := l.Type.IncomparableField(); f != nil {
+				Yyerror("invalid operation: %v (struct containing %v cannot be compared)", n, f.Type)
+				n.Type = nil
+				return n
+			}
 		}
 
 		t = l.Type
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index a3e8a04..9310171 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -3020,30 +3020,27 @@
 	// a struct/array containing a non-memory field/element.
 	// Small memory is handled inline, and single non-memory
 	// is handled during type check (OCMPSTR etc).
-	a := algtype1(t, nil)
-
-	if a != AMEM && a != -1 {
-		Fatalf("eqfor %v", t)
-	}
-
-	if a == AMEM {
+	switch a, _ := algtype1(t); a {
+	case AMEM:
 		n := syslook("memequal")
 		n = substArgTypes(n, t, t)
 		*needsize = 1
 		return n
+	case ASPECIAL:
+		sym := typesymprefix(".eq", t)
+		n := newname(sym)
+		n.Class = PFUNC
+		ntype := Nod(OTFUNC, nil, nil)
+		ntype.List.Append(Nod(ODCLFIELD, nil, typenod(Ptrto(t))))
+		ntype.List.Append(Nod(ODCLFIELD, nil, typenod(Ptrto(t))))
+		ntype.Rlist.Append(Nod(ODCLFIELD, nil, typenod(Types[TBOOL])))
+		ntype = typecheck(ntype, Etype)
+		n.Type = ntype.Type
+		*needsize = 0
+		return n
 	}
-
-	sym := typesymprefix(".eq", t)
-	n := newname(sym)
-	n.Class = PFUNC
-	ntype := Nod(OTFUNC, nil, nil)
-	ntype.List.Append(Nod(ODCLFIELD, nil, typenod(Ptrto(t))))
-	ntype.List.Append(Nod(ODCLFIELD, nil, typenod(Ptrto(t))))
-	ntype.Rlist.Append(Nod(ODCLFIELD, nil, typenod(Types[TBOOL])))
-	ntype = typecheck(ntype, Etype)
-	n.Type = ntype.Type
-	*needsize = 0
-	return n
+	Fatalf("eqfor %v", t)
+	return nil
 }
 
 // The result of walkcompare MUST be assigned back to n, e.g.