github.com/bir3/gocompiler@v0.9.2202/src/cmd/compile/internal/ssa/fuse.go (about) 1 // Copyright 2015 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 import ( 8 "github.com/bir3/gocompiler/src/cmd/internal/src" 9 "fmt" 10 ) 11 12 // fuseEarly runs fuse(f, fuseTypePlain|fuseTypeIntInRange). 13 func fuseEarly(f *Func) { fuse(f, fuseTypePlain|fuseTypeIntInRange) } 14 15 // fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect). 16 func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect) } 17 18 type fuseType uint8 19 20 const ( 21 fuseTypePlain fuseType = 1 << iota 22 fuseTypeIf 23 fuseTypeIntInRange 24 fuseTypeBranchRedirect 25 fuseTypeShortCircuit 26 ) 27 28 // fuse simplifies control flow by joining basic blocks. 29 func fuse(f *Func, typ fuseType) { 30 for changed := true; changed; { 31 changed = false 32 // Be sure to avoid quadratic behavior in fuseBlockPlain. See issue 13554. 33 // Previously this was dealt with using backwards iteration, now fuseBlockPlain 34 // handles large runs of blocks. 35 for i := len(f.Blocks) - 1; i >= 0; i-- { 36 b := f.Blocks[i] 37 if typ&fuseTypeIf != 0 { 38 changed = fuseBlockIf(b) || changed 39 } 40 if typ&fuseTypeIntInRange != 0 { 41 changed = fuseIntegerComparisons(b) || changed 42 } 43 if typ&fuseTypePlain != 0 { 44 changed = fuseBlockPlain(b) || changed 45 } 46 if typ&fuseTypeShortCircuit != 0 { 47 changed = shortcircuitBlock(b) || changed 48 } 49 } 50 51 if typ&fuseTypeBranchRedirect != 0 { 52 changed = fuseBranchRedirect(f) || changed 53 } 54 if changed { 55 f.invalidateCFG() 56 } 57 } 58 } 59 60 // fuseBlockIf handles the following cases where s0 and s1 are empty blocks. 61 // 62 // b b b b 63 // \ / \ / | \ / \ / | | | 64 // s0 s1 | s1 s0 | | | 65 // \ / | / \ | | | 66 // ss ss ss ss 67 // 68 // If all Phi ops in ss have identical variables for slots corresponding to 69 // s0, s1 and b then the branch can be dropped. 70 // This optimization often comes up in switch statements with multiple 71 // expressions in a case clause: 72 // 73 // switch n { 74 // case 1,2,3: return 4 75 // } 76 // 77 // TODO: If ss doesn't contain any OpPhis, are s0 and s1 dead code anyway. 78 func fuseBlockIf(b *Block) bool { 79 if b.Kind != BlockIf { 80 return false 81 } 82 // It doesn't matter how much Preds does s0 or s1 have. 83 var ss0, ss1 *Block 84 s0 := b.Succs[0].b 85 i0 := b.Succs[0].i 86 if s0.Kind != BlockPlain || !isEmpty(s0) { 87 s0, ss0 = b, s0 88 } else { 89 ss0 = s0.Succs[0].b 90 i0 = s0.Succs[0].i 91 } 92 s1 := b.Succs[1].b 93 i1 := b.Succs[1].i 94 if s1.Kind != BlockPlain || !isEmpty(s1) { 95 s1, ss1 = b, s1 96 } else { 97 ss1 = s1.Succs[0].b 98 i1 = s1.Succs[0].i 99 } 100 if ss0 != ss1 { 101 if s0.Kind == BlockPlain && isEmpty(s0) && s1.Kind == BlockPlain && isEmpty(s1) { 102 // Two special cases where both s0, s1 and ss are empty blocks. 103 if s0 == ss1 { 104 s0, ss0 = b, ss1 105 } else if ss0 == s1 { 106 s1, ss1 = b, ss0 107 } else { 108 return false 109 } 110 } else { 111 return false 112 } 113 } 114 ss := ss0 115 116 // s0 and s1 are equal with b if the corresponding block is missing 117 // (2nd, 3rd and 4th case in the figure). 118 119 for _, v := range ss.Values { 120 if v.Op == OpPhi && v.Uses > 0 && v.Args[i0] != v.Args[i1] { 121 return false 122 } 123 } 124 125 // We do not need to redirect the Preds of s0 and s1 to ss, 126 // the following optimization will do this. 127 b.removeEdge(0) 128 if s0 != b && len(s0.Preds) == 0 { 129 s0.removeEdge(0) 130 // Move any (dead) values in s0 to b, 131 // where they will be eliminated by the next deadcode pass. 132 for _, v := range s0.Values { 133 v.Block = b 134 } 135 b.Values = append(b.Values, s0.Values...) 136 // Clear s0. 137 s0.Kind = BlockInvalid 138 s0.Values = nil 139 s0.Succs = nil 140 s0.Preds = nil 141 } 142 143 b.Kind = BlockPlain 144 b.Likely = BranchUnknown 145 b.ResetControls() 146 // The values in b may be dead codes, and clearing them in time may 147 // obtain new optimization opportunities. 148 // First put dead values that can be deleted into a slice walkValues. 149 // Then put their arguments in walkValues before resetting the dead values 150 // in walkValues, because the arguments may also become dead values. 151 walkValues := []*Value{} 152 for _, v := range b.Values { 153 if v.Uses == 0 && v.removeable() { 154 walkValues = append(walkValues, v) 155 } 156 } 157 for len(walkValues) != 0 { 158 v := walkValues[len(walkValues)-1] 159 walkValues = walkValues[:len(walkValues)-1] 160 if v.Uses == 0 && v.removeable() { 161 walkValues = append(walkValues, v.Args...) 162 v.reset(OpInvalid) 163 } 164 } 165 return true 166 } 167 168 // isEmpty reports whether b contains any live values. 169 // There may be false positives. 170 func isEmpty(b *Block) bool { 171 for _, v := range b.Values { 172 if v.Uses > 0 || v.Op.IsCall() || v.Op.HasSideEffects() || v.Type.IsVoid() || opcodeTable[v.Op].nilCheck { 173 return false 174 } 175 } 176 return true 177 } 178 179 // fuseBlockPlain handles a run of blocks with length >= 2, 180 // whose interior has single predecessors and successors, 181 // b must be BlockPlain, allowing it to be any node except the 182 // last (multiple successors means not BlockPlain). 183 // Cycles are handled and merged into b's successor. 184 func fuseBlockPlain(b *Block) bool { 185 if b.Kind != BlockPlain { 186 return false 187 } 188 189 c := b.Succs[0].b 190 if len(c.Preds) != 1 || c == b { // At least 2 distinct blocks. 191 return false 192 } 193 194 // find earliest block in run. Avoid simple cycles. 195 for len(b.Preds) == 1 && b.Preds[0].b != c && b.Preds[0].b.Kind == BlockPlain { 196 b = b.Preds[0].b 197 } 198 199 // find latest block in run. Still beware of simple cycles. 200 for { 201 if c.Kind != BlockPlain { 202 break 203 } // Has exactly 1 successor 204 cNext := c.Succs[0].b 205 if cNext == b { 206 break 207 } // not a cycle 208 if len(cNext.Preds) != 1 { 209 break 210 } // no other incoming edge 211 c = cNext 212 } 213 214 // Try to preserve any statement marks on the ends of blocks; move values to C 215 var b_next *Block 216 for bx := b; bx != c; bx = b_next { 217 // For each bx with an end-of-block statement marker, 218 // try to move it to a value in the next block, 219 // or to the next block's end, if possible. 220 b_next = bx.Succs[0].b 221 if bx.Pos.IsStmt() == src.PosIsStmt { 222 l := bx.Pos.Line() // looking for another place to mark for line l 223 outOfOrder := false 224 for _, v := range b_next.Values { 225 if v.Pos.IsStmt() == src.PosNotStmt { 226 continue 227 } 228 if l == v.Pos.Line() { // Found a Value with same line, therefore done. 229 v.Pos = v.Pos.WithIsStmt() 230 l = 0 231 break 232 } 233 if l < v.Pos.Line() { 234 // The order of values in a block is not specified so OOO in a block is not interesting, 235 // but they do all come before the end of the block, so this disqualifies attaching to end of b_next. 236 outOfOrder = true 237 } 238 } 239 if l != 0 && !outOfOrder && (b_next.Pos.Line() == l || b_next.Pos.IsStmt() != src.PosIsStmt) { 240 b_next.Pos = bx.Pos.WithIsStmt() 241 } 242 } 243 // move all of bx's values to c (note containing loop excludes c) 244 for _, v := range bx.Values { 245 v.Block = c 246 } 247 } 248 249 // Compute the total number of values and find the largest value slice in the run, to maximize chance of storage reuse. 250 total := 0 251 totalBeforeMax := 0 // number of elements preceding the maximum block (i.e. its position in the result). 252 max_b := b // block with maximum capacity 253 254 for bx := b; ; bx = bx.Succs[0].b { 255 if cap(bx.Values) > cap(max_b.Values) { 256 totalBeforeMax = total 257 max_b = bx 258 } 259 total += len(bx.Values) 260 if bx == c { 261 break 262 } 263 } 264 265 // Use c's storage if fused blocks will fit, else use the max if that will fit, else allocate new storage. 266 267 // Take care to avoid c.Values pointing to b.valstorage. 268 // See golang.org/issue/18602. 269 270 // It's important to keep the elements in the same order; maintenance of 271 // debugging information depends on the order of *Values in Blocks. 272 // This can also cause changes in the order (which may affect other 273 // optimizations and possibly compiler output) for 32-vs-64 bit compilation 274 // platforms (word size affects allocation bucket size affects slice capacity). 275 276 // figure out what slice will hold the values, 277 // preposition the destination elements if not allocating new storage 278 var t []*Value 279 if total <= len(c.valstorage) { 280 t = c.valstorage[:total] 281 max_b = c 282 totalBeforeMax = total - len(c.Values) 283 copy(t[totalBeforeMax:], c.Values) 284 } else if total <= cap(max_b.Values) { // in place, somewhere 285 t = max_b.Values[0:total] 286 copy(t[totalBeforeMax:], max_b.Values) 287 } else { 288 t = make([]*Value, total) 289 max_b = nil 290 } 291 292 // copy the values 293 copyTo := 0 294 for bx := b; ; bx = bx.Succs[0].b { 295 if bx != max_b { 296 copy(t[copyTo:], bx.Values) 297 } else if copyTo != totalBeforeMax { // trust but verify. 298 panic(fmt.Errorf("totalBeforeMax (%d) != copyTo (%d), max_b=%v, b=%v, c=%v", totalBeforeMax, copyTo, max_b, b, c)) 299 } 300 if bx == c { 301 break 302 } 303 copyTo += len(bx.Values) 304 } 305 c.Values = t 306 307 // replace b->c edge with preds(b) -> c 308 c.predstorage[0] = Edge{} 309 if len(b.Preds) > len(b.predstorage) { 310 c.Preds = b.Preds 311 } else { 312 c.Preds = append(c.predstorage[:0], b.Preds...) 313 } 314 for i, e := range c.Preds { 315 p := e.b 316 p.Succs[e.i] = Edge{c, i} 317 } 318 f := b.Func 319 if f.Entry == b { 320 f.Entry = c 321 } 322 323 // trash b's fields, just in case 324 for bx := b; bx != c; bx = b_next { 325 b_next = bx.Succs[0].b 326 327 bx.Kind = BlockInvalid 328 bx.Values = nil 329 bx.Preds = nil 330 bx.Succs = nil 331 } 332 return true 333 }