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 {
|
func makefield(name string, t *Type) *Type {
|
||||||
f := typ(TFIELD)
|
f := typ(TFIELD)
|
||||||
f.Type = t
|
f.Type = t
|
||||||
f.Sym = new(Sym)
|
f.Sym = nopkg.Lookup(name)
|
||||||
f.Sym.Name = name
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ package gc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmd/compile/internal/ssa"
|
"cmd/compile/internal/ssa"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Type) Size() int64 {
|
func (t *Type) Size() int64 {
|
||||||
|
|
@ -35,6 +36,248 @@ func (t *Type) Equal(u ssa.Type) bool {
|
||||||
return Eqtype(t, x)
|
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 {
|
func (t *Type) IsBoolean() bool {
|
||||||
return t.Etype == TBOOL
|
return t.Etype == TBOOL
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,12 +155,15 @@ func cse(f *Func) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rewrites := 0
|
||||||
|
|
||||||
// Apply substitutions
|
// Apply substitutions
|
||||||
for _, b := range f.Blocks {
|
for _, b := range f.Blocks {
|
||||||
for _, v := range b.Values {
|
for _, v := range b.Values {
|
||||||
for i, w := range v.Args {
|
for i, w := range v.Args {
|
||||||
if x := rewrite[w.ID]; x != nil {
|
if x := rewrite[w.ID]; x != nil {
|
||||||
v.SetArg(i, x)
|
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
|
// An eqclass approximates an equivalence class. During the
|
||||||
|
|
@ -197,9 +203,8 @@ type eqclass []*Value
|
||||||
// backed by the same storage as the input slice.
|
// backed by the same storage as the input slice.
|
||||||
// Equivalence classes of size 1 are ignored.
|
// Equivalence classes of size 1 are ignored.
|
||||||
func partitionValues(a []*Value) []eqclass {
|
func partitionValues(a []*Value) []eqclass {
|
||||||
typNames := map[Type]string{}
|
|
||||||
auxIDs := map[interface{}]int32{}
|
auxIDs := map[interface{}]int32{}
|
||||||
sort.Sort(sortvalues{a, typNames, auxIDs})
|
sort.Sort(sortvalues{a, auxIDs})
|
||||||
|
|
||||||
var partition []eqclass
|
var partition []eqclass
|
||||||
for len(a) > 0 {
|
for len(a) > 0 {
|
||||||
|
|
@ -217,10 +222,10 @@ func partitionValues(a []*Value) []eqclass {
|
||||||
v.Args[0].AuxInt != w.Args[0].AuxInt) ||
|
v.Args[0].AuxInt != w.Args[0].AuxInt) ||
|
||||||
len(v.Args) >= 2 && (v.Args[1].Op != w.Args[1].Op ||
|
len(v.Args) >= 2 && (v.Args[1].Op != w.Args[1].Op ||
|
||||||
v.Args[1].AuxInt != w.Args[1].AuxInt) ||
|
v.Args[1].AuxInt != w.Args[1].AuxInt) ||
|
||||||
typNames[v.Type] != typNames[w.Type] {
|
v.Type.Compare(w.Type) != CMPeq {
|
||||||
if Debug > 3 {
|
if Debug > 3 {
|
||||||
fmt.Printf("CSE.partitionValues separates %s from %s, AuxInt=%v, Aux=%v, typNames=%v",
|
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, typNames[v.Type] != typNames[w.Type])
|
v.LongString(), w.LongString(), v.AuxInt != w.AuxInt, v.Aux != w.Aux, v.Type.Compare(w.Type))
|
||||||
if !rootsDiffer {
|
if !rootsDiffer {
|
||||||
if len(v.Args) >= 1 {
|
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)
|
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.
|
// Sort values to make the initial partition.
|
||||||
type sortvalues struct {
|
type sortvalues struct {
|
||||||
a []*Value // array of values
|
a []*Value // array of values
|
||||||
typNames map[Type]string // type -> type ID map
|
auxIDs map[interface{}]int32 // aux -> aux ID map
|
||||||
auxIDs map[interface{}]int32 // aux -> aux ID map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv sortvalues) Len() int { return len(sv.a) }
|
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
|
// Sort by type, using the ssa.Type Compare method
|
||||||
// them with < directly. Instead, map types to their names and
|
|
||||||
// sort on that.
|
|
||||||
if v.Type != w.Type {
|
if v.Type != w.Type {
|
||||||
x := sv.typNames[v.Type]
|
c := v.Type.Compare(w.Type)
|
||||||
if x == "" {
|
if c != CMPeq {
|
||||||
x = v.Type.String()
|
return c == CMPlt
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
if v.Aux != w.Aux {
|
||||||
x := sv.auxIDs[v.Aux]
|
x := sv.auxIDs[v.Aux]
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ type Type interface {
|
||||||
String() string
|
String() string
|
||||||
SimpleString() string // a coarser generic description of T, e.g. T's underlying type
|
SimpleString() string // a coarser generic description of T, e.g. T's underlying type
|
||||||
Equal(Type) bool
|
Equal(Type) bool
|
||||||
|
Compare(Type) Cmp // compare types, returning one of CMPlt, CMPeq, CMPgt.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special compiler-only types.
|
// 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) FieldOff(i int64) int64 { panic("not implemented") }
|
||||||
func (t *CompilerType) NumElem() 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 {
|
func (t *CompilerType) Equal(u Type) bool {
|
||||||
x, ok := u.(*CompilerType)
|
x, ok := u.(*CompilerType)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,29 @@ func (t *TypeImpl) Equal(u Type) bool {
|
||||||
return x == t
|
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 (
|
var (
|
||||||
// shortcuts for commonly used basic types
|
// shortcuts for commonly used basic types
|
||||||
TypeInt8 = &TypeImpl{Size_: 1, Align: 1, Integer: true, Signed: true, Name: "int8"}
|
TypeInt8 = &TypeImpl{Size_: 1, Align: 1, Integer: true, Signed: true, Name: "int8"}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue