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  }