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