github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/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 // Next two fields not currently used, but cheap to maintain, 15 // and aid in computation of inner-ness and list of blocks. 16 nBlocks int32 // Number of blocks in this loop but not within inner loops 17 isInner bool // True if never discovered to contain a loop 18 containsCall bool // if any block in this loop or any loop it contains is a BlockCall or BlockDefer 19 } 20 21 // outerinner records that outer contains inner 22 func (sdom sparseTree) outerinner(outer, inner *loop) { 23 oldouter := inner.outer 24 if oldouter == nil || sdom.isAncestorEq(oldouter.header, outer.header) { 25 inner.outer = outer 26 outer.isInner = false 27 if inner.containsCall { 28 outer.setContainsCall() 29 } 30 } 31 } 32 33 func (l *loop) setContainsCall() { 34 for ; l != nil && !l.containsCall; l = l.outer { 35 l.containsCall = true 36 } 37 38 } 39 func (l *loop) checkContainsCall(bb *Block) { 40 if bb.Kind == BlockCall || bb.Kind == BlockDefer { 41 l.setContainsCall() 42 } 43 } 44 45 type loopnest struct { 46 f *Func 47 b2l []*loop 48 po []*Block 49 sdom sparseTree 50 loops []*loop 51 } 52 53 func min8(a, b int8) int8 { 54 if a < b { 55 return a 56 } 57 return b 58 } 59 60 func max8(a, b int8) int8 { 61 if a > b { 62 return a 63 } 64 return b 65 } 66 67 const ( 68 blDEFAULT = 0 69 blMin = blDEFAULT 70 blCALL = 1 71 blRET = 2 72 blEXIT = 3 73 ) 74 75 var bllikelies [4]string = [4]string{"default", "call", "ret", "exit"} 76 77 func describePredictionAgrees(b *Block, prediction BranchPrediction) string { 78 s := "" 79 if prediction == b.Likely { 80 s = " (agrees with previous)" 81 } else if b.Likely != BranchUnknown { 82 s = " (disagrees with previous, ignored)" 83 } 84 return s 85 } 86 87 func describeBranchPrediction(f *Func, b *Block, likely, not int8, prediction BranchPrediction) { 88 f.Config.Warnl(b.Line, "Branch prediction rule %s < %s%s", 89 bllikelies[likely-blMin], bllikelies[not-blMin], describePredictionAgrees(b, prediction)) 90 } 91 92 func likelyadjust(f *Func) { 93 // The values assigned to certain and local only matter 94 // in their rank order. 0 is default, more positive 95 // is less likely. It's possible to assign a negative 96 // unlikeliness (though not currently the case). 97 certain := make([]int8, f.NumBlocks()) // In the long run, all outcomes are at least this bad. Mainly for Exit 98 local := make([]int8, f.NumBlocks()) // for our immediate predecessors. 99 100 nest := loopnestfor(f) 101 po := nest.po 102 b2l := nest.b2l 103 104 for _, b := range po { 105 switch b.Kind { 106 case BlockExit: 107 // Very unlikely. 108 local[b.ID] = blEXIT 109 certain[b.ID] = blEXIT 110 111 // Ret, it depends. 112 case BlockRet, BlockRetJmp: 113 local[b.ID] = blRET 114 certain[b.ID] = blRET 115 116 // Calls. TODO not all calls are equal, names give useful clues. 117 // Any name-based heuristics are only relative to other calls, 118 // and less influential than inferences from loop structure. 119 case BlockCall, BlockDefer: 120 local[b.ID] = blCALL 121 certain[b.ID] = max8(blCALL, certain[b.Succs[0].ID]) 122 123 default: 124 if len(b.Succs) == 1 { 125 certain[b.ID] = certain[b.Succs[0].ID] 126 } else if len(b.Succs) == 2 { 127 // If successor is an unvisited backedge, it's in loop and we don't care. 128 // Its default unlikely is also zero which is consistent with favoring loop edges. 129 // Notice that this can act like a "reset" on unlikeliness at loops; the 130 // default "everything returns" unlikeliness is erased by min with the 131 // backedge likeliness; however a loop with calls on every path will be 132 // tagged with call cost. Net effect is that loop entry is favored. 133 b0 := b.Succs[0].ID 134 b1 := b.Succs[1].ID 135 certain[b.ID] = min8(certain[b0], certain[b1]) 136 137 l := b2l[b.ID] 138 l0 := b2l[b0] 139 l1 := b2l[b1] 140 141 prediction := b.Likely 142 // Weak loop heuristic -- both source and at least one dest are in loops, 143 // and there is a difference in the destinations. 144 // TODO what is best arrangement for nested loops? 145 if l != nil && l0 != l1 { 146 noprediction := false 147 switch { 148 // prefer not to exit loops 149 case l1 == nil: 150 prediction = BranchLikely 151 case l0 == nil: 152 prediction = BranchUnlikely 153 154 // prefer to stay in loop, not exit to outer. 155 case l == l0: 156 prediction = BranchLikely 157 case l == l1: 158 prediction = BranchUnlikely 159 default: 160 noprediction = true 161 } 162 if f.pass.debug > 0 && !noprediction { 163 f.Config.Warnl(b.Line, "Branch prediction rule stay in loop%s", 164 describePredictionAgrees(b, prediction)) 165 } 166 167 } else { 168 // Lacking loop structure, fall back on heuristics. 169 if certain[b1] > certain[b0] { 170 prediction = BranchLikely 171 if f.pass.debug > 0 { 172 describeBranchPrediction(f, b, certain[b0], certain[b1], prediction) 173 } 174 } else if certain[b0] > certain[b1] { 175 prediction = BranchUnlikely 176 if f.pass.debug > 0 { 177 describeBranchPrediction(f, b, certain[b1], certain[b0], prediction) 178 } 179 } else if local[b1] > local[b0] { 180 prediction = BranchLikely 181 if f.pass.debug > 0 { 182 describeBranchPrediction(f, b, local[b0], local[b1], prediction) 183 } 184 } else if local[b0] > local[b1] { 185 prediction = BranchUnlikely 186 if f.pass.debug > 0 { 187 describeBranchPrediction(f, b, local[b1], local[b0], prediction) 188 } 189 } 190 } 191 if b.Likely != prediction { 192 if b.Likely == BranchUnknown { 193 b.Likely = prediction 194 } 195 } 196 } 197 } 198 if f.pass.debug > 2 { 199 f.Config.Warnl(b.Line, "BP: Block %s, local=%s, certain=%s", b, bllikelies[local[b.ID]-blMin], bllikelies[certain[b.ID]-blMin]) 200 } 201 202 } 203 } 204 205 func (l *loop) String() string { 206 return fmt.Sprintf("hdr:%s", l.header) 207 } 208 209 func (l *loop) LongString() string { 210 i := "" 211 o := "" 212 if l.isInner { 213 i = ", INNER" 214 } 215 if l.outer != nil { 216 o = ", o=" + l.outer.header.String() 217 } 218 return fmt.Sprintf("hdr:%s%s%s", l.header, i, o) 219 } 220 221 // nearestOuterLoop returns the outer loop of loop most nearly 222 // containing block b; the header must dominate b. loop itself 223 // is assumed to not be that loop. For acceptable performance, 224 // we're relying on loop nests to not be terribly deep. 225 func (l *loop) nearestOuterLoop(sdom sparseTree, b *Block) *loop { 226 var o *loop 227 for o = l.outer; o != nil && !sdom.isAncestorEq(o.header, b); o = o.outer { 228 } 229 return o 230 } 231 232 func loopnestfor(f *Func) *loopnest { 233 po := postorder(f) 234 dom := dominators(f) 235 sdom := newSparseTree(f, dom) 236 b2l := make([]*loop, f.NumBlocks()) 237 loops := make([]*loop, 0) 238 239 // Reducible-loop-nest-finding. 240 for _, b := range po { 241 if f.pass.debug > 3 { 242 fmt.Printf("loop finding (0) at %s\n", b) 243 } 244 245 var innermost *loop // innermost header reachable from this block 246 247 // IF any successor s of b is in a loop headed by h 248 // AND h dominates b 249 // THEN b is in the loop headed by h. 250 // 251 // Choose the first/innermost such h. 252 // 253 // IF s itself dominates b, the s is a loop header; 254 // and there may be more than one such s. 255 // Since there's at most 2 successors, the inner/outer ordering 256 // between them can be established with simple comparisons. 257 for _, bb := range b.Succs { 258 l := b2l[bb.ID] 259 260 if sdom.isAncestorEq(bb, b) { // Found a loop header 261 if l == nil { 262 l = &loop{header: bb, isInner: true} 263 loops = append(loops, l) 264 b2l[bb.ID] = l 265 l.checkContainsCall(bb) 266 } 267 } else { // Perhaps a loop header is inherited. 268 // is there any loop containing our successor whose 269 // header dominates b? 270 if l != nil && !sdom.isAncestorEq(l.header, b) { 271 l = l.nearestOuterLoop(sdom, b) 272 } 273 } 274 275 if l == nil || innermost == l { 276 continue 277 } 278 279 if innermost == nil { 280 innermost = l 281 continue 282 } 283 284 if sdom.isAncestor(innermost.header, l.header) { 285 sdom.outerinner(innermost, l) 286 innermost = l 287 } else if sdom.isAncestor(l.header, innermost.header) { 288 sdom.outerinner(l, innermost) 289 } 290 } 291 292 if innermost != nil { 293 b2l[b.ID] = innermost 294 innermost.checkContainsCall(b) 295 innermost.nBlocks++ 296 } 297 } 298 if f.pass.debug > 1 && len(loops) > 0 { 299 fmt.Printf("Loops in %s:\n", f.Name) 300 for _, l := range loops { 301 fmt.Printf("%s, b=", l.LongString()) 302 for _, b := range f.Blocks { 303 if b2l[b.ID] == l { 304 fmt.Printf(" %s", b) 305 } 306 } 307 fmt.Print("\n") 308 } 309 fmt.Printf("Nonloop blocks in %s:", f.Name) 310 for _, b := range f.Blocks { 311 if b2l[b.ID] == nil { 312 fmt.Printf(" %s", b) 313 } 314 } 315 fmt.Print("\n") 316 } 317 return &loopnest{f, b2l, po, sdom, loops} 318 }