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