github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/ssa/blockopt.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ssa 6 7 // Simple block optimizations to simplify the control flow graph. 8 9 // TODO(adonovan): opt: instead of creating several "unreachable" blocks 10 // per function in the Builder, reuse a single one (e.g. at Blocks[1]) 11 // to reduce garbage. 12 13 import ( 14 "fmt" 15 "os" 16 ) 17 18 // If true, perform sanity checking and show progress at each 19 // successive iteration of optimizeBlocks. Very verbose. 20 const debugBlockOpt = false 21 22 // markReachable sets Index=-1 for all blocks reachable from b. 23 func markReachable(b *BasicBlock) { 24 b.Index = -1 25 for _, succ := range b.Succs { 26 if succ.Index == 0 { 27 markReachable(succ) 28 } 29 } 30 } 31 32 // deleteUnreachableBlocks marks all reachable blocks of f and 33 // eliminates (nils) all others, including possibly cyclic subgraphs. 34 func deleteUnreachableBlocks(f *Function) { 35 const white, black = 0, -1 36 // We borrow b.Index temporarily as the mark bit. 37 for _, b := range f.Blocks { 38 b.Index = white 39 } 40 markReachable(f.Blocks[0]) 41 if f.Recover != nil { 42 markReachable(f.Recover) 43 } 44 for i, b := range f.Blocks { 45 if b.Index == white { 46 for _, c := range b.Succs { 47 if c.Index == black { 48 c.removePred(b) // delete white->black edge 49 } 50 } 51 if debugBlockOpt { 52 fmt.Fprintln(os.Stderr, "unreachable", b) 53 } 54 f.Blocks[i] = nil // delete b 55 } 56 } 57 f.removeNilBlocks() 58 } 59 60 // jumpThreading attempts to apply simple jump-threading to block b, 61 // in which a->b->c become a->c if b is just a Jump. 62 // The result is true if the optimization was applied. 63 func jumpThreading(f *Function, b *BasicBlock) bool { 64 if b.Index == 0 { 65 return false // don't apply to entry block 66 } 67 if b.Instrs == nil { 68 return false 69 } 70 if _, ok := b.Instrs[0].(*Jump); !ok { 71 return false // not just a jump 72 } 73 c := b.Succs[0] 74 if c == b { 75 return false // don't apply to degenerate jump-to-self. 76 } 77 if c.hasPhi() { 78 return false // not sound without more effort 79 } 80 for j, a := range b.Preds { 81 a.replaceSucc(b, c) 82 83 // If a now has two edges to c, replace its degenerate If by Jump. 84 if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c { 85 jump := new(Jump) 86 jump.setBlock(a) 87 a.Instrs[len(a.Instrs)-1] = jump 88 a.Succs = a.Succs[:1] 89 c.removePred(b) 90 } else { 91 if j == 0 { 92 c.replacePred(b, a) 93 } else { 94 c.Preds = append(c.Preds, a) 95 } 96 } 97 98 if debugBlockOpt { 99 fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c) 100 } 101 } 102 f.Blocks[b.Index] = nil // delete b 103 return true 104 } 105 106 // fuseBlocks attempts to apply the block fusion optimization to block 107 // a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1. 108 // The result is true if the optimization was applied. 109 func fuseBlocks(f *Function, a *BasicBlock) bool { 110 if len(a.Succs) != 1 { 111 return false 112 } 113 b := a.Succs[0] 114 if len(b.Preds) != 1 { 115 return false 116 } 117 118 // Degenerate &&/|| ops may result in a straight-line CFG 119 // containing φ-nodes. (Ideally we'd replace such them with 120 // their sole operand but that requires Referrers, built later.) 121 if b.hasPhi() { 122 return false // not sound without further effort 123 } 124 125 // Eliminate jump at end of A, then copy all of B across. 126 a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...) 127 for _, instr := range b.Instrs { 128 instr.setBlock(a) 129 } 130 131 // A inherits B's successors 132 a.Succs = append(a.succs2[:0], b.Succs...) 133 134 // Fix up Preds links of all successors of B. 135 for _, c := range b.Succs { 136 c.replacePred(b, a) 137 } 138 139 if debugBlockOpt { 140 fmt.Fprintln(os.Stderr, "fuseBlocks", a, b) 141 } 142 143 f.Blocks[b.Index] = nil // delete b 144 return true 145 } 146 147 // optimizeBlocks() performs some simple block optimizations on a 148 // completed function: dead block elimination, block fusion, jump 149 // threading. 150 func optimizeBlocks(f *Function) { 151 deleteUnreachableBlocks(f) 152 153 // Loop until no further progress. 154 changed := true 155 for changed { 156 changed = false 157 158 if debugBlockOpt { 159 f.WriteTo(os.Stderr) 160 mustSanityCheck(f, nil) 161 } 162 163 for _, b := range f.Blocks { 164 // f.Blocks will temporarily contain nils to indicate 165 // deleted blocks; we remove them at the end. 166 if b == nil { 167 continue 168 } 169 170 // Fuse blocks. b->c becomes bc. 171 if fuseBlocks(f, b) { 172 changed = true 173 } 174 175 // a->b->c becomes a->c if b contains only a Jump. 176 if jumpThreading(f, b) { 177 changed = true 178 continue // (b was disconnected) 179 } 180 } 181 } 182 f.removeNilBlocks() 183 }