package ssaimport ()// passLayoutBlocks implements Builder.LayoutBlocks. This re-organizes builder.reversePostOrderedBasicBlocks.//// TODO: there are tons of room for improvement here. e.g. LLVM has BlockPlacementPass using BlockFrequencyInfo,// BranchProbabilityInfo, and LoopInfo to do a much better job. Also, if we have the profiling instrumentation// like ball-larus algorithm, then we could do profile-guided optimization. Basically all of them are trying// to maximize the fall-through opportunities which is most efficient.//// Here, fallthrough happens when a block ends with jump instruction whose target is the right next block in the// builder.reversePostOrderedBasicBlocks.//// Currently, we just place blocks using the DFS reverse post-order of the dominator tree with the heuristics:// 1. a split edge trampoline towards a loop header will be placed as a fallthrough.// 2. we invert the brz and brnz if it makes the fallthrough more likely.//// This heuristic is done in maybeInvertBranches function.func passLayoutBlocks( *builder) {// We might end up splitting critical edges which adds more basic blocks, // so we store the currently existing basic blocks in nonSplitBlocks temporarily. // That way we can iterate over the original basic blocks while appending new ones into reversePostOrderedBasicBlocks. := .blkStack[:0]for , := range .reversePostOrderedBasicBlocks {if !.Valid() {continue } = append(, )if != len(.reversePostOrderedBasicBlocks)-1 { _ = maybeInvertBranches(, , .reversePostOrderedBasicBlocks[+1]) } }var []*basicBlock// Reset the order slice since we update on the fly by splitting critical edges. .reversePostOrderedBasicBlocks = .reversePostOrderedBasicBlocks[:0] := .blkStack2[:0]for , := range {for := range .preds { := .preds[].blkif .visited == 1 || !.Valid() {continue } elseif .reversePostOrder < .reversePostOrder {// This means the edge is critical, and this pred is the trampoline and yet to be inserted. // Split edge trampolines must come before the destination in reverse post-order. .reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, ) .visited = 1// mark as inserted. } }// Now that we've already added all the potential trampoline blocks incoming to this block, // we can add this block itself. .reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, ) .visited = 1// mark as inserted.iflen(.success) < 2 {// There won't be critical edge originating from this block.continue } elseif .currentInstr.opcode == OpcodeBrTable {// We don't split critical edges here, because at the construction site of BrTable, we already split the edges.continue }for , := range .success {if !.ReturnBlock() && // If the successor is a return block, we need to split the edge any way because we need "epilogue" to be inserted.// Plus if there's no multiple incoming edges to this successor, (pred, succ) is not critical.len(.preds) < 2 {continue }// Otherwise, we are sure this is a critical edge. To modify the CFG, we need to find the predecessor info // from the successor.var *basicBlockPredecessorInfofor := range .preds { // This linear search should not be a problem since the number of predecessors should almost always small. := &.preds[]if .blk == { = break } }if == nil {// This must be a bug in somewhere around branch manipulation.panic("BUG: predecessor info not found while the successor exists in successors list") }ifwazevoapi.SSALoggingEnabled {fmt.Printf("trying to split edge from %d->%d at %s\n", .ID(), .ID(), .branch.Format()) } := .splitCriticalEdge(, , )// Update the successors slice because the target is no longer the original `succ`. .success[] = ifwazevoapi.SSAValidationEnabled { = append(, ) }ifwazevoapi.SSALoggingEnabled {fmt.Printf("edge split from %d->%d at %s as %d->%d->%d \n", .ID(), .ID(), .branch.Format(), .ID(), .ID(), .ID()) } := .currentInstrif .opcode == OpcodeJump && BasicBlockID(.rValue) == .id {// This can be lowered as fallthrough at the end of the block. .reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, ) .visited = 1// mark as inserted. } else { = append(, ) } }for , := range {if .success[0].reversePostOrder <= .reversePostOrder { // "<=", not "<" because the target might be itself.// This means the critical edge was backward, so we insert after the current block immediately. .reversePostOrderedBasicBlocks = append(.reversePostOrderedBasicBlocks, ) .visited = 1// mark as inserted. } // If the target is forward, we can wait to insert until the target is inserted. } = [:0] // Reuse the stack for the next block. }ifwazevoapi.SSALoggingEnabled {var []stringfor , := range .reversePostOrderedBasicBlocks { = append(, .Name()) }fmt.Println("ordered blocks: ", strings.Join(, ", ")) }ifwazevoapi.SSAValidationEnabled {for , := range {if .visited != 1 {panic("BUG: trampoline block not inserted: " + .formatHeader()) } .validate() } }// Reuse the stack for the next iteration. .blkStack2 = [:0]}// markFallthroughJumps finds the fallthrough jumps and marks them as such.func ( *builder) () { := len(.reversePostOrderedBasicBlocks) - 1for , := range .reversePostOrderedBasicBlocks {if < { := .currentInstrif .opcode == OpcodeJump && BasicBlockID(.rValue) == .reversePostOrderedBasicBlocks[+1].id { .AsFallthroughJump() } } }}// maybeInvertBranches inverts the branch instructions if it is likely possible to the fallthrough more likely with simple heuristics.// nextInRPO is the next block in the reverse post-order.//// Returns true if the branch is inverted for testing purpose.func maybeInvertBranches( *builder, *basicBlock, *basicBlock) bool { := .currentInstrif .opcode == OpcodeBrTable {returnfalse } := .previf == nil || (.opcode != OpcodeBrnz && .opcode != OpcodeBrz) {returnfalse }iflen(.vs.View()) != 0 || len(.vs.View()) != 0 {// If either one of them has arguments, we don't invert the branches.returnfalse }// So this block has two branches (a conditional branch followed by an unconditional branch) at the end. // We can invert the condition of the branch if it makes the fallthrough more likely. := .basicBlock(BasicBlockID(.rValue)) := .basicBlock(BasicBlockID(.rValue))if .loopHeader {// First, if the tail's target is loopHeader, we don't need to do anything here, // because the edge is likely to be critical edge for complex loops (e.g. loop with branches inside it). // That means, we will split the edge in the end of LayoutBlocks function, and insert the trampoline block // right after this block, which will be fallthrough in any way.returnfalse } elseif .loopHeader {// On the other hand, if the condBranch's target is loopHeader, we invert the condition of the branch // so that we could get the fallthrough to the trampoline block.goto }if == {// Also, if the tail's target is the next block in the reverse post-order, we don't need to do anything here, // because if this is not critical edge, we would end up placing these two blocks adjacent to each other. // Even if it is the critical edge, we place the trampoline block right after this block, which will be fallthrough in any way.returnfalse } elseif == {// If the condBranch's target is the next block in the reverse post-order, we invert the condition of the branch // so that we could get the fallthrough to the block.goto } else {returnfalse }:for := range .preds { := &.preds[]if .branch == { .branch = break } }for := range .preds { := &.preds[]if .branch == { .branch = break } } .InvertBrx() .rValue = Value(.ID()) .rValue = Value(.ID())ifwazevoapi.SSALoggingEnabled {fmt.Printf("inverting branches at %d->%d and %d->%d\n", .ID(), .ID(), .ID(), .ID()) }returntrue}// splitCriticalEdge splits the critical edge between the given predecessor (`pred`) and successor (owning `predInfo`).//// - `pred` is the source of the critical edge,// - `succ` is the destination of the critical edge,// - `predInfo` is the predecessor info in the succ.preds slice which represents the critical edge.//// Why splitting critical edges is important? See following links://// - https://en.wikipedia.org/wiki/Control-flow_graph// - https://nickdesaulniers.github.io/blog/2023/01/27/critical-edge-splitting///// The returned basic block is the trampoline block which is inserted to split the critical edge.func ( *builder) (, *basicBlock, *basicBlockPredecessorInfo) *basicBlock {// In the following, we convert the following CFG: // // pred --(originalBranch)--> succ // // to the following CFG: // // pred --(newBranch)--> trampoline --(originalBranch)-> succ // // where trampoline is a new basic block which is created to split the critical edge. := .allocateBasicBlock()ifint(.id) >= len(.dominators) { .dominators = append(.dominators, make([]*basicBlock, .id+1)...) } .dominators[.id] = := .branch// Replace originalBranch with the newBranch. := .AllocateInstruction() .opcode = .opcode .rValue = Value(.ID())switch .opcode {caseOpcodeJump:caseOpcodeBrz, OpcodeBrnz: .opcode = OpcodeJump// Trampoline consists of one unconditional branch. .v = .v .v = ValueInvaliddefault:panic("BUG: critical edge shouldn't be originated from br_table") }swapInstruction(, , )// Replace the original branch with the new branch. .rootInstr = .currentInstr = .success = append(.success, ) // Do not use []*basicBlock{pred} because we might have already allocated the slice. .preds = append(.preds, // same as ^.basicBlockPredecessorInfo{blk: , branch: }) .Seal()// Update the original branch to point to the trampoline. .blk = .branch = ifwazevoapi.SSAValidationEnabled { .validate() }iflen(.params.View()) > 0 {panic("trampoline should not have params") }// Assign the same order as the original block so that this will be placed before the actual destination. .reversePostOrder = .reversePostOrderreturn}// swapInstruction replaces `old` in the block `blk` with `New`.func swapInstruction( *basicBlock, , *Instruction) {if .rootInstr == { .rootInstr = := .next .next = .prev = } else {if .currentInstr == { .currentInstr = } := .prev .next, .prev = , if := .next; != nil { .next, .prev = , } } .prev, .next = nil, nil}
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.