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