From b5016cbbbd237e7530fa061e87fa22396904901b Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 5 Dec 2013 09:50:18 -0500 Subject: [PATCH] go.tools/ssa: expose dominator tree of control-flow graph in API. New APIs: (*BasicBlock).{Idom,Dominees,Dominates} (*Function).DomPreorder Messy but systematic refactoring of domNode: - renamed "domInfo". - embedded directly in BasicBlock, not as pointer. Block field removed. - Level field removed; was unused. - Working state of LT algorithm now in its own type. {semi,parent,ancestor} fields moved into it. - remaining fields made private; accessors added. - use 32-bit ints for pre/postorder numbers. - allocate LT working space (5 copies of fn.Blocks) contiguously. dom.go is simpler but somewhat more verbose. Also: - we always build the domtree now---yet memory usage is down 5%. - number the Recover block too. - add sanity check for DomPreorder. R=gri CC=golang-dev https://golang.org/cl/37230043 --- ssa/dom.go | 232 ++++++++++++++++++++++++++++++---------------------- ssa/func.go | 3 +- ssa/lift.go | 38 ++++----- ssa/ssa.go | 13 ++- 4 files changed, 162 insertions(+), 124 deletions(-) diff --git a/ssa/dom.go b/ssa/dom.go index 66501892..138b8d6b 100644 --- a/ssa/dom.go +++ b/ssa/dom.go @@ -22,57 +22,90 @@ import ( "io" "math/big" "os" + "sort" ) -// domNode represents a node in the dominator tree. +// Idom returns the block that immediately dominates b: +// its parent in the dominator tree, if any. +// Neither the entry node (b.Index==0) nor recover node +// (b==b.Parent().Recover()) have a parent. // -// TODO(adonovan): export this, when ready. -type domNode struct { - Block *BasicBlock // the basic block; n.Block.dom == n - Idom *domNode // immediate dominator (parent in dominator tree) - Children []*domNode // nodes dominated by this one - Level int // level number of node within tree; zero for root - Pre, Post int // pre- and post-order numbering within dominator tree +func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom } - // Working state for Lengauer-Tarjan algorithm - // (during which Pre is repurposed for CFG DFS preorder number). - // TODO(adonovan): opt: measure allocating these as temps. - semi *domNode // semidominator - parent *domNode // parent in DFS traversal of CFG - ancestor *domNode // ancestor with least sdom +// Dominees returns the list of blocks that b immediately dominates: +// its children in the dominator tree. +// +func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children } + +// Dominates reports whether b dominates c. +func (b *BasicBlock) Dominates(c *BasicBlock) bool { + return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post } -// ltDfs implements the depth-first search part of the LT algorithm. -func ltDfs(v *domNode, i int, preorder []*domNode) int { +type byDomPreorder []*BasicBlock + +func (a byDomPreorder) Len() int { return len(a) } +func (a byDomPreorder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byDomPreorder) Less(i, j int) bool { return a[i].dom.pre < a[j].dom.pre } + +// DomPreorder returns a new slice containing the blocks of f in +// dominator tree preorder. +// +func (f *Function) DomPreorder() []*BasicBlock { + n := len(f.Blocks) + order := make(byDomPreorder, n, n) + copy(order, f.Blocks) + sort.Sort(order) + return order +} + +// domInfo contains a BasicBlock's dominance information. +type domInfo struct { + idom *BasicBlock // immediate dominator (parent in domtree) + children []*BasicBlock // nodes immediately dominated by this one + pre, post int32 // pre- and post-order numbering within domtree +} + +// ltState holds the working state for Lengauer-Tarjan algorithm +// (during which domInfo.pre is repurposed for CFG DFS preorder number). +type ltState struct { + // Each slice is indexed by b.Index. + sdom []*BasicBlock // b's semidominator + parent []*BasicBlock // b's parent in DFS traversal of CFG + ancestor []*BasicBlock // b's ancestor with least sdom +} + +// dfs implements the depth-first search part of the LT algorithm. +func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 { preorder[i] = v - v.Pre = i // For now: DFS preorder of spanning tree of CFG + v.dom.pre = i // For now: DFS preorder of spanning tree of CFG i++ - v.semi = v - v.ancestor = nil - for _, succ := range v.Block.Succs { - if w := succ.dom; w.semi == nil { - w.parent = v - i = ltDfs(w, i, preorder) + lt.sdom[v.Index] = v + lt.link(nil, v) + for _, w := range v.Succs { + if lt.sdom[w.Index] == nil { + lt.parent[w.Index] = v + i = lt.dfs(w, i, preorder) } } return i } -// ltEval implements the EVAL part of the LT algorithm. -func ltEval(v *domNode) *domNode { +// eval implements the EVAL part of the LT algorithm. +func (lt *ltState) eval(v *BasicBlock) *BasicBlock { // TODO(adonovan): opt: do path compression per simple LT. u := v - for ; v.ancestor != nil; v = v.ancestor { - if v.semi.Pre < u.semi.Pre { + for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] { + if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre { u = v } } return u } -// ltLink implements the LINK part of the LT algorithm. -func ltLink(v, w *domNode) { - w.ancestor = v +// link implements the LINK part of the LT algorithm. +func (lt *ltState) link(v, w *BasicBlock) { + lt.ancestor[w.Index] = v } // buildDomTree computes the dominator tree of f using the LT algorithm. @@ -82,90 +115,89 @@ func buildDomTree(f *Function) { // The step numbers refer to the original LT paper; the // reodering is due to Georgiadis. - // Initialize domNode nodes. + // Clear any previous domInfo. for _, b := range f.Blocks { - dom := b.dom - if dom == nil { - dom = &domNode{Block: b} - b.dom = dom - } else { - dom.Block = b // reuse - } + b.dom = domInfo{} + } + + n := len(f.Blocks) + // Allocate space for 5 contiguous [n]*BasicBlock arrays: + // sdom, parent, ancestor, preorder, buckets. + space := make([]*BasicBlock, 5*n, 5*n) + lt := ltState{ + sdom: space[0:n], + parent: space[n : 2*n], + ancestor: space[2*n : 3*n], } // Step 1. Number vertices by depth-first preorder. - n := len(f.Blocks) - preorder := make([]*domNode, n) - root := f.Blocks[0].dom - prenum := ltDfs(root, 0, preorder) - var recover *domNode - if f.Recover != nil { - recover = f.Recover.dom - ltDfs(recover, prenum, preorder) + preorder := space[3*n : 4*n] + root := f.Blocks[0] + prenum := lt.dfs(root, 0, preorder) + recover := f.Recover + if recover != nil { + lt.dfs(recover, prenum, preorder) } - buckets := make([]*domNode, n) + buckets := space[4*n : 5*n] copy(buckets, preorder) // In reverse preorder... - for i := n - 1; i > 0; i-- { + for i := int32(n) - 1; i > 0; i-- { w := preorder[i] // Step 3. Implicitly define the immediate dominator of each node. - for v := buckets[i]; v != w; v = buckets[v.Pre] { - u := ltEval(v) - if u.semi.Pre < i { - v.Idom = u + for v := buckets[i]; v != w; v = buckets[v.dom.pre] { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < i { + v.dom.idom = u } else { - v.Idom = w + v.dom.idom = w } } // Step 2. Compute the semidominators of all nodes. - w.semi = w.parent - for _, pred := range w.Block.Preds { - v := pred.dom - u := ltEval(v) - if u.semi.Pre < w.semi.Pre { - w.semi = u.semi + lt.sdom[w.Index] = lt.parent[w.Index] + for _, v := range w.Preds { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre { + lt.sdom[w.Index] = lt.sdom[u.Index] } } - ltLink(w.parent, w) + lt.link(lt.parent[w.Index], w) - if w.parent == w.semi { - w.Idom = w.parent + if lt.parent[w.Index] == lt.sdom[w.Index] { + w.dom.idom = lt.parent[w.Index] } else { - buckets[i] = buckets[w.semi.Pre] - buckets[w.semi.Pre] = w + buckets[i] = buckets[lt.sdom[w.Index].dom.pre] + buckets[lt.sdom[w.Index].dom.pre] = w } } // The final 'Step 3' is now outside the loop. - for v := buckets[0]; v != root; v = buckets[v.Pre] { - v.Idom = root + for v := buckets[0]; v != root; v = buckets[v.dom.pre] { + v.dom.idom = root } // Step 4. Explicitly define the immediate dominator of each // node, in preorder. for _, w := range preorder[1:] { if w == root || w == recover { - w.Idom = nil + w.dom.idom = nil } else { - if w.Idom != w.semi { - w.Idom = w.Idom.Idom + if w.dom.idom != lt.sdom[w.Index] { + w.dom.idom = w.dom.idom.dom.idom } // Calculate Children relation as inverse of Idom. - w.Idom.Children = append(w.Idom.Children, w) + w.dom.idom.dom.children = append(w.dom.idom.dom.children, w) } - - // Clear working state. - w.semi = nil - w.parent = nil - w.ancestor = nil } - numberDomTree(root, 0, 0, 0) + pre, post := numberDomTree(root, 0, 0) + if recover != nil { + numberDomTree(recover, pre, post) + } // printDomTreeDot(os.Stderr, f) // debugging // printDomTreeText(os.Stderr, root, 0) // debugging @@ -177,29 +209,19 @@ func buildDomTree(f *Function) { // numberDomTree sets the pre- and post-order numbers of a depth-first // traversal of the dominator tree rooted at v. These are used to -// answer dominance queries in constant time. Also, it sets the level -// numbers (zero for the root) used for frontier computation. +// answer dominance queries in constant time. // -func numberDomTree(v *domNode, pre, post, level int) (int, int) { - v.Level = level - level++ - v.Pre = pre +func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) { + v.dom.pre = pre pre++ - for _, child := range v.Children { - pre, post = numberDomTree(child, pre, post, level) + for _, child := range v.dom.children { + pre, post = numberDomTree(child, pre, post) } - v.Post = post + v.dom.post = post post++ return pre, post } -// dominates returns true if b dominates c. -// Requires that dominance information is up-to-date. -// -func dominates(b, c *BasicBlock) bool { - return b.dom.Pre <= c.dom.Pre && c.dom.Post <= b.dom.Post -} - // Testing utilities ---------------------------------------- // sanityCheckDomTree checks the correctness of the dominator tree @@ -223,7 +245,7 @@ func sanityCheckDomTree(f *Function) { // Initialization. for i, b := range f.Blocks { if i == 0 || b == f.Recover { - // The root is dominated only by itself. + // A root is dominated only by itself. D[i].SetBit(&D[0], 0, 1) } else { // All other blocks are (initially) dominated @@ -262,7 +284,7 @@ func sanityCheckDomTree(f *Function) { if c == f.Recover { continue } - actual := dominates(b, c) + actual := b.Dominates(c) expected := D[j].Bit(i) == 1 if actual != expected { fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected) @@ -270,17 +292,27 @@ func sanityCheckDomTree(f *Function) { } } } + + preorder := f.DomPreorder() + for _, b := range f.Blocks { + if got := preorder[b.dom.pre]; got != b { + fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b) + ok = false + } + } + if !ok { panic("sanityCheckDomTree failed for " + f.String()) } + } // Printing functions ---------------------------------------- // printDomTree prints the dominator tree as text, using indentation. -func printDomTreeText(w io.Writer, v *domNode, indent int) { - fmt.Fprintf(w, "%*s%s\n", 4*indent, "", v.Block) - for _, child := range v.Children { +func printDomTreeText(w io.Writer, v *BasicBlock, indent int) { + fmt.Fprintf(w, "%*s%s\n", 4*indent, "", v) + for _, child := range v.dom.children { printDomTreeText(w, child, indent+1) } } @@ -292,17 +324,17 @@ func printDomTreeDot(w io.Writer, f *Function) { fmt.Fprintln(w, "digraph domtree {") for i, b := range f.Blocks { v := b.dom - fmt.Fprintf(w, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.Pre, b, v.Pre, v.Post) + fmt.Fprintf(w, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post) // TODO(adonovan): improve appearance of edges // belonging to both dominator tree and CFG. // Dominator tree edge. if i != 0 { - fmt.Fprintf(w, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.Idom.Pre, v.Pre) + fmt.Fprintf(w, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre) } // CFG edges. for _, pred := range b.Preds { - fmt.Fprintf(w, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.Pre, v.Pre) + fmt.Fprintf(w, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre) } } fmt.Fprintln(w, "}") diff --git a/ssa/func.go b/ssa/func.go index b2059ef2..be26163b 100644 --- a/ssa/func.go +++ b/ssa/func.go @@ -324,11 +324,12 @@ func (f *Function) finishBody() { buildReferrers(f) + buildDomTree(f) + if f.Prog.mode&NaiveForm == 0 { // For debugging pre-state of lifting pass: // numberRegisters(f) // f.DumpTo(os.Stderr) - lift(f) } diff --git a/ssa/lift.go b/ssa/lift.go index 3cf1e041..61198645 100644 --- a/ssa/lift.go +++ b/ssa/lift.go @@ -68,9 +68,9 @@ const debugLifting = false // type domFrontier [][]*BasicBlock -func (df domFrontier) add(u, v *domNode) { - p := &df[u.Block.Index] - *p = append(*p, v.Block) +func (df domFrontier) add(u, v *BasicBlock) { + p := &df[u.Index] + *p = append(*p, v) } // build builds the dominance frontier df for the dominator (sub)tree @@ -79,21 +79,21 @@ func (df domFrontier) add(u, v *domNode) { // TODO(adonovan): opt: consider Berlin approach, computing pruned SSA // by pruning the entire IDF computation, rather than merely pruning // the DF -> IDF step. -func (df domFrontier) build(u *domNode) { +func (df domFrontier) build(u *BasicBlock) { // Encounter each node u in postorder of dom tree. - for _, child := range u.Children { + for _, child := range u.dom.children { df.build(child) } - for _, vb := range u.Block.Succs { - if v := vb.dom; v.Idom != u { - df.add(u, v) + for _, vb := range u.Succs { + if v := vb.dom; v.idom != u { + df.add(u, vb) } } - for _, w := range u.Children { - for _, vb := range df[w.Block.Index] { + for _, w := range u.dom.children { + for _, vb := range df[w.Index] { // TODO(adonovan): opt: use word-parallel bitwise union. - if v := vb.dom; v.Idom != u { - df.add(u, v) + if v := vb.dom; v.idom != u { + df.add(u, vb) } } } @@ -101,9 +101,9 @@ func (df domFrontier) build(u *domNode) { func buildDomFrontier(fn *Function) domFrontier { df := make(domFrontier, len(fn.Blocks)) - df.build(fn.Blocks[0].dom) + df.build(fn.Blocks[0]) if fn.Recover != nil { - df.build(fn.Recover.dom) + df.build(fn.Recover) } return df } @@ -115,11 +115,12 @@ func buildDomFrontier(fn *Function) domFrontier { // Preconditions: // - fn has no dead blocks (blockopt has run). // - Def/use info (Operands and Referrers) is up-to-date. +// - The dominator tree is up-to-date. // func lift(fn *Function) { // TODO(adonovan): opt: lots of little optimizations may be // worthwhile here, especially if they cause us to avoid - // buildDomTree. For example: + // buildDomFrontier. For example: // // - Alloc never loaded? Eliminate. // - Alloc never stored? Replace all loads with a zero constant. @@ -135,9 +136,6 @@ func lift(fn *Function) { // Unclear. // // But we will start with the simplest correct code. - - buildDomTree(fn) - df := buildDomFrontier(fn) if debugLifting { @@ -562,10 +560,10 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) { // Continue depth-first recursion over domtree, pushing a // fresh copy of the renaming map for each subtree. - for _, v := range u.dom.Children { + for _, v := range u.dom.children { // TODO(adonovan): opt: avoid copy on final iteration; use destructive update. r := make([]Value, len(renaming)) copy(r, renaming) - rename(v.Block, r, newPhis) + rename(v, r, newPhis) } } diff --git a/ssa/ssa.go b/ssa/ssa.go index c7b550ec..a8a301de 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -239,9 +239,11 @@ type Instruction interface { // // Functions are immutable values; they do not have addresses. // +// Blocks contains the function's control-flow graph (CFG). // Blocks[0] is the function entry point; block order is not otherwise // semantically significant, though it may affect the readability of // the disassembly. +// To iterate over the blocks in dominance order, use DomPreorder(). // // Recover is an optional second entry point to which control resumes // after a recovered panic. The Recover block may contain only a load @@ -302,8 +304,13 @@ type Function struct { // i.e. Preds is nil. Empty blocks are typically pruned. // // BasicBlocks and their Preds/Succs relation form a (possibly cyclic) -// graph independent of the SSA Value graph. It is illegal for -// multiple edges to exist between the same pair of blocks. +// graph independent of the SSA Value graph: the control-flow graph or +// CFG. It is illegal for multiple edges to exist between the same +// pair of blocks. +// +// Each BasicBlock is also a node in the dominator tree of the CFG. +// The tree may be navigated using Idom()/Dominees() and queried using +// Dominates(). // // The order of Preds and Succs are significant (to Phi and If // instructions, respectively). @@ -315,7 +322,7 @@ type BasicBlock struct { Instrs []Instruction // instructions in order Preds, Succs []*BasicBlock // predecessors and successors succs2 [2]*BasicBlock // initial space for Succs. - dom *domNode // node in dominator tree; optional. + dom domInfo // dominator tree info gaps int // number of nil Instrs (transient). rundefers int // number of rundefers (transient) }