github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/cmd/compile/internal/ssa/likelyadjust.go (about) 1 // Copyright 2016 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 "fmt" 9 ) 10 11 type loop struct { 12 header *Block // The header node of this (reducible) loop 13 outer *loop // loop containing this loop 14 15 // By default, children exits, and depth are not initialized. 16 children []*loop // loops nested directly within this loop. Initialized by assembleChildren(). 17 exits []*Block // exits records blocks reached by exits from this loop. Initialized by findExits(). 18 19 // Loops aren't that common, so rather than force regalloc to keep 20 // a map or slice for its data, just put it here. 21 spills []*Value 22 scratch int32 23 24 // Next three fields used by regalloc and/or 25 // aid in computation of inner-ness and list of blocks. 26 nBlocks int32 // Number of blocks in this loop but not within inner loops 27 depth int16 // Nesting depth of the loop; 1 is outermost. Initialized by calculateDepths(). 28 isInner bool // True if never discovered to contain a loop 29 30 // register allocation uses this. 31 containsCall bool // if any block in this loop or any loop it contains has a call 32 } 33 34 // outerinner records that outer contains inner 35 func (sdom SparseTree) outerinner(outer, inner *loop) { 36 // There could be other outer loops found in some random order, 37 // locate the new outer loop appropriately among them. 38 oldouter := inner.outer 39 for oldouter != nil && sdom.isAncestor(outer.header, oldouter.header) { 40 inner = oldouter 41 oldouter = inner.outer 42 } 43 if outer == oldouter { 44 return 45 } 46 if oldouter != nil { 47 outer.outer = oldouter 48 } 49 50 inner.outer = outer 51 outer.isInner = false 52 if inner.containsCall { 53 outer.setContainsCall() 54 } 55 } 56 57 func (l *loop) setContainsCall() { 58 for ; l != nil && !l.containsCall; l = l.outer { 59 l.containsCall = true 60 } 61 62 } 63 func (l *loop) checkContainsCall(bb *Block) { 64 if bb.Kind == BlockDefer { 65 l.setContainsCall() 66 return 67 } 68 for _, v := range bb.Values { 69 if opcodeTable[v.Op].call { 70 l.setContainsCall() 71 return 72 } 73 } 74 } 75 76 type loopnest struct { 77 f *Func 78 b2l []*loop 79 po []*Block 80 sdom SparseTree 81 loops []*loop 82 83 // Record which of the lazily initialized fields have actually been initialized. 84 initializedChildren, initializedDepth, initializedExits bool 85 } 86 87 func min8(a, b int8) int8 { 88 if a < b { 89 return a 90 } 91 return b 92 } 93 94 func max8(a, b int8) int8 { 95 if a > b { 96 return a 97 } 98 return b 99 } 100 101 const ( 102 blDEFAULT = 0 103 blMin = blDEFAULT 104 blCALL = 1 105 blRET = 2 106 blEXIT = 3 107 ) 108 109 var bllikelies [4]string = [4]string{"default", "call", "ret", "exit"} 110 111 func describePredictionAgrees(b *Block, prediction BranchPrediction) string { 112 s := "" 113 if prediction == b.Likely { 114 s = " (agrees with previous)" 115 } else if b.Likely != BranchUnknown { 116 s = " (disagrees with previous, ignored)" 117 } 118 return s 119 } 120 121 func describeBranchPrediction(f *Func, b *Block, likely, not int8, prediction BranchPrediction) { 122 f.Warnl(b.Pos, "Branch prediction rule %s < %s%s", 123 bllikelies[likely-blMin], bllikelies[not-blMin], describePredictionAgrees(b, prediction)) 124 } 125 126 func likelyadjust(f *Func) { 127 // The values assigned to certain and local only matter 128 // in their rank order. 0 is default, more positive 129 // is less likely. It's possible to assign a negative 130 // unlikeliness (though not currently the case). 131 certain := make([]int8, f.NumBlocks()) // In the long run, all outcomes are at least this bad. Mainly for Exit 132 local := make([]int8, f.NumBlocks()) // for our immediate predecessors. 133 134 po := f.postorder() 135 nest := f.loopnest() 136 b2l := nest.b2l 137 138 for _, b := range po { 139 switch b.Kind { 140 case BlockExit: 141 // Very unlikely. 142 local[b.ID] = blEXIT 143 certain[b.ID] = blEXIT 144 145 // Ret, it depends. 146 case BlockRet, BlockRetJmp: 147 local[b.ID] = blRET 148 certain[b.ID] = blRET 149 150 // Calls. TODO not all calls are equal, names give useful clues. 151 // Any name-based heuristics are only relative to other calls, 152 // and less influential than inferences from loop structure. 153 case BlockDefer: 154 local[b.ID] = blCALL 155 certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID]) 156 157 default: 158 if len(b.Succs) == 1 { 159 certain[b.ID] = certain[b.Succs[0].b.ID] 160 } else if len(b.Succs) == 2 { 161 // If successor is an unvisited backedge, it's in loop and we don't care. 162 // Its default unlikely is also zero which is consistent with favoring loop edges. 163 // Notice that this can act like a "reset" on unlikeliness at loops; the 164 // default "everything returns" unlikeliness is erased by min with the 165 // backedge likeliness; however a loop with calls on every path will be 166 // tagged with call cost. Net effect is that loop entry is favored. 167 b0 := b.Succs[0].b.ID 168 b1 := b.Succs[1].b.ID 169 certain[b.ID] = min8(certain[b0], certain[b1]) 170 171 l := b2l[b.ID] 172 l0 := b2l[b0] 173 l1 := b2l[b1] 174 175 prediction := b.Likely 176 // Weak loop heuristic -- both source and at least one dest are in loops, 177 // and there is a difference in the destinations. 178 // TODO what is best arrangement for nested loops? 179 if l != nil && l0 != l1 { 180 noprediction := false 181 switch { 182 // prefer not to exit loops 183 case l1 == nil: 184 prediction = BranchLikely 185 case l0 == nil: 186 prediction = BranchUnlikely 187 188 // prefer to stay in loop, not exit to outer. 189 case l == l0: 190 prediction = BranchLikely 191 case l == l1: 192 prediction = BranchUnlikely 193 default: 194 noprediction = true 195 } 196 if f.pass.debug > 0 && !noprediction { 197 f.Warnl(b.Pos, "Branch prediction rule stay in loop%s", 198 describePredictionAgrees(b, prediction)) 199 } 200 201 } else { 202 // Lacking loop structure, fall back on heuristics. 203 if certain[b1] > certain[b0] { 204 prediction = BranchLikely 205 if f.pass.debug > 0 { 206 describeBranchPrediction(f, b, certain[b0], certain[b1], prediction) 207 } 208 } else if certain[b0] > certain[b1] { 209 prediction = BranchUnlikely 210 if f.pass.debug > 0 { 211 describeBranchPrediction(f, b, certain[b1], certain[b0], prediction) 212 } 213 } else if local[b1] > local[b0] { 214 prediction = BranchLikely 215 if f.pass.debug > 0 { 216 describeBranchPrediction(f, b, local[b0], local[b1], prediction) 217 } 218 } else if local[b0] > local[b1] { 219 prediction = BranchUnlikely 220 if f.pass.debug > 0 { 221 describeBranchPrediction(f, b, local[b1], local[b0], prediction) 222 } 223 } 224 } 225 if b.Likely != prediction { 226 if b.Likely == BranchUnknown { 227 b.Likely = prediction 228 } 229 } 230 } 231 // Look for calls in the block. If there is one, make this block unlikely. 232 for _, v := range b.Values { 233 if opcodeTable[v.Op].call { 234 local[b.ID] = blCALL 235 certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID]) 236 } 237 } 238 } 239 if f.pass.debug > 2 { 240 f.Warnl(b.Pos, "BP: Block %s, local=%s, certain=%s", b, bllikelies[local[b.ID]-blMin], bllikelies[certain[b.ID]-blMin]) 241 } 242 243 } 244 } 245 246 func (l *loop) String() string { 247 return fmt.Sprintf("hdr:%s", l.header) 248 } 249 250 func (l *loop) LongString() string { 251 i := "" 252 o := "" 253 if l.isInner { 254 i = ", INNER" 255 } 256 if l.outer != nil { 257 o = ", o=" + l.outer.header.String() 258 } 259 return fmt.Sprintf("hdr:%s%s%s", l.header, i, o) 260 } 261 262 // nearestOuterLoop returns the outer loop of loop most nearly 263 // containing block b; the header must dominate b. loop itself 264 // is assumed to not be that loop. For acceptable performance, 265 // we're relying on loop nests to not be terribly deep. 266 func (l *loop) nearestOuterLoop(sdom SparseTree, b *Block) *loop { 267 var o *loop 268 for o = l.outer; o != nil && !sdom.isAncestorEq(o.header, b); o = o.outer { 269 } 270 return o 271 } 272 273 func loopnestfor(f *Func) *loopnest { 274 po := f.postorder() 275 sdom := f.sdom() 276 b2l := make([]*loop, f.NumBlocks()) 277 loops := make([]*loop, 0) 278 279 // Reducible-loop-nest-finding. 280 for _, b := range po { 281 if f.pass.debug > 3 { 282 fmt.Printf("loop finding (0) at %s\n", b) 283 } 284 285 var innermost *loop // innermost header reachable from this block 286 287 // IF any successor s of b is in a loop headed by h 288 // AND h dominates b 289 // THEN b is in the loop headed by h. 290 // 291 // Choose the first/innermost such h. 292 // 293 // IF s itself dominates b, the s is a loop header; 294 // and there may be more than one such s. 295 // Since there's at most 2 successors, the inner/outer ordering 296 // between them can be established with simple comparisons. 297 for _, e := range b.Succs { 298 bb := e.b 299 l := b2l[bb.ID] 300 301 if sdom.isAncestorEq(bb, b) { // Found a loop header 302 if l == nil { 303 l = &loop{header: bb, isInner: true} 304 loops = append(loops, l) 305 b2l[bb.ID] = l 306 l.checkContainsCall(bb) 307 } 308 } else { // Perhaps a loop header is inherited. 309 // is there any loop containing our successor whose 310 // header dominates b? 311 if l != nil && !sdom.isAncestorEq(l.header, b) { 312 l = l.nearestOuterLoop(sdom, b) 313 } 314 } 315 316 if l == nil || innermost == l { 317 continue 318 } 319 320 if innermost == nil { 321 innermost = l 322 continue 323 } 324 325 if sdom.isAncestor(innermost.header, l.header) { 326 sdom.outerinner(innermost, l) 327 innermost = l 328 } else if sdom.isAncestor(l.header, innermost.header) { 329 sdom.outerinner(l, innermost) 330 } 331 } 332 333 if innermost != nil { 334 b2l[b.ID] = innermost 335 innermost.checkContainsCall(b) 336 innermost.nBlocks++ 337 } 338 } 339 340 ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops} 341 342 // Curious about the loopiness? "-d=ssa/likelyadjust/stats" 343 if f.pass.stats > 0 && len(loops) > 0 { 344 ln.assembleChildren() 345 ln.calculateDepths() 346 ln.findExits() 347 348 // Note stats for non-innermost loops are slightly flawed because 349 // they don't account for inner loop exits that span multiple levels. 350 351 for _, l := range loops { 352 x := len(l.exits) 353 cf := 0 354 if !l.containsCall { 355 cf = 1 356 } 357 inner := 0 358 if l.isInner { 359 inner++ 360 } 361 362 f.LogStat("loopstats:", 363 l.depth, "depth", x, "exits", 364 inner, "is_inner", cf, "is_callfree", l.nBlocks, "n_blocks") 365 } 366 } 367 368 if f.pass.debug > 1 && len(loops) > 0 { 369 fmt.Printf("Loops in %s:\n", f.Name) 370 for _, l := range loops { 371 fmt.Printf("%s, b=", l.LongString()) 372 for _, b := range f.Blocks { 373 if b2l[b.ID] == l { 374 fmt.Printf(" %s", b) 375 } 376 } 377 fmt.Print("\n") 378 } 379 fmt.Printf("Nonloop blocks in %s:", f.Name) 380 for _, b := range f.Blocks { 381 if b2l[b.ID] == nil { 382 fmt.Printf(" %s", b) 383 } 384 } 385 fmt.Print("\n") 386 } 387 return ln 388 } 389 390 // assembleChildren initializes the children field of each 391 // loop in the nest. Loop A is a child of loop B if A is 392 // directly nested within B (based on the reducible-loops 393 // detection above) 394 func (ln *loopnest) assembleChildren() { 395 if ln.initializedChildren { 396 return 397 } 398 for _, l := range ln.loops { 399 if l.outer != nil { 400 l.outer.children = append(l.outer.children, l) 401 } 402 } 403 ln.initializedChildren = true 404 } 405 406 // calculateDepths uses the children field of loops 407 // to determine the nesting depth (outer=1) of each 408 // loop. This is helpful for finding exit edges. 409 func (ln *loopnest) calculateDepths() { 410 if ln.initializedDepth { 411 return 412 } 413 ln.assembleChildren() 414 for _, l := range ln.loops { 415 if l.outer == nil { 416 l.setDepth(1) 417 } 418 } 419 ln.initializedDepth = true 420 } 421 422 // findExits uses loop depth information to find the 423 // exits from a loop. 424 func (ln *loopnest) findExits() { 425 if ln.initializedExits { 426 return 427 } 428 ln.calculateDepths() 429 b2l := ln.b2l 430 for _, b := range ln.po { 431 l := b2l[b.ID] 432 if l != nil && len(b.Succs) == 2 { 433 sl := b2l[b.Succs[0].b.ID] 434 if recordIfExit(l, sl, b.Succs[0].b) { 435 continue 436 } 437 sl = b2l[b.Succs[1].b.ID] 438 if recordIfExit(l, sl, b.Succs[1].b) { 439 continue 440 } 441 } 442 } 443 ln.initializedExits = true 444 } 445 446 // depth returns the loop nesting level of block b. 447 func (ln *loopnest) depth(b ID) int16 { 448 if l := ln.b2l[b]; l != nil { 449 return l.depth 450 } 451 return 0 452 } 453 454 // recordIfExit checks sl (the loop containing b) to see if it 455 // is outside of loop l, and if so, records b as an exit block 456 // from l and returns true. 457 func recordIfExit(l, sl *loop, b *Block) bool { 458 if sl != l { 459 if sl == nil || sl.depth <= l.depth { 460 l.exits = append(l.exits, b) 461 return true 462 } 463 // sl is not nil, and is deeper than l 464 // it's possible for this to be a goto into an irreducible loop made from gotos. 465 for sl.depth > l.depth { 466 sl = sl.outer 467 } 468 if sl != l { 469 l.exits = append(l.exits, b) 470 return true 471 } 472 } 473 return false 474 } 475 476 func (l *loop) setDepth(d int16) { 477 l.depth = d 478 for _, c := range l.children { 479 c.setDepth(d + 1) 480 } 481 }