2015-03-27 13:41:30 -07:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package ssa
|
|
|
|
|
|
2016-02-08 12:07:39 -05:00
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"sort"
|
|
|
|
|
)
|
2015-03-27 13:41:30 -07:00
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
const (
|
|
|
|
|
cmpDepth = 4
|
|
|
|
|
)
|
|
|
|
|
|
2015-03-27 13:41:30 -07:00
|
|
|
// cse does common-subexpression elimination on the Function.
|
2016-03-01 23:21:55 +00:00
|
|
|
// Values are just relinked, nothing is deleted. A subsequent deadcode
|
2015-03-27 13:41:30 -07:00
|
|
|
// pass is required to actually remove duplicate expressions.
|
|
|
|
|
func cse(f *Func) {
|
|
|
|
|
// Two values are equivalent if they satisfy the following definition:
|
|
|
|
|
// equivalent(v, w):
|
|
|
|
|
// v.op == w.op
|
|
|
|
|
// v.type == w.type
|
|
|
|
|
// v.aux == w.aux
|
2015-06-23 16:44:06 -07:00
|
|
|
// v.auxint == w.auxint
|
2015-03-27 13:41:30 -07:00
|
|
|
// len(v.args) == len(w.args)
|
2015-07-20 18:50:17 -07:00
|
|
|
// v.block == w.block if v.op == OpPhi
|
2015-03-27 13:41:30 -07:00
|
|
|
// equivalent(v.args[i], w.args[i]) for i in 0..len(v.args)-1
|
|
|
|
|
|
|
|
|
|
// The algorithm searches for a partition of f's values into
|
|
|
|
|
// equivalence classes using the above definition.
|
|
|
|
|
// It starts with a coarse partition and iteratively refines it
|
|
|
|
|
// until it reaches a fixed point.
|
|
|
|
|
|
2016-01-27 16:47:23 -08:00
|
|
|
// Make initial coarse partitions by using a subset of the conditions above.
|
|
|
|
|
a := make([]*Value, 0, f.NumValues())
|
2016-02-23 17:52:17 -06:00
|
|
|
auxIDs := auxmap{}
|
2015-03-27 13:41:30 -07:00
|
|
|
for _, b := range f.Blocks {
|
|
|
|
|
for _, v := range b.Values {
|
2016-02-23 17:52:17 -06:00
|
|
|
if auxIDs[v.Aux] == 0 {
|
|
|
|
|
auxIDs[v.Aux] = int32(len(auxIDs)) + 1
|
|
|
|
|
}
|
2016-01-27 16:47:23 -08:00
|
|
|
if v.Type.IsMemory() {
|
|
|
|
|
continue // memory values can never cse
|
2015-07-20 18:50:17 -07:00
|
|
|
}
|
2016-02-22 11:19:15 +01:00
|
|
|
if opcodeTable[v.Op].commutative && len(v.Args) == 2 && v.Args[1].ID < v.Args[0].ID {
|
|
|
|
|
// Order the arguments of binary commutative operations.
|
|
|
|
|
v.Args[0], v.Args[1] = v.Args[1], v.Args[0]
|
|
|
|
|
}
|
2016-01-27 16:47:23 -08:00
|
|
|
a = append(a, v)
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-02-23 17:52:17 -06:00
|
|
|
partition := partitionValues(a, auxIDs)
|
2015-03-27 13:41:30 -07:00
|
|
|
|
|
|
|
|
// map from value id back to eqclass id
|
2016-01-27 16:47:23 -08:00
|
|
|
valueEqClass := make([]ID, f.NumValues())
|
|
|
|
|
for _, b := range f.Blocks {
|
|
|
|
|
for _, v := range b.Values {
|
|
|
|
|
// Use negative equivalence class #s for unique values.
|
|
|
|
|
valueEqClass[v.ID] = -v.ID
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-27 13:41:30 -07:00
|
|
|
for i, e := range partition {
|
[dev.ssa] cmd/compile: enhance command line option processing for SSA
The -d compiler flag can also specify ssa phase and flag,
for example -d=ssa/generic_cse/time,ssa/generic_cse/stats
Spaces in the phase names can be specified with an
underscore. Flags currently parsed (not necessarily
recognized by the phases yet) are:
on, off, mem, time, debug, stats, and test
On, off and time are handled in the harness,
debug, stats, and test are interpreted by the phase itself.
The pass is now attached to the Func being compiled, and a
new method logStats(key, ...value) on *Func to encourage a
semi-standardized format for that output. Output fields
are separated by tabs to ease digestion by awk and
spreadsheets. For example,
if f.pass.stats > 0 {
f.logStat("CSE REWRITES", rewrites)
}
Change-Id: I16db2b5af64c50ca9a47efeb51d961147a903abc
Reviewed-on: https://go-review.googlesource.com/19885
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Todd Neal <todd@tneal.org>
2016-02-25 13:10:51 -05:00
|
|
|
if f.pass.debug > 1 && len(e) > 500 {
|
2016-02-10 19:39:32 -06:00
|
|
|
fmt.Printf("CSE.large partition (%d): ", len(e))
|
|
|
|
|
for j := 0; j < 3; j++ {
|
|
|
|
|
fmt.Printf("%s ", e[j].LongString())
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-27 13:41:30 -07:00
|
|
|
for _, v := range e {
|
2016-01-27 16:47:23 -08:00
|
|
|
valueEqClass[v.ID] = ID(i)
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
[dev.ssa] cmd/compile: enhance command line option processing for SSA
The -d compiler flag can also specify ssa phase and flag,
for example -d=ssa/generic_cse/time,ssa/generic_cse/stats
Spaces in the phase names can be specified with an
underscore. Flags currently parsed (not necessarily
recognized by the phases yet) are:
on, off, mem, time, debug, stats, and test
On, off and time are handled in the harness,
debug, stats, and test are interpreted by the phase itself.
The pass is now attached to the Func being compiled, and a
new method logStats(key, ...value) on *Func to encourage a
semi-standardized format for that output. Output fields
are separated by tabs to ease digestion by awk and
spreadsheets. For example,
if f.pass.stats > 0 {
f.logStat("CSE REWRITES", rewrites)
}
Change-Id: I16db2b5af64c50ca9a47efeb51d961147a903abc
Reviewed-on: https://go-review.googlesource.com/19885
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Todd Neal <todd@tneal.org>
2016-02-25 13:10:51 -05:00
|
|
|
if f.pass.debug > 2 && len(e) > 1 {
|
2016-02-08 12:07:39 -05:00
|
|
|
fmt.Printf("CSE.partition #%d:", i)
|
|
|
|
|
for _, v := range e {
|
|
|
|
|
fmt.Printf(" %s", v.String())
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("\n")
|
|
|
|
|
}
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find an equivalence class where some members of the class have
|
2016-03-01 23:21:55 +00:00
|
|
|
// non-equivalent arguments. Split the equivalence class appropriately.
|
2015-03-27 13:41:30 -07:00
|
|
|
// Repeat until we can't find any more splits.
|
|
|
|
|
for {
|
|
|
|
|
changed := false
|
|
|
|
|
|
2015-07-15 14:38:19 -06:00
|
|
|
// partition can grow in the loop. By not using a range loop here,
|
|
|
|
|
// we process new additions as they arrive, avoiding O(n^2) behavior.
|
|
|
|
|
for i := 0; i < len(partition); i++ {
|
|
|
|
|
e := partition[i]
|
2015-03-27 13:41:30 -07:00
|
|
|
v := e[0]
|
|
|
|
|
// all values in this equiv class that are not equivalent to v get moved
|
2015-07-22 21:21:50 -07:00
|
|
|
// into another equiv class.
|
|
|
|
|
// To avoid allocating while building that equivalence class,
|
2015-09-11 10:28:33 -07:00
|
|
|
// move the values equivalent to v to the beginning of e
|
|
|
|
|
// and other values to the end of e.
|
2015-07-22 21:21:50 -07:00
|
|
|
allvals := e
|
2015-03-27 13:41:30 -07:00
|
|
|
eqloop:
|
|
|
|
|
for j := 1; j < len(e); {
|
|
|
|
|
w := e[j]
|
2016-02-06 20:56:50 -06:00
|
|
|
equivalent := true
|
2015-03-27 13:41:30 -07:00
|
|
|
for i := 0; i < len(v.Args); i++ {
|
2016-02-06 20:56:50 -06:00
|
|
|
if valueEqClass[v.Args[i].ID] != valueEqClass[w.Args[i].ID] {
|
|
|
|
|
equivalent = false
|
|
|
|
|
break
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-02-06 20:56:50 -06:00
|
|
|
if !equivalent || !v.Type.Equal(w.Type) {
|
|
|
|
|
// w is not equivalent to v.
|
|
|
|
|
// move it to the end and shrink e.
|
|
|
|
|
e[j], e[len(e)-1] = e[len(e)-1], e[j]
|
|
|
|
|
e = e[:len(e)-1]
|
|
|
|
|
valueEqClass[w.ID] = ID(len(partition))
|
|
|
|
|
changed = true
|
|
|
|
|
continue eqloop
|
|
|
|
|
}
|
2016-03-01 23:21:55 +00:00
|
|
|
// v and w are equivalent. Keep w in e.
|
2015-03-27 13:41:30 -07:00
|
|
|
j++
|
|
|
|
|
}
|
|
|
|
|
partition[i] = e
|
2015-09-11 10:28:33 -07:00
|
|
|
if len(e) < len(allvals) {
|
|
|
|
|
partition = append(partition, allvals[len(e):])
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !changed {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-13 10:58:38 +02:00
|
|
|
// Dominator tree (f.sdom) is computed by the generic domtree pass.
|
2015-03-27 13:41:30 -07:00
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// Compute substitutions we would like to do. We substitute v for w
|
2015-03-27 13:41:30 -07:00
|
|
|
// if v and w are in the same equivalence class and v dominates w.
|
|
|
|
|
rewrite := make([]*Value, f.NumValues())
|
|
|
|
|
for _, e := range partition {
|
2016-04-13 08:51:46 -04:00
|
|
|
sort.Sort(sortbyentry{e, f.sdom})
|
2016-04-14 19:09:57 -04:00
|
|
|
for i := 0; i < len(e)-1; i++ {
|
2016-04-13 08:51:46 -04:00
|
|
|
// e is sorted by entry value so maximal dominant element should be
|
|
|
|
|
// found first in the slice
|
2016-04-14 19:09:57 -04:00
|
|
|
v := e[i]
|
|
|
|
|
if v == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e[i] = nil
|
2015-03-27 13:41:30 -07:00
|
|
|
// Replace all elements of e which v dominates
|
2016-04-14 19:09:57 -04:00
|
|
|
for j := i + 1; j < len(e); j++ {
|
|
|
|
|
w := e[j]
|
|
|
|
|
if w == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2016-04-13 08:51:46 -04:00
|
|
|
if f.sdom.isAncestorEq(v.Block, w.Block) {
|
2015-03-27 13:41:30 -07:00
|
|
|
rewrite[w.ID] = v
|
2016-04-14 19:09:57 -04:00
|
|
|
e[j] = nil
|
2015-03-27 13:41:30 -07:00
|
|
|
} else {
|
2016-04-14 19:09:57 -04:00
|
|
|
// since the blocks are assorted in ascending order by entry number
|
|
|
|
|
// once we know that we don't dominate a block we can't dominate any
|
|
|
|
|
// 'later' block
|
|
|
|
|
break
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
[dev.ssa] cmd/compile: enhance command line option processing for SSA
The -d compiler flag can also specify ssa phase and flag,
for example -d=ssa/generic_cse/time,ssa/generic_cse/stats
Spaces in the phase names can be specified with an
underscore. Flags currently parsed (not necessarily
recognized by the phases yet) are:
on, off, mem, time, debug, stats, and test
On, off and time are handled in the harness,
debug, stats, and test are interpreted by the phase itself.
The pass is now attached to the Func being compiled, and a
new method logStats(key, ...value) on *Func to encourage a
semi-standardized format for that output. Output fields
are separated by tabs to ease digestion by awk and
spreadsheets. For example,
if f.pass.stats > 0 {
f.logStat("CSE REWRITES", rewrites)
}
Change-Id: I16db2b5af64c50ca9a47efeb51d961147a903abc
Reviewed-on: https://go-review.googlesource.com/19885
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Todd Neal <todd@tneal.org>
2016-02-25 13:10:51 -05:00
|
|
|
rewrites := int64(0)
|
2016-02-11 15:09:43 -05:00
|
|
|
|
2015-03-27 13:41:30 -07:00
|
|
|
// 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)
|
2016-02-11 15:09:43 -05:00
|
|
|
rewrites++
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-11 14:59:01 -08:00
|
|
|
if v := b.Control; v != nil {
|
|
|
|
|
if x := rewrite[v.ID]; x != nil {
|
|
|
|
|
if v.Op == OpNilCheck {
|
|
|
|
|
// nilcheck pass will remove the nil checks and log
|
|
|
|
|
// them appropriately, so don't mess with them here.
|
|
|
|
|
continue
|
|
|
|
|
}
|
2016-03-15 20:45:50 -07:00
|
|
|
b.SetControl(x)
|
2015-12-11 14:59:01 -08:00
|
|
|
}
|
|
|
|
|
}
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
[dev.ssa] cmd/compile: enhance command line option processing for SSA
The -d compiler flag can also specify ssa phase and flag,
for example -d=ssa/generic_cse/time,ssa/generic_cse/stats
Spaces in the phase names can be specified with an
underscore. Flags currently parsed (not necessarily
recognized by the phases yet) are:
on, off, mem, time, debug, stats, and test
On, off and time are handled in the harness,
debug, stats, and test are interpreted by the phase itself.
The pass is now attached to the Func being compiled, and a
new method logStats(key, ...value) on *Func to encourage a
semi-standardized format for that output. Output fields
are separated by tabs to ease digestion by awk and
spreadsheets. For example,
if f.pass.stats > 0 {
f.logStat("CSE REWRITES", rewrites)
}
Change-Id: I16db2b5af64c50ca9a47efeb51d961147a903abc
Reviewed-on: https://go-review.googlesource.com/19885
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Todd Neal <todd@tneal.org>
2016-02-25 13:10:51 -05:00
|
|
|
if f.pass.stats > 0 {
|
|
|
|
|
f.logStat("CSE REWRITES", rewrites)
|
2016-02-11 15:09:43 -05:00
|
|
|
}
|
2015-03-27 13:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// An eqclass approximates an equivalence class. During the
|
2015-03-27 13:41:30 -07:00
|
|
|
// algorithm it may represent the union of several of the
|
|
|
|
|
// final equivalence classes.
|
|
|
|
|
type eqclass []*Value
|
|
|
|
|
|
2016-01-27 16:47:23 -08:00
|
|
|
// partitionValues partitions the values into equivalence classes
|
|
|
|
|
// based on having all the following features match:
|
|
|
|
|
// - opcode
|
|
|
|
|
// - type
|
|
|
|
|
// - auxint
|
|
|
|
|
// - aux
|
|
|
|
|
// - nargs
|
|
|
|
|
// - block # if a phi op
|
2016-02-08 12:07:39 -05:00
|
|
|
// - first two arg's opcodes and auxint
|
|
|
|
|
// - NOT first two arg's aux; that can break CSE.
|
2016-01-27 16:47:23 -08:00
|
|
|
// partitionValues returns a list of equivalence classes, each
|
2016-03-01 23:21:55 +00:00
|
|
|
// being a sorted by ID list of *Values. The eqclass slices are
|
2016-01-27 16:47:23 -08:00
|
|
|
// backed by the same storage as the input slice.
|
|
|
|
|
// Equivalence classes of size 1 are ignored.
|
2016-02-23 17:52:17 -06:00
|
|
|
func partitionValues(a []*Value, auxIDs auxmap) []eqclass {
|
2016-02-11 15:09:43 -05:00
|
|
|
sort.Sort(sortvalues{a, auxIDs})
|
2016-01-27 16:47:23 -08:00
|
|
|
|
|
|
|
|
var partition []eqclass
|
|
|
|
|
for len(a) > 0 {
|
|
|
|
|
v := a[0]
|
|
|
|
|
j := 1
|
|
|
|
|
for ; j < len(a); j++ {
|
|
|
|
|
w := a[j]
|
2016-02-23 17:52:17 -06:00
|
|
|
if cmpVal(v, w, auxIDs, cmpDepth) != CMPeq {
|
2016-01-27 16:47:23 -08:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if j > 1 {
|
|
|
|
|
partition = append(partition, a[:j])
|
|
|
|
|
}
|
|
|
|
|
a = a[j:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return partition
|
|
|
|
|
}
|
2016-02-23 17:52:17 -06:00
|
|
|
func lt2Cmp(isLt bool) Cmp {
|
|
|
|
|
if isLt {
|
|
|
|
|
return CMPlt
|
|
|
|
|
}
|
|
|
|
|
return CMPgt
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
|
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
type auxmap map[interface{}]int32
|
|
|
|
|
|
|
|
|
|
func cmpVal(v, w *Value, auxIDs auxmap, depth int) Cmp {
|
|
|
|
|
// Try to order these comparison by cost (cheaper first)
|
2016-01-27 16:47:23 -08:00
|
|
|
if v.Op != w.Op {
|
2016-02-23 17:52:17 -06:00
|
|
|
return lt2Cmp(v.Op < w.Op)
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
|
|
|
|
if v.AuxInt != w.AuxInt {
|
2016-02-23 17:52:17 -06:00
|
|
|
return lt2Cmp(v.AuxInt < w.AuxInt)
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
|
|
|
|
if len(v.Args) != len(w.Args) {
|
2016-02-23 17:52:17 -06:00
|
|
|
return lt2Cmp(len(v.Args) < len(w.Args))
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
2016-02-23 17:52:17 -06:00
|
|
|
if v.Op == OpPhi && v.Block != w.Block {
|
|
|
|
|
return lt2Cmp(v.Block.ID < w.Block.ID)
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
2016-02-06 20:56:50 -06:00
|
|
|
|
2016-04-12 17:12:26 -07:00
|
|
|
switch v.Op {
|
|
|
|
|
case OpStaticCall, OpAMD64CALLstatic, OpARMCALLstatic:
|
|
|
|
|
sym := v.Aux.(GCSym)
|
|
|
|
|
if sym.IsRuntimeCall("newobject") {
|
|
|
|
|
return lt2Cmp(v.ID < w.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
if tc := v.Type.Compare(w.Type); tc != CMPeq {
|
|
|
|
|
return tc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v.Aux != w.Aux {
|
|
|
|
|
if v.Aux == nil {
|
|
|
|
|
return CMPlt
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
2016-02-23 17:52:17 -06:00
|
|
|
if w.Aux == nil {
|
|
|
|
|
return CMPgt
|
|
|
|
|
}
|
|
|
|
|
return lt2Cmp(auxIDs[v.Aux] < auxIDs[w.Aux])
|
|
|
|
|
}
|
2016-02-06 20:56:50 -06:00
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
if depth > 0 {
|
|
|
|
|
for i := range v.Args {
|
|
|
|
|
if v.Args[i] == w.Args[i] {
|
|
|
|
|
// skip comparing equal args
|
|
|
|
|
continue
|
2016-02-06 20:56:50 -06:00
|
|
|
}
|
2016-02-23 17:52:17 -06:00
|
|
|
if ac := cmpVal(v.Args[i], w.Args[i], auxIDs, depth-1); ac != CMPeq {
|
|
|
|
|
return ac
|
2016-01-27 16:47:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
return CMPeq
|
|
|
|
|
}
|
2016-01-27 16:47:23 -08:00
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
// Sort values to make the initial partition.
|
|
|
|
|
type sortvalues struct {
|
|
|
|
|
a []*Value // array of values
|
|
|
|
|
auxIDs auxmap // aux -> aux ID map
|
|
|
|
|
}
|
2016-01-27 16:47:23 -08:00
|
|
|
|
2016-02-23 17:52:17 -06:00
|
|
|
func (sv sortvalues) Len() int { return len(sv.a) }
|
|
|
|
|
func (sv sortvalues) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
|
|
|
|
|
func (sv sortvalues) Less(i, j int) bool {
|
|
|
|
|
v := sv.a[i]
|
|
|
|
|
w := sv.a[j]
|
|
|
|
|
if cmp := cmpVal(v, w, sv.auxIDs, cmpDepth); cmp != CMPeq {
|
|
|
|
|
return cmp == CMPlt
|
|
|
|
|
}
|
2016-01-27 16:47:23 -08:00
|
|
|
|
|
|
|
|
// Sort by value ID last to keep the sort result deterministic.
|
|
|
|
|
return v.ID < w.ID
|
|
|
|
|
}
|
2016-04-13 08:51:46 -04:00
|
|
|
|
|
|
|
|
type sortbyentry struct {
|
|
|
|
|
a []*Value // array of values
|
|
|
|
|
sdom sparseTree
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (sv sortbyentry) Len() int { return len(sv.a) }
|
|
|
|
|
func (sv sortbyentry) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
|
|
|
|
|
func (sv sortbyentry) Less(i, j int) bool {
|
|
|
|
|
v := sv.a[i]
|
|
|
|
|
w := sv.a[j]
|
|
|
|
|
return sv.sdom.maxdomorder(v.Block) < sv.sdom.maxdomorder(w.Block)
|
|
|
|
|
}
|