package ssaimport ()// passCalculateImmediateDominators calculates immediate dominators for each basic block.// The result is stored in b.dominators. This make it possible for the following passes to// use builder.isDominatedBy to check if a block is dominated by another block.//// At the last of pass, this function also does the loop detection and sets the basicBlock.loop flag.func passCalculateImmediateDominators( *builder) { := .reversePostOrderedBasicBlocks[:0]// Store the reverse postorder from the entrypoint into reversePostOrder slice. // This calculation of reverse postorder is not described in the paper, // so we use heuristic to calculate it so that we could potentially handle arbitrary // complex CFGs under the assumption that success is sorted in program's natural order. // That means blk.success[i] always appears before blk.success[i+1] in the source program, // which is a reasonable assumption as long as SSA Builder is properly used. // // First we push blocks in postorder iteratively visit successors of the entry block. := .entryBlk() := append(.blkStack[:0], )// These flags are used to track the state of the block in the DFS traversal. // We temporarily use the reversePostOrder field to store the state.const , , = 0, 1, 2 .visited = forlen() > 0 { := len() - 1 := [] = [:]switch .visited {case :// This is likely a bug in the frontend.panic("BUG: unsupported CFG")case :// This is the first time to pop this block, and we have to see the successors first. // So push this block again to the stack. = append(, )// And push the successors to the stack if necessary.for , := range .success {if .ReturnBlock() || .invalid {continue }if .visited == { .visited = = append(, ) } }// Finally, we could pop this block once we pop all of its successors. .visited = case :// Note: at this point we push blk in postorder despite its name. = append(, )default:panic("BUG") } }// At this point, reversePostOrder has postorder actually, so we reverse it.for := len()/2 - 1; >= 0; -- { := len() - 1 - [], [] = [], [] }for , := range { .reversePostOrder = int32() }// Reuse the dominators slice if possible from the previous computation of function. .dominators = .dominators[:cap(.dominators)]iflen(.dominators) < .basicBlocksPool.Allocated() {// Generously reserve space in the slice because the slice will be reused future allocation. .dominators = append(.dominators, make([]*basicBlock, .basicBlocksPool.Allocated())...) }calculateDominators(, .dominators)// Reuse the slices for the future use. .blkStack = // For the following passes. .reversePostOrderedBasicBlocks = // Ready to detect loops!subPassLoopDetection()}// calculateDominators calculates the immediate dominator of each node in the CFG, and store the result in `doms`.// The algorithm is based on the one described in the paper "A Simple, Fast Dominance Algorithm"// https://www.cs.rice.edu/~keith/EMBED/dom.pdf which is a faster/simple alternative to the well known Lengauer-Tarjan algorithm.//// The following code almost matches the pseudocode in the paper with one exception (see the code comment below).//// The result slice `doms` must be pre-allocated with the size larger than the size of dfsBlocks.func calculateDominators( []*basicBlock, []*basicBlock) { , := [0], [1: /* skips entry point */]for , := range { [.id] = nil } [.id] = := truefor { = falsefor , := range {var *basicBlockfor := range .preds { := .preds[].blk// Skip if this pred is not reachable yet. Note that this is not described in the paper, // but it is necessary to handle nested loops etc.if [.id] == nil {continue }if == nil { = continue } else { = intersect(, , ) } }if [.id] != { [.id] = = true } } }}// intersect returns the common dominator of blk1 and blk2.//// This is the `intersect` function in the paper.func intersect( []*basicBlock, *basicBlock, *basicBlock) *basicBlock { , := , for != {// Move the 'finger1' upwards to its immediate dominator.for .reversePostOrder > .reversePostOrder { = [.id] }// Move the 'finger2' upwards to its immediate dominator.for .reversePostOrder > .reversePostOrder { = [.id] } }return}// subPassLoopDetection detects loops in the function using the immediate dominators.//// This is run at the last of passCalculateImmediateDominators.func subPassLoopDetection( *builder) {for := .blockIteratorBegin(); != nil; = .blockIteratorNext() {for := range .preds { := .preds[].blkif .invalid {continue }if .isDominatedBy(, ) { .loopHeader = true } } }}// buildLoopNestingForest builds the loop nesting forest for the function.// This must be called after branch splitting since it relies on the CFG.func passBuildLoopNestingForest( *builder) { := .entryBlk() := .dominatorsfor , := range .reversePostOrderedBasicBlocks { := [.id]for !.loopHeader && != { = [.id] }if == && .loopHeader { .loopNestingForestRoots = append(.loopNestingForestRoots, ) } elseif == { } elseif .loopHeader { .loopNestingForestChildren = .loopNestingForestChildren.Append(&.varLengthBasicBlockPool, ) } }ifwazevoapi.SSALoggingEnabled {for , := range .loopNestingForestRoots {printLoopNestingForest(.(*basicBlock), 0) } }}func printLoopNestingForest( *basicBlock, int) {fmt.Println(strings.Repeat("\t", ), "loop nesting forest root:", .ID())for , := range .loopNestingForestChildren.View() {fmt.Println(strings.Repeat("\t", +1), "child:", .ID())if .LoopHeader() { (.(*basicBlock), +2) } }}type dominatorSparseTree struct { time int32 euler []*basicBlock first, depth []int32 table [][]int32}// passBuildDominatorTree builds the dominator tree for the function, and constructs builder.sparseTree.func passBuildDominatorTree( *builder) {// First we materialize the children of each node in the dominator tree. := .dominatorsfor , := range .reversePostOrderedBasicBlocks { := [.id]if == nil {panic("BUG") } elseif == {// This is the entry block.continue }if := .child; == nil { .child = } else { .child = .sibling = } }// Reset the state from the previous computation. := .basicBlocksPool.Allocated() := &.sparseTree .euler = append(.euler[:0], make([]*basicBlock, 2*-1)...) .first = append(.first[:0], make([]int32, )...)for := range .first { .first[] = -1 } .depth = append(.depth[:0], make([]int32, 2*-1)...) .time = 0// Start building the sparse tree. .eulerTour(.entryBlk(), 0) .buildSparseTable()}func ( *dominatorSparseTree) ( *basicBlock, int32) {ifwazevoapi.SSALoggingEnabled {fmt.Println(strings.Repeat("\t", int()), "euler tour:", .ID()) } .euler[.time] = .depth[.time] = if .first[.id] == -1 { .first[.id] = .time } .time++for := .child; != nil; = .sibling { .(, +1) .euler[.time] = // add the current node again after visiting a child .depth[.time] = .time++ }}// buildSparseTable builds a sparse table for RMQ queries.func ( *dominatorSparseTree) () { := len(.depth) := int(math.Log2(float64())) + 1 := .tableif >= len() { = append(, make([][]int32, -len()+1)...) }for := range {iflen([]) < { [] = append([], make([]int32, -len([]))...) } [][0] = int32() }for := 1; 1<< <= ; ++ {for := 0; +(1<<)-1 < ; ++ {if .depth[[][-1]] < .depth[[+(1<<(-1))][-1]] { [][] = [][-1] } else { [][] = [+(1<<(-1))][-1] } } } .table = }// rmq performs a range minimum query on the sparse table.func ( *dominatorSparseTree) (, int32) int32 { := .table := .depth := int(math.Log2(float64( - + 1)))if [[][]] <= [[-(1<<)+1][]] {return [][] }return [-(1<<)+1][]}// findLCA finds the LCA using the Euler tour and RMQ.func ( *dominatorSparseTree) (, BasicBlockID) *basicBlock { := .firstif [] > [] { , = , }return .euler[.rmq([], [])]}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.