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 	// 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 }
    522