github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/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  	// 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  }