1
0
mirror of https://github.com/golang/go synced 2024-10-05 01:31:22 -06:00

cmd/compile: share dominator tree among many passes

These passes do not modify the dominator tree too much.

% benchstat old.txt new.txt
name       old time/op     new time/op     delta
Template       335ms ± 3%      325ms ± 8%    ~             (p=0.074 n=8+9)
GoTypes        1.05s ± 1%      1.05s ± 3%    ~            (p=0.095 n=9+10)
Compiler       5.37s ± 4%      5.29s ± 1%  -1.42%         (p=0.022 n=9+10)
MakeBash       34.9s ± 3%      34.4s ± 2%    ~            (p=0.095 n=9+10)

name       old alloc/op    new alloc/op    delta
Template      55.4MB ± 0%     54.9MB ± 0%  -0.81%        (p=0.000 n=10+10)
GoTypes        179MB ± 0%      178MB ± 0%  -0.89%        (p=0.000 n=10+10)
Compiler       807MB ± 0%      798MB ± 0%  -1.10%        (p=0.000 n=10+10)

name       old allocs/op   new allocs/op   delta
Template        498k ± 0%       496k ± 0%  -0.29%          (p=0.000 n=9+9)
GoTypes        1.42M ± 0%      1.41M ± 0%  -0.24%        (p=0.000 n=10+10)
Compiler       5.61M ± 0%      5.60M ± 0%  -0.12%        (p=0.000 n=10+10)

Change-Id: I4cd20cfba3f132ebf371e16046ab14d7e42799ec
Reviewed-on: https://go-review.googlesource.com/21806
Run-TryBot: Alexandru Moșoi <alexandru@mosoi.ro>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Alexandru Moșoi 2016-04-11 21:51:29 +02:00 committed by Alexandru Moșoi
parent a223ccae38
commit 9743e4b031
7 changed files with 36 additions and 17 deletions

View File

