Home | History | Annotate | Download | only in ssa
      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 }
    463