mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.ssa] cmd/compile: double speed of CSE phase
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>
This commit is contained in:
parent
88c1ef5b45
commit
c3db6c95b6
5 changed files with 321 additions and 26 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -246,7 +251,6 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue