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