@ -234,6 +234,7 @@ var passes = [...]pass{
{name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values
{name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt {name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt
{name: "generic cse", fn: cse}, {name: "generic cse", fn: cse},
{name: "generic domtree", fn: domTree},
{name: "phiopt", fn: phiopt}, {name: "phiopt", fn: phiopt},
{name: "nilcheckelim", fn: nilcheckelim}, {name: "nilcheckelim", fn: nilcheckelim},
{name: "prove", fn: prove}, {name: "prove", fn: prove},
@ -288,6 +289,10 @@ var passOrder = [...]constraint{
{"opt", "nilcheckelim"}, {"opt", "nilcheckelim"},
// tighten should happen before lowering to avoid splitting naturally paired instructions such as CMP/SET // tighten should happen before lowering to avoid splitting naturally paired instructions such as CMP/SET
{"tighten", "lower"}, {"tighten", "lower"},
// nilcheckelim, prove and loopbce share idom.
{"generic domtree", "nilcheckelim"},
{"generic domtree", "prove"},
{"generic domtree", "loopbce"},
// tighten will be most effective when as many values have been removed as possible // tighten will be most effective when as many values have been removed as possible
{"generic deadcode", "tighten"}, {"generic deadcode", "tighten"},
{"generic cse", "tighten"}, {"generic cse", "tighten"},

View File

@ -364,3 +364,9 @@ func intersect(b, c *Block, postnum []int, idom []*Block) *Block {
} }
return b return b
} }
// build immediate dominators.
func domTree(f *Func) {
f.idom = dominators(f)
f.sdom = newSparseTree(f, f.idom)
}

View File

@ -36,6 +36,9 @@ type Func struct {
freeValues *Value // free Values linked by argstorage[0]. All other fields except ID are 0/nil. freeValues *Value // free Values linked by argstorage[0]. All other fields except ID are 0/nil.
freeBlocks *Block // free Blocks linked by succstorage[0]. All other fields except ID are 0/nil. freeBlocks *Block // free Blocks linked by succstorage[0]. All other fields except ID are 0/nil.
idom []*Block // precomputed immediate dominators
sdom sparseTree // precomputed dominator tree
constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type
} }

View File

@ -31,7 +31,7 @@ type indVar struct {
// //
// //
// TODO: handle 32 bit operations // TODO: handle 32 bit operations
func findIndVar(f *Func, sdom sparseTree) []indVar { func findIndVar(f *Func) []indVar {
var iv []indVar var iv []indVar
nextb: nextb:
@ -110,7 +110,7 @@ nextb:
// Second condition: b.Succs[entry] dominates nxt so that // Second condition: b.Succs[entry] dominates nxt so that
// nxt is computed when inc < max, meaning nxt <= max. // nxt is computed when inc < max, meaning nxt <= max.
if !sdom.isAncestorEq(b.Succs[entry], nxt.Block) { if !f.sdom.isAncestorEq(b.Succs[entry], nxt.Block) {
// inc+ind can only be reached through the branch that enters the loop. // inc+ind can only be reached through the branch that enters the loop.
continue continue
} }
@ -160,20 +160,18 @@ nextb:
// loopbce performs loop based bounds check elimination. // loopbce performs loop based bounds check elimination.
func loopbce(f *Func) { func loopbce(f *Func) {
idom := dominators(f) ivList := findIndVar(f)
sdom := newSparseTree(f, idom)
ivList := findIndVar(f, sdom)
m := make(map[*Value]indVar) m := make(map[*Value]indVar)
for _, iv := range ivList { for _, iv := range ivList {
m[iv.ind] = iv m[iv.ind] = iv
} }
removeBoundsChecks(f, sdom, m) removeBoundsChecks(f, m)
} }
// removesBoundsChecks remove IsInBounds and IsSliceInBounds based on the induction variables. // removesBoundsChecks remove IsInBounds and IsSliceInBounds based on the induction variables.
func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) { func removeBoundsChecks(f *Func, m map[*Value]indVar) {
for _, b := range f.Blocks { for _, b := range f.Blocks {
if b.Kind != BlockIf { if b.Kind != BlockIf {
continue continue
@ -202,7 +200,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) {
goto skip1 goto skip1
} }
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) {
if v.Args[1] == iv.max { if v.Args[1] == iv.max {
if f.pass.debug > 0 { if f.pass.debug > 0 {
f.Config.Warnl(b.Line, "Found redundant %s", v.Op) f.Config.Warnl(b.Line, "Found redundant %s", v.Op)
@ -229,7 +227,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) {
goto skip2 goto skip2
} }
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) { if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isNonNegative(iv.min) {
if v.Args[1].Op == OpSliceCap && iv.max.Op == OpSliceLen && v.Args[1].Args[0] == iv.max.Args[0] { if v.Args[1].Op == OpSliceCap && iv.max.Op == OpSliceLen && v.Args[1].Args[0] == iv.max.Args[0] {
if f.pass.debug > 0 { if f.pass.debug > 0 {
f.Config.Warnl(b.Line, "Found redundant %s (len promoted to cap)", v.Op) f.Config.Warnl(b.Line, "Found redundant %s (len promoted to cap)", v.Op)
@ -250,7 +248,7 @@ func removeBoundsChecks(f *Func, sdom sparseTree, m map[*Value]indVar) {
} }
// ind + add >= 0 <-> min + add >= 0 <-> min >= -add // ind + add >= 0 <-> min + add >= 0 <-> min >= -add
if iv, has := m[ind]; has && sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) { if iv, has := m[ind]; has && f.sdom.isAncestorEq(iv.entry, b) && isGreaterOrEqualThan(iv.min, -add) {
if !v.Args[1].isGenericIntConst() || !iv.max.isGenericIntConst() { if !v.Args[1].isGenericIntConst() || !iv.max.isGenericIntConst() {
goto skip3 goto skip3
} }

View File

@ -11,7 +11,7 @@ func nilcheckelim(f *Func) {
// A nil check is redundant if the same nil check was successful in a // A nil check is redundant if the same nil check was successful in a
// dominating block. The efficacy of this pass depends heavily on the // dominating block. The efficacy of this pass depends heavily on the
// efficacy of the cse pass. // efficacy of the cse pass.
idom := dominators(f) idom := f.idom
domTree := make([][]*Block, f.NumBlocks()) domTree := make([][]*Block, f.NumBlocks())
// Create a block ID -> [dominees] mapping // Create a block ID -> [dominees] mapping

View File

@ -49,6 +49,7 @@ func benchmarkNilCheckDeep(b *testing.B, depth int) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
} }
} }
@ -83,6 +84,7 @@ func TestNilcheckSimple(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -120,6 +122,7 @@ func TestNilcheckDomOrder(t *testing.T) {
Goto("exit"))) Goto("exit")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -153,6 +156,7 @@ func TestNilcheckAddr(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -187,6 +191,7 @@ func TestNilcheckAddPtr(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -231,6 +236,7 @@ func TestNilcheckPhi(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -272,6 +278,7 @@ func TestNilcheckKeepRemove(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -319,6 +326,7 @@ func TestNilcheckInFalseBranch(t *testing.T) {
Exit("mem"))) Exit("mem")))
CheckFunc(fun.f) CheckFunc(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -370,6 +378,7 @@ func TestNilcheckUser(t *testing.T) {
CheckFunc(fun.f) CheckFunc(fun.f)
// we need the opt here to rewrite the user nilcheck // we need the opt here to rewrite the user nilcheck
opt(fun.f) opt(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check
@ -414,6 +423,7 @@ func TestNilcheckBug(t *testing.T) {
CheckFunc(fun.f) CheckFunc(fun.f)
// we need the opt here to rewrite the user nilcheck // we need the opt here to rewrite the user nilcheck
opt(fun.f) opt(fun.f)
domTree(fun.f)
nilcheckelim(fun.f) nilcheckelim(fun.f)
// clean up the removed nil check // clean up the removed nil check

View File

@ -445,9 +445,6 @@ var (
// else branch of the first comparison is executed, we already know that i < len(a). // else branch of the first comparison is executed, we already know that i < len(a).
// The code for the second panic can be removed. // The code for the second panic can be removed.
func prove(f *Func) { func prove(f *Func) {
idom := dominators(f)
sdom := newSparseTree(f, idom)
// current node state // current node state
type walkState int type walkState int
const ( const (
@ -471,8 +468,8 @@ func prove(f *Func) {
for len(work) > 0 { for len(work) > 0 {
node := work[len(work)-1] node := work[len(work)-1]
work = work[:len(work)-1] work = work[:len(work)-1]
parent := idom[node.block.ID] parent := f.idom[node.block.ID]
branch := getBranch(sdom, parent, node.block) branch := getBranch(f.sdom, parent, node.block)
switch node.state { switch node.state {
case descend: case descend:
@ -491,7 +488,7 @@ func prove(f *Func) {
block: node.block, block: node.block,
state: simplify, state: simplify,
}) })
for s := sdom.Child(node.block); s != nil; s = sdom.Sibling(s) { for s := f.sdom.Child(node.block); s != nil; s = f.sdom.Sibling(s) {
work = append(work, bp{ work = append(work, bp{
block: s, block: s,
state: descend, state: descend,