1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:54:42 -07:00

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
This commit is contained in:
Alan Donovan 2013-12-05 09:50:18 -05:00
parent c846ececde
commit b5016cbbbd
4 changed files with 162 additions and 124 deletions

View File

@ -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, "}")

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}