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