github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/compile/internal/ssa/branchelim.go (about) 1 // Copyright 2017 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 // branchelim tries to eliminate branches by 8 // generating CondSelect instructions. 9 // 10 // Search for basic blocks that look like 11 // 12 // bb0 bb0 13 // | \ / \ 14 // | bb1 or bb1 bb2 <- trivial if/else blocks 15 // | / \ / 16 // bb2 bb3 17 // 18 // where the intermediate blocks are mostly empty (with no side-effects); 19 // rewrite Phis in the postdominator as CondSelects. 20 func branchelim(f *Func) { 21 // FIXME: add support for lowering CondSelects on more architectures 22 switch f.Config.arch { 23 case "arm64", "amd64": 24 // implemented 25 default: 26 return 27 } 28 29 change := true 30 for change { 31 change = false 32 for _, b := range f.Blocks { 33 change = elimIf(f, b) || elimIfElse(f, b) || change 34 } 35 } 36 } 37 38 func canCondSelect(v *Value, arch string) bool { 39 // For now, stick to simple scalars that fit in registers 40 switch { 41 case v.Type.Size() > v.Block.Func.Config.RegSize: 42 return false 43 case v.Type.IsPtrShaped(): 44 return true 45 case v.Type.IsInteger(): 46 if arch == "amd64" && v.Type.Size() < 2 { 47 // amd64 doesn't support CMOV with byte registers 48 return false 49 } 50 return true 51 default: 52 return false 53 } 54 } 55 56 func elimIf(f *Func, dom *Block) bool { 57 // See if dom is an If with one arm that 58 // is trivial and succeeded by the other 59 // successor of dom. 60 if dom.Kind != BlockIf || dom.Likely != BranchUnknown { 61 return false 62 } 63 var simple, post *Block 64 for i := range dom.Succs { 65 bb, other := dom.Succs[i].Block(), dom.Succs[i^1].Block() 66 if isLeafPlain(bb) && bb.Succs[0].Block() == other { 67 simple = bb 68 post = other 69 break 70 } 71 } 72 if simple == nil || len(post.Preds) != 2 || post == dom { 73 return false 74 } 75 76 // We've found our diamond CFG of blocks. 77 // Now decide if fusing 'simple' into dom+post 78 // looks profitable. 79 80 // Check that there are Phis, and that all of them 81 // can be safely rewritten to CondSelect. 82 hasphis := false 83 for _, v := range post.Values { 84 if v.Op == OpPhi { 85 hasphis = true 86 if !canCondSelect(v, f.Config.arch) { 87 return false 88 } 89 } 90 } 91 if !hasphis { 92 return false 93 } 94 95 // Pick some upper bound for the number of instructions 96 // we'd be willing to execute just to generate a dead 97 // argument to CondSelect. In the worst case, this is 98 // the number of useless instructions executed. 99 const maxfuseinsts = 2 100 101 if len(simple.Values) > maxfuseinsts || !allTrivial(simple) { 102 return false 103 } 104 105 // Replace Phi instructions in b with CondSelect instructions 106 swap := (post.Preds[0].Block() == dom) != (dom.Succs[0].Block() == post) 107 for _, v := range post.Values { 108 if v.Op != OpPhi { 109 continue 110 } 111 v.Op = OpCondSelect 112 if swap { 113 v.Args[0], v.Args[1] = v.Args[1], v.Args[0] 114 } 115 v.AddArg(dom.Control) 116 } 117 118 // Put all of the instructions into 'dom' 119 // and update the CFG appropriately. 120 dom.Kind = post.Kind 121 dom.SetControl(post.Control) 122 dom.Aux = post.Aux 123 dom.Succs = append(dom.Succs[:0], post.Succs...) 124 for i := range dom.Succs { 125 e := dom.Succs[i] 126 e.b.Preds[e.i].b = dom 127 } 128 129 for i := range simple.Values { 130 simple.Values[i].Block = dom 131 } 132 for i := range post.Values { 133 post.Values[i].Block = dom 134 } 135 dom.Values = append(dom.Values, simple.Values...) 136 dom.Values = append(dom.Values, post.Values...) 137 138 // Trash 'post' and 'simple' 139 clobberBlock(post) 140 clobberBlock(simple) 141 142 f.invalidateCFG() 143 return true 144 } 145 146 // is this a BlockPlain with one predecessor? 147 func isLeafPlain(b *Block) bool { 148 return b.Kind == BlockPlain && len(b.Preds) == 1 149 } 150 151 func clobberBlock(b *Block) { 152 b.Values = nil 153 b.Preds = nil 154 b.Succs = nil 155 b.Aux = nil 156 b.SetControl(nil) 157 b.Likely = BranchUnknown 158 b.Kind = BlockInvalid 159 } 160 161 func elimIfElse(f *Func, b *Block) bool { 162 // See if 'b' ends in an if/else: it should 163 // have two successors, both of which are BlockPlain 164 // and succeeded by the same block. 165 if b.Kind != BlockIf || b.Likely != BranchUnknown { 166 return false 167 } 168 yes, no := b.Succs[0].Block(), b.Succs[1].Block() 169 if !isLeafPlain(yes) || len(yes.Values) > 1 || !allTrivial(yes) { 170 return false 171 } 172 if !isLeafPlain(no) || len(no.Values) > 1 || !allTrivial(no) { 173 return false 174 } 175 if b.Succs[0].Block().Succs[0].Block() != b.Succs[1].Block().Succs[0].Block() { 176 return false 177 } 178 // block that postdominates the if/else 179 post := b.Succs[0].Block().Succs[0].Block() 180 if len(post.Preds) != 2 || post == b { 181 return false 182 } 183 hasphis := false 184 for _, v := range post.Values { 185 if v.Op == OpPhi { 186 hasphis = true 187 if !canCondSelect(v, f.Config.arch) { 188 return false 189 } 190 } 191 } 192 if !hasphis { 193 return false 194 } 195 196 // Don't generate CondSelects if branch is cheaper. 197 if !shouldElimIfElse(no, yes, post, f.Config.arch) { 198 return false 199 } 200 201 // now we're committed: rewrite each Phi as a CondSelect 202 swap := post.Preds[0].Block() != b.Succs[0].Block() 203 for _, v := range post.Values { 204 if v.Op != OpPhi { 205 continue 206 } 207 v.Op = OpCondSelect 208 if swap { 209 v.Args[0], v.Args[1] = v.Args[1], v.Args[0] 210 } 211 v.AddArg(b.Control) 212 } 213 214 // Move the contents of all of these 215 // blocks into 'b' and update CFG edges accordingly 216 b.Kind = post.Kind 217 b.SetControl(post.Control) 218 b.Aux = post.Aux 219 b.Succs = append(b.Succs[:0], post.Succs...) 220 for i := range b.Succs { 221 e := b.Succs[i] 222 e.b.Preds[e.i].b = b 223 } 224 for i := range post.Values { 225 post.Values[i].Block = b 226 } 227 for i := range yes.Values { 228 yes.Values[i].Block = b 229 } 230 for i := range no.Values { 231 no.Values[i].Block = b 232 } 233 b.Values = append(b.Values, yes.Values...) 234 b.Values = append(b.Values, no.Values...) 235 b.Values = append(b.Values, post.Values...) 236 237 // trash post, yes, and no 238 clobberBlock(yes) 239 clobberBlock(no) 240 clobberBlock(post) 241 242 f.invalidateCFG() 243 return true 244 } 245 246 // shouldElimIfElse reports whether estimated cost of eliminating branch 247 // is lower than threshold. 248 func shouldElimIfElse(no, yes, post *Block, arch string) bool { 249 switch arch { 250 default: 251 return true 252 case "amd64": 253 const maxcost = 2 254 phi := 0 255 other := 0 256 for _, v := range post.Values { 257 if v.Op == OpPhi { 258 // Each phi results in CondSelect, which lowers into CMOV, 259 // CMOV has latency >1 on most CPUs. 260 phi++ 261 } 262 for _, x := range v.Args { 263 if x.Block == no || x.Block == yes { 264 other++ 265 } 266 } 267 } 268 cost := phi * 1 269 if phi > 1 { 270 // If we have more than 1 phi and some values in post have args 271 // in yes or no blocks, we may have to recalucalte condition, because 272 // those args may clobber flags. For now assume that all operations clobber flags. 273 cost += other * 1 274 } 275 return cost < maxcost 276 } 277 } 278 279 func allTrivial(b *Block) bool { 280 // don't fuse memory ops, Phi ops, divides (can panic), 281 // or anything else with side-effects 282 for _, v := range b.Values { 283 if v.Op == OpPhi || isDivMod(v.Op) || v.Type.IsMemory() || 284 v.MemoryArg() != nil || opcodeTable[v.Op].hasSideEffects { 285 return false 286 } 287 } 288 return true 289 } 290 291 func isDivMod(op Op) bool { 292 switch op { 293 case OpDiv8, OpDiv8u, OpDiv16, OpDiv16u, 294 OpDiv32, OpDiv32u, OpDiv64, OpDiv64u, OpDiv128u, 295 OpDiv32F, OpDiv64F, 296 OpMod8, OpMod8u, OpMod16, OpMod16u, 297 OpMod32, OpMod32u, OpMod64, OpMod64u: 298 return true 299 default: 300 return false 301 } 302 }