]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.ssa] cmd/compile: double speed of CSE phase
authorDavid Chase <drchase@google.com>
Thu, 11 Feb 2016 20:09:43 +0000 (15:09 -0500)
committerDavid Chase <drchase@google.com>
Mon, 22 Feb 2016 17:15:41 +0000 (17:15 +0000)
Replaced comparison based on (*Type).String() with an
allocation-free structural comparison.  Roughly doubles
speed of CSE, also reduces allocations.

Checked that roughly the same number of CSEs were detected
during make.bash (about a million) and that "new" CSEs
were caused by the effect described above.

Change-Id: Id205a9f6986efd518043e12d651f0b01206aeb1b
Reviewed-on: https://go-review.googlesource.com/19471
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/compile/internal/gc/reflect.go
src/cmd/compile/internal/gc/type.go
src/cmd/compile/internal/ssa/cse.go
src/cmd/compile/internal/ssa/type.go
src/cmd/compile/internal/ssa/type_test.go

index 264955c702b4d0aa629d3a0216d2f70ed5cfa4a2..f6dd75ec4a6824d346c4c18a9ee289a127875c26 100644 (file)
@@ -55,8 +55,7 @@ const (
 func makefield(name string, t *Type) *Type {
        f := typ(TFIELD)
        f.Type = t
-       f.Sym = new(Sym)
-       f.Sym.Name = name
+       f.Sym = nopkg.Lookup(name)
        return f
 }
 
index 3f218ee3da2a864cf8d4e970868e40e658fc06c1..f09094ce23ced8469e615c6aab605f3130bc1c97 100644 (file)
@@ -11,6 +11,7 @@ package gc
 
 import (
        "cmd/compile/internal/ssa"
+       "fmt"
 )
 
 func (t *Type) Size() int64 {
@@ -35,6 +36,248 @@ func (t *Type) Equal(u ssa.Type) bool {
        return Eqtype(t, x)
 }
 
+// Compare compares types for purposes of the SSA back
+// end, returning an ssa.Cmp (one of CMPlt, CMPeq, CMPgt).
+// The answers are correct for an optimizer
+// or code generator, but not for Go source.
+// For example, "type gcDrainFlags int" results in
+// two Go-different types that Compare equal.
+// The order chosen is also arbitrary, only division into
+// equivalence classes (Types that compare CMPeq) matters.
+func (t *Type) Compare(u ssa.Type) ssa.Cmp {
+       x, ok := u.(*Type)
+       // ssa.CompilerType is smaller than gc.Type
+       // bare pointer equality is easy.
+       if !ok {
+               return ssa.CMPgt
+       }
+       if x == t {
+               return ssa.CMPeq
+       }
+       return t.cmp(x)
+}
+
+func cmpForNe(x bool) ssa.Cmp {
+       if x {
+               return ssa.CMPlt
+       }
+       return ssa.CMPgt
+}
+
+func (r *Sym) cmpsym(s *Sym) ssa.Cmp {
+       if r == s {
+               return ssa.CMPeq
+       }
+       if r == nil {
+               return ssa.CMPlt
+       }
+       if s == nil {
+               return ssa.CMPgt
+       }
+       // Fast sort, not pretty sort
+       if len(r.Name) != len(s.Name) {
+               return cmpForNe(len(r.Name) < len(s.Name))
+       }
+       if r.Pkg != s.Pkg {
+               if len(r.Pkg.Prefix) != len(s.Pkg.Prefix) {
+                       return cmpForNe(len(r.Pkg.Prefix) < len(s.Pkg.Prefix))
+               }
+               if r.Pkg.Prefix != s.Pkg.Prefix {
+                       return cmpForNe(r.Pkg.Prefix < s.Pkg.Prefix)
+               }
+       }
+       if r.Name != s.Name {
+               return cmpForNe(r.Name < s.Name)
+       }
+       return ssa.CMPeq
+}
+
+// cmp compares two *Types t and x, returning ssa.CMPlt,
+// ssa.CMPeq, ssa.CMPgt as t<x, t==x, t>x, for an arbitrary
+// and optimizer-centric notion of comparison.
+func (t *Type) cmp(x *Type) ssa.Cmp {
+       // This follows the structure of Eqtype in subr.go
+       // with two exceptions.
+       // 1. Symbols are compared more carefully because a <,=,> result is desired.
+       // 2. Maps are treated specially to avoid endless recursion -- maps
+       //    contain an internal data type not expressible in Go source code.
+       if t == x {
+               return ssa.CMPeq
+       }
+       if t == nil {
+               return ssa.CMPlt
+       }
+       if x == nil {
+               return ssa.CMPgt
+       }
+
+       if t.Etype != x.Etype {
+               return cmpForNe(t.Etype < x.Etype)
+       }
+
+       if t.Sym != nil || x.Sym != nil {
+               // Special case: we keep byte and uint8 separate
+               // for error messages.  Treat them as equal.
+               switch t.Etype {
+               case TUINT8:
+                       if (t == Types[TUINT8] || t == bytetype) && (x == Types[TUINT8] || x == bytetype) {
+                               return ssa.CMPeq
+                       }
+
+               case TINT32:
+                       if (t == Types[runetype.Etype] || t == runetype) && (x == Types[runetype.Etype] || x == runetype) {
+                               return ssa.CMPeq
+                       }
+               }
+       }
+
+       csym := t.Sym.cmpsym(x.Sym)
+       if csym != ssa.CMPeq {
+               return csym
+       }
+
+       if x.Sym != nil {
+               // Syms non-nil, if vargens match then equal.
+               if t.Vargen == x.Vargen {
+                       return ssa.CMPeq
+               }
+               if t.Vargen < x.Vargen {
+                       return ssa.CMPlt
+               }
+               return ssa.CMPgt
+       }
+       // both syms nil, look at structure below.
+
+       switch t.Etype {
+       case TBOOL, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TUNSAFEPTR, TUINTPTR,
+               TINT8, TINT16, TINT32, TINT64, TINT, TUINT8, TUINT16, TUINT32, TUINT64, TUINT:
+               return ssa.CMPeq
+       }
+
+       switch t.Etype {
+       case TMAP, TFIELD:
+               // No special cases for these two, they are handled
+               // by the general code after the switch.
+
+       case TPTR32, TPTR64:
+               return t.Type.cmp(x.Type)
+
+       case TSTRUCT:
+               if t.Map == nil {
+                       if x.Map != nil {
+                               return ssa.CMPlt // nil < non-nil
+                       }
+                       // to the fallthrough
+               } else if x.Map == nil {
+                       return ssa.CMPgt // nil > non-nil
+               } else if t.Map.Bucket == t {
+                       // Both have non-nil Map
+                       // Special case for Maps which include a recursive type where the recursion is not broken with a named type
+                       if x.Map.Bucket != x {
+                               return ssa.CMPlt // bucket maps are least
+                       }
+                       return t.Map.cmp(x.Map)
+               } // If t != t.Map.Bucket, fall through to general case
+
+               fallthrough
+       case TINTER:
+               t1 := t.Type
+               x1 := x.Type
+               for ; t1 != nil && x1 != nil; t1, x1 = t1.Down, x1.Down {
+                       if t1.Embedded != x1.Embedded {
+                               if t1.Embedded < x1.Embedded {
+                                       return ssa.CMPlt
+                               }
+                               return ssa.CMPgt
+                       }
+                       if t1.Note != x1.Note {
+                               if t1.Note == nil {
+                                       return ssa.CMPlt
+                               }
+                               if x1.Note == nil {
+                                       return ssa.CMPgt
+                               }
+                               if *t1.Note != *x1.Note {
+                                       if *t1.Note < *x1.Note {
+                                               return ssa.CMPlt
+                                       }
+                                       return ssa.CMPgt
+                               }
+                       }
+                       c := t1.Sym.cmpsym(x1.Sym)
+                       if c != ssa.CMPeq {
+                               return c
+                       }
+                       c = t1.Type.cmp(x1.Type)
+                       if c != ssa.CMPeq {
+                               return c
+                       }
+               }
+               if t1 == x1 {
+                       return ssa.CMPeq
+               }
+               if t1 == nil {
+                       return ssa.CMPlt
+               }
+               return ssa.CMPgt
+
+       case TFUNC:
+               t1 := t.Type
+               t2 := x.Type
+               for ; t1 != nil && t2 != nil; t1, t2 = t1.Down, t2.Down {
+                       // Loop over fields in structs, ignoring argument names.
+                       ta := t1.Type
+                       tb := t2.Type
+                       for ; ta != nil && tb != nil; ta, tb = ta.Down, tb.Down {
+                               if ta.Isddd != tb.Isddd {
+                                       if ta.Isddd {
+                                               return ssa.CMPgt
+                                       }
+                                       return ssa.CMPlt
+                               }
+                               c := ta.Type.cmp(tb.Type)
+                               if c != ssa.CMPeq {
+                                       return c
+                               }
+                       }
+
+                       if ta != tb {
+                               if t1 == nil {
+                                       return ssa.CMPlt
+                               }
+                               return ssa.CMPgt
+                       }
+               }
+               if t1 != t2 {
+                       if t1 == nil {
+                               return ssa.CMPlt
+                       }
+                       return ssa.CMPgt
+               }
+               return ssa.CMPeq
+
+       case TARRAY:
+               if t.Bound != x.Bound {
+                       return cmpForNe(t.Bound < x.Bound)
+               }
+
+       case TCHAN:
+               if t.Chan != x.Chan {
+                       return cmpForNe(t.Chan < x.Chan)
+               }
+
+       default:
+               e := fmt.Sprintf("Do not know how to compare %s with %s", t, x)
+               panic(e)
+       }
+
+       c := t.Down.cmp(x.Down)
+       if c != ssa.CMPeq {
+               return c
+       }
+       return t.Type.cmp(x.Type)
+}
+
 func (t *Type) IsBoolean() bool {
        return t.Etype == TBOOL
 }
index ea4fe0a97b37e706b12915e812674a504759da81..44bd87683db978592829fb5420025517bb4e7d88 100644 (file)
@@ -155,12 +155,15 @@ func cse(f *Func) {
                }
        }
 
+       rewrites := 0
+
        // Apply substitutions
        for _, b := range f.Blocks {
                for _, v := range b.Values {
                        for i, w := range v.Args {
                                if x := rewrite[w.ID]; x != nil {
                                        v.SetArg(i, x)
+                                       rewrites++
                                }
                        }
                }
@@ -175,6 +178,9 @@ func cse(f *Func) {
                        }
                }
        }
+       if Debug > 0 && rewrites > 0 {
+               fmt.Printf("CSE: %d rewrites\n", rewrites)
+       }
 }
 
 // An eqclass approximates an equivalence class.  During the
@@ -197,9 +203,8 @@ type eqclass []*Value
 // backed by the same storage as the input slice.
 // Equivalence classes of size 1 are ignored.
 func partitionValues(a []*Value) []eqclass {
-       typNames := map[Type]string{}
        auxIDs := map[interface{}]int32{}
-       sort.Sort(sortvalues{a, typNames, auxIDs})
+       sort.Sort(sortvalues{a, auxIDs})
 
        var partition []eqclass
        for len(a) > 0 {
@@ -217,10 +222,10 @@ func partitionValues(a []*Value) []eqclass {
                                        v.Args[0].AuxInt != w.Args[0].AuxInt) ||
                                len(v.Args) >= 2 && (v.Args[1].Op != w.Args[1].Op ||
                                        v.Args[1].AuxInt != w.Args[1].AuxInt) ||
-                               typNames[v.Type] != typNames[w.Type] {
+                               v.Type.Compare(w.Type) != CMPeq {
                                if Debug > 3 {
-                                       fmt.Printf("CSE.partitionValues separates %s from %s, AuxInt=%v, Aux=%v, typNames=%v",
-                                               v.LongString(), w.LongString(), v.AuxInt != w.AuxInt, v.Aux != w.Aux, typNames[v.Type] != typNames[w.Type])
+                                       fmt.Printf("CSE.partitionValues separates %s from %s, AuxInt=%v, Aux=%v, Type.compare=%v",
+                                               v.LongString(), w.LongString(), v.AuxInt != w.AuxInt, v.Aux != w.Aux, v.Type.Compare(w.Type))
                                        if !rootsDiffer {
                                                if len(v.Args) >= 1 {
                                                        fmt.Printf(", a0Op=%v, a0AuxInt=%v", v.Args[0].Op != w.Args[0].Op, v.Args[0].AuxInt != w.Args[0].AuxInt)
@@ -245,9 +250,8 @@ func partitionValues(a []*Value) []eqclass {
 
 // Sort values to make the initial partition.
 type sortvalues struct {
-       a        []*Value              // array of values
-       typNames map[Type]string       // type -> type ID map
-       auxIDs   map[interface{}]int32 // aux -> aux ID map
+       a      []*Value              // array of values
+       auxIDs map[interface{}]int32 // aux -> aux ID map
 }
 
 func (sv sortvalues) Len() int      { return len(sv.a) }
@@ -301,26 +305,17 @@ func (sv sortvalues) Less(i, j int) bool {
                }
        }
 
-       // Sort by type.  Types are just interfaces, so we can't compare
-       // them with < directly.  Instead, map types to their names and
-       // sort on that.
+       // Sort by type, using the ssa.Type Compare method
        if v.Type != w.Type {
-               x := sv.typNames[v.Type]
-               if x == "" {
-                       x = v.Type.String()
-                       sv.typNames[v.Type] = x
-               }
-               y := sv.typNames[w.Type]
-               if y == "" {
-                       y = w.Type.String()
-                       sv.typNames[w.Type] = y
-               }
-               if x != y {
-                       return x < y
+               c := v.Type.Compare(w.Type)
+               if c != CMPeq {
+                       return c == CMPlt
                }
        }
 
-       // Same deal for aux fields.
+       // Aux fields are interfaces with no comparison
+       // method.  Use a map to number distinct ones,
+       // and use those numbers for comparison.
        if v.Aux != w.Aux {
                x := sv.auxIDs[v.Aux]
                if x == 0 {
index 9a692dcfb078f2e7e683df879595c6bc4cded593..afe04fa043c0e9ee545fb723861f574f27145f4c 100644 (file)
@@ -40,6 +40,7 @@ type Type interface {
        String() string
        SimpleString() string // a coarser generic description of T, e.g. T's underlying type
        Equal(Type) bool
+       Compare(Type) Cmp // compare types, returning one of CMPlt, CMPeq, CMPgt.
 }
 
 // Special compiler-only types.
@@ -76,6 +77,40 @@ func (t *CompilerType) FieldType(i int64) Type { panic("not implemented") }
 func (t *CompilerType) FieldOff(i int64) int64 { panic("not implemented") }
 func (t *CompilerType) NumElem() int64         { panic("not implemented") }
 
+// Cmp is a comparison between values a and b.
+// -1 if a < b
+//  0 if a == b
+//  1 if a > b
+type Cmp int8
+
+const (
+       CMPlt = Cmp(-1)
+       CMPeq = Cmp(0)
+       CMPgt = Cmp(1)
+)
+
+func (t *CompilerType) Compare(u Type) Cmp {
+       x, ok := u.(*CompilerType)
+       // ssa.CompilerType is smaller than any other type
+       if !ok {
+               return CMPlt
+       }
+       // desire fast sorting, not pretty sorting.
+       if len(t.Name) == len(x.Name) {
+               if t.Name == x.Name {
+                       return CMPeq
+               }
+               if t.Name < x.Name {
+                       return CMPlt
+               }
+               return CMPgt
+       }
+       if len(t.Name) > len(x.Name) {
+               return CMPgt
+       }
+       return CMPlt
+}
+
 func (t *CompilerType) Equal(u Type) bool {
        x, ok := u.(*CompilerType)
        if !ok {
index f09919a6526da794c9352679e3843cec29882c38..26c8223c6219127e92cde7200fd63f12934c8936 100644 (file)
@@ -57,6 +57,29 @@ func (t *TypeImpl) Equal(u Type) bool {
        return x == t
 }
 
+func (t *TypeImpl) Compare(u Type) Cmp {
+       x, ok := u.(*TypeImpl)
+       // ssa.CompilerType < ssa.TypeImpl < gc.Type
+       if !ok {
+               _, ok := u.(*CompilerType)
+               if ok {
+                       return CMPgt
+               }
+               return CMPlt
+       }
+       if t == x {
+               return CMPeq
+       }
+       if t.Name < x.Name {
+               return CMPlt
+       }
+       if t.Name > x.Name {
+               return CMPgt
+       }
+       return CMPeq
+
+}
+
 var (
        // shortcuts for commonly used basic types
        TypeInt8       = &TypeImpl{Size_: 1, Align: 1, Integer: true, Signed: true, Name: "int8"}