github.com/bir3/gocompiler@v0.9.2202/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  	containsUnavoidableCall bool // True if all paths through the loop have 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  }
    51  
    52  func checkContainsCall(bb *Block) bool {
    53  	if bb.Kind == BlockDefer {
    54  		return true
    55  	}
    56  	for _, v := range bb.Values {
    57  		if opcodeTable[v.Op].call {
    58  			return true
    59  		}
    60  	}
    61  	return false
    62  }
    63  
    64  type loopnest struct {
    65  	f              *Func
    66  	b2l            []*loop
    67  	po             []*Block
    68  	sdom           SparseTree
    69  	loops          []*loop
    70  	hasIrreducible bool // TODO current treatment of irreducible loops is very flaky, if accurate loops are needed, must punt at function level.
    71  
    72  	// Record which of the lazily initialized fields have actually been initialized.
    73  	initializedChildren, initializedDepth, initializedExits bool
    74  }
    75  
    76  func min8(a, b int8) int8 {
    77  	if a < b {
    78  		return a
    79  	}
    80  	return b
    81  }
    82  
    83  func max8(a, b int8) int8 {
    84  	if a > b {
    85  		return a
    86  	}
    87  	return b
    88  }
    89  
    90  const (
    91  	blDEFAULT = 0
    92  	blMin     = blDEFAULT
    93  	blCALL    = 1
    94  	blRET     = 2
    95  	blEXIT    = 3
    96  )
    97  
    98  var bllikelies = [4]string{"default", "call", "ret", "exit"}
    99  
   100  func describePredictionAgrees(b *Block, prediction BranchPrediction) string {
   101  	s := ""
   102  	if prediction == b.Likely {
   103  		s = " (agrees with previous)"
   104  	} else if b.Likely != BranchUnknown {
   105  		s = " (disagrees with previous, ignored)"
   106  	}
   107  	return s
   108  }
   109  
   110  func describeBranchPrediction(f *Func, b *Block, likely, not int8, prediction BranchPrediction) {
   111  	f.Warnl(b.Pos, "Branch prediction rule %s < %s%s",
   112  		bllikelies[likely-blMin], bllikelies[not-blMin], describePredictionAgrees(b, prediction))
   113  }
   114  
   115  func likelyadjust(f *Func) {
   116  	// The values assigned to certain and local only matter
   117  	// in their rank order.  0 is default, more positive
   118  	// is less likely. It's possible to assign a negative
   119  	// unlikeliness (though not currently the case).
   120  	certain := f.Cache.allocInt8Slice(f.NumBlocks()) // In the long run, all outcomes are at least this bad. Mainly for Exit
   121  	defer f.Cache.freeInt8Slice(certain)
   122  	local := f.Cache.allocInt8Slice(f.NumBlocks()) // for our immediate predecessors.
   123  	defer f.Cache.freeInt8Slice(local)
   124  
   125  	po := f.postorder()
   126  	nest := f.loopnest()
   127  	b2l := nest.b2l
   128  
   129  	for _, b := range po {
   130  		switch b.Kind {
   131  		case BlockExit:
   132  			// Very unlikely.
   133  			local[b.ID] = blEXIT
   134  			certain[b.ID] = blEXIT
   135  
   136  			// Ret, it depends.
   137  		case BlockRet, BlockRetJmp:
   138  			local[b.ID] = blRET
   139  			certain[b.ID] = blRET
   140  
   141  			// Calls. TODO not all calls are equal, names give useful clues.
   142  			// Any name-based heuristics are only relative to other calls,
   143  			// and less influential than inferences from loop structure.
   144  		case BlockDefer:
   145  			local[b.ID] = blCALL
   146  			certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID])
   147  
   148  		default:
   149  			if len(b.Succs) == 1 {
   150  				certain[b.ID] = certain[b.Succs[0].b.ID]
   151  			} else if len(b.Succs) == 2 {
   152  				// If successor is an unvisited backedge, it's in loop and we don't care.
   153  				// Its default unlikely is also zero which is consistent with favoring loop edges.
   154  				// Notice that this can act like a "reset" on unlikeliness at loops; the
   155  				// default "everything returns" unlikeliness is erased by min with the
   156  				// backedge likeliness; however a loop with calls on every path will be
   157  				// tagged with call cost. Net effect is that loop entry is favored.
   158  				b0 := b.Succs[0].b.ID
   159  				b1 := b.Succs[1].b.ID
   160  				certain[b.ID] = min8(certain[b0], certain[b1])
   161  
   162  				l := b2l[b.ID]
   163  				l0 := b2l[b0]
   164  				l1 := b2l[b1]
   165  
   166  				prediction := b.Likely
   167  				// Weak loop heuristic -- both source and at least one dest are in loops,
   168  				// and there is a difference in the destinations.
   169  				// TODO what is best arrangement for nested loops?
   170  				if l != nil && l0 != l1 {
   171  					noprediction := false
   172  					switch {
   173  					// prefer not to exit loops
   174  					case l1 == nil:
   175  						prediction = BranchLikely
   176  					case l0 == nil:
   177  						prediction = BranchUnlikely
   178  
   179  						// prefer to stay in loop, not exit to outer.
   180  					case l == l0:
   181  						prediction = BranchLikely
   182  					case l == l1:
   183  						prediction = BranchUnlikely
   184  					default:
   185  						noprediction = true
   186  					}
   187  					if f.pass.debug > 0 && !noprediction {
   188  						f.Warnl(b.Pos, "Branch prediction rule stay in loop%s",
   189  							describePredictionAgrees(b, prediction))
   190  					}
   191  
   192  				} else {
   193  					// Lacking loop structure, fall back on heuristics.
   194  					if certain[b1] > certain[b0] {
   195  						prediction = BranchLikely
   196  						if f.pass.debug > 0 {
   197  							describeBranchPrediction(f, b, certain[b0], certain[b1], prediction)
   198  						}
   199  					} else if certain[b0] > certain[b1] {
   200  						prediction = BranchUnlikely
   201  						if f.pass.debug > 0 {
   202  							describeBranchPrediction(f, b, certain[b1], certain[b0], prediction)
   203  						}
   204  					} else if local[b1] > local[b0] {
   205  						prediction = BranchLikely
   206  						if f.pass.debug > 0 {
   207  							describeBranchPrediction(f, b, local[b0], local[b1], prediction)
   208  						}
   209  					} else if local[b0] > local[b1] {
   210  						prediction = BranchUnlikely
   211  						if f.pass.debug > 0 {
   212  							describeBranchPrediction(f, b, local[b1], local[b0], prediction)
   213  						}
   214  					}
   215  				}
   216  				if b.Likely != prediction {
   217  					if b.Likely == BranchUnknown {
   218  						b.Likely = prediction
   219  					}
   220  				}
   221  			}
   222  			// Look for calls in the block.  If there is one, make this block unlikely.
   223  			for _, v := range b.Values {
   224  				if opcodeTable[v.Op].call {
   225  					local[b.ID] = blCALL
   226  					certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID])
   227  					break
   228  				}
   229  			}
   230  		}
   231  		if f.pass.debug > 2 {
   232  			f.Warnl(b.Pos, "BP: Block %s, local=%s, certain=%s", b, bllikelies[local[b.ID]-blMin], bllikelies[certain[b.ID]-blMin])
   233  		}
   234  
   235  	}
   236  }
   237  
   238  func (l *loop) String() string {
   239  	return fmt.Sprintf("hdr:%s", l.header)
   240  }
   241  
   242  func (l *loop) LongString() string {
   243  	i := ""
   244  	o := ""
   245  	if l.isInner {
   246  		i = ", INNER"
   247  	}
   248  	if l.outer != nil {
   249  		o = ", o=" + l.outer.header.String()
   250  	}
   251  	return fmt.Sprintf("hdr:%s%s%s", l.header, i, o)
   252  }
   253  
   254  func (l *loop) isWithinOrEq(ll *loop) bool {
   255  	if ll == nil { // nil means whole program
   256  		return true
   257  	}
   258  	for ; l != nil; l = l.outer {
   259  		if l == ll {
   260  			return true
   261  		}
   262  	}
   263  	return false
   264  }
   265  
   266  // nearestOuterLoop returns the outer loop of loop most nearly
   267  // containing block b; the header must dominate b.  loop itself
   268  // is assumed to not be that loop. For acceptable performance,
   269  // we're relying on loop nests to not be terribly deep.
   270  func (l *loop) nearestOuterLoop(sdom SparseTree, b *Block) *loop {
   271  	var o *loop
   272  	for o = l.outer; o != nil && !sdom.IsAncestorEq(o.header, b); o = o.outer {
   273  	}
   274  	return o
   275  }
   276  
   277  func loopnestfor(f *Func) *loopnest {
   278  	po := f.postorder()
   279  	sdom := f.Sdom()
   280  	b2l := make([]*loop, f.NumBlocks())
   281  	loops := make([]*loop, 0)
   282  	visited := f.Cache.allocBoolSlice(f.NumBlocks())
   283  	defer f.Cache.freeBoolSlice(visited)
   284  	sawIrred := false
   285  
   286  	if f.pass.debug > 2 {
   287  		fmt.Printf("loop finding in %s\n", f.Name)
   288  	}
   289  
   290  	// Reducible-loop-nest-finding.
   291  	for _, b := range po {
   292  		if f.pass != nil && f.pass.debug > 3 {
   293  			fmt.Printf("loop finding at %s\n", b)
   294  		}
   295  
   296  		var innermost *loop // innermost header reachable from this block
   297  
   298  		// IF any successor s of b is in a loop headed by h
   299  		// AND h dominates b
   300  		// THEN b is in the loop headed by h.
   301  		//
   302  		// Choose the first/innermost such h.
   303  		//
   304  		// IF s itself dominates b, then s is a loop header;
   305  		// and there may be more than one such s.
   306  		// Since there's at most 2 successors, the inner/outer ordering
   307  		// between them can be established with simple comparisons.
   308  		for _, e := range b.Succs {
   309  			bb := e.b
   310  			l := b2l[bb.ID]
   311  
   312  			if sdom.IsAncestorEq(bb, b) { // Found a loop header
   313  				if f.pass != nil && f.pass.debug > 4 {
   314  					fmt.Printf("loop finding    succ %s of %s is header\n", bb.String(), b.String())
   315  				}
   316  				if l == nil {
   317  					l = &loop{header: bb, isInner: true}
   318  					loops = append(loops, l)
   319  					b2l[bb.ID] = l
   320  				}
   321  			} else if !visited[bb.ID] { // Found an irreducible loop
   322  				sawIrred = true
   323  				if f.pass != nil && f.pass.debug > 4 {
   324  					fmt.Printf("loop finding    succ %s of %s is IRRED, in %s\n", bb.String(), b.String(), f.Name)
   325  				}
   326  			} else if l != nil {
   327  				// TODO handle case where l is irreducible.
   328  				// Perhaps a loop header is inherited.
   329  				// is there any loop containing our successor whose
   330  				// header dominates b?
   331  				if !sdom.IsAncestorEq(l.header, b) {
   332  					l = l.nearestOuterLoop(sdom, b)
   333  				}
   334  				if f.pass != nil && f.pass.debug > 4 {
   335  					if l == nil {
   336  						fmt.Printf("loop finding    succ %s of %s has no loop\n", bb.String(), b.String())
   337  					} else {
   338  						fmt.Printf("loop finding    succ %s of %s provides loop with header %s\n", bb.String(), b.String(), l.header.String())
   339  					}
   340  				}
   341  			} else { // No loop
   342  				if f.pass != nil && f.pass.debug > 4 {
   343  					fmt.Printf("loop finding    succ %s of %s has no loop\n", bb.String(), b.String())
   344  				}
   345  
   346  			}
   347  
   348  			if l == nil || innermost == l {
   349  				continue
   350  			}
   351  
   352  			if innermost == nil {
   353  				innermost = l
   354  				continue
   355  			}
   356  
   357  			if sdom.isAncestor(innermost.header, l.header) {
   358  				sdom.outerinner(innermost, l)
   359  				innermost = l
   360  			} else if sdom.isAncestor(l.header, innermost.header) {
   361  				sdom.outerinner(l, innermost)
   362  			}
   363  		}
   364  
   365  		if innermost != nil {
   366  			b2l[b.ID] = innermost
   367  			innermost.nBlocks++
   368  		}
   369  		visited[b.ID] = true
   370  	}
   371  
   372  	ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops, hasIrreducible: sawIrred}
   373  
   374  	// Calculate containsUnavoidableCall for regalloc
   375  	dominatedByCall := f.Cache.allocBoolSlice(f.NumBlocks())
   376  	defer f.Cache.freeBoolSlice(dominatedByCall)
   377  	for _, b := range po {
   378  		if checkContainsCall(b) {
   379  			dominatedByCall[b.ID] = true
   380  		}
   381  	}
   382  	// Run dfs to find path through the loop that avoids all calls.
   383  	// Such path either escapes loop or return back to header.
   384  	// It isn't enough to have exit not dominated by any call, for example:
   385  	// ... some loop
   386  	// call1   call2
   387  	//   \      /
   388  	//     exit
   389  	// ...
   390  	// exit is not dominated by any call, but we don't have call-free path to it.
   391  	for _, l := range loops {
   392  		// Header contains call.
   393  		if dominatedByCall[l.header.ID] {
   394  			l.containsUnavoidableCall = true
   395  			continue
   396  		}
   397  		callfreepath := false
   398  		tovisit := make([]*Block, 0, len(l.header.Succs))
   399  		// Push all non-loop non-exit successors of header onto toVisit.
   400  		for _, s := range l.header.Succs {
   401  			nb := s.Block()
   402  			// This corresponds to loop with zero iterations.
   403  			if !l.iterationEnd(nb, b2l) {
   404  				tovisit = append(tovisit, nb)
   405  			}
   406  		}
   407  		for len(tovisit) > 0 {
   408  			cur := tovisit[len(tovisit)-1]
   409  			tovisit = tovisit[:len(tovisit)-1]
   410  			if dominatedByCall[cur.ID] {
   411  				continue
   412  			}
   413  			// Record visited in dominatedByCall.
   414  			dominatedByCall[cur.ID] = true
   415  			for _, s := range cur.Succs {
   416  				nb := s.Block()
   417  				if l.iterationEnd(nb, b2l) {
   418  					callfreepath = true
   419  				}
   420  				if !dominatedByCall[nb.ID] {
   421  					tovisit = append(tovisit, nb)
   422  				}
   423  
   424  			}
   425  			if callfreepath {
   426  				break
   427  			}
   428  		}
   429  		if !callfreepath {
   430  			l.containsUnavoidableCall = true
   431  		}
   432  	}
   433  
   434  	// Curious about the loopiness? "-d=ssa/likelyadjust/stats"
   435  	if f.pass != nil && f.pass.stats > 0 && len(loops) > 0 {
   436  		ln.assembleChildren()
   437  		ln.calculateDepths()
   438  		ln.findExits()
   439  
   440  		// Note stats for non-innermost loops are slightly flawed because
   441  		// they don't account for inner loop exits that span multiple levels.
   442  
   443  		for _, l := range loops {
   444  			x := len(l.exits)
   445  			cf := 0
   446  			if !l.containsUnavoidableCall {
   447  				cf = 1
   448  			}
   449  			inner := 0
   450  			if l.isInner {
   451  				inner++
   452  			}
   453  
   454  			f.LogStat("loopstats:",
   455  				l.depth, "depth", x, "exits",
   456  				inner, "is_inner", cf, "always_calls", l.nBlocks, "n_blocks")
   457  		}
   458  	}
   459  
   460  	if f.pass != nil && f.pass.debug > 1 && len(loops) > 0 {
   461  		fmt.Printf("Loops in %s:\n", f.Name)
   462  		for _, l := range loops {
   463  			fmt.Printf("%s, b=", l.LongString())
   464  			for _, b := range f.Blocks {
   465  				if b2l[b.ID] == l {
   466  					fmt.Printf(" %s", b)
   467  				}
   468  			}
   469  			fmt.Print("\n")
   470  		}
   471  		fmt.Printf("Nonloop blocks in %s:", f.Name)
   472  		for _, b := range f.Blocks {
   473  			if b2l[b.ID] == nil {
   474  				fmt.Printf(" %s", b)
   475  			}
   476  		}
   477  		fmt.Print("\n")
   478  	}
   479  	return ln
   480  }
   481  
   482  // assembleChildren initializes the children field of each
   483  // loop in the nest.  Loop A is a child of loop B if A is
   484  // directly nested within B (based on the reducible-loops
   485  // detection above)
   486  func (ln *loopnest) assembleChildren() {
   487  	if ln.initializedChildren {
   488  		return
   489  	}
   490  	for _, l := range ln.loops {
   491  		if l.outer != nil {
   492  			l.outer.children = append(l.outer.children, l)
   493  		}
   494  	}
   495  	ln.initializedChildren = true
   496  }
   497  
   498  // calculateDepths uses the children field of loops
   499  // to determine the nesting depth (outer=1) of each
   500  // loop.  This is helpful for finding exit edges.
   501  func (ln *loopnest) calculateDepths() {
   502  	if ln.initializedDepth {
   503  		return
   504  	}
   505  	ln.assembleChildren()
   506  	for _, l := range ln.loops {
   507  		if l.outer == nil {
   508  			l.setDepth(1)
   509  		}
   510  	}
   511  	ln.initializedDepth = true
   512  }
   513  
   514  // findExits uses loop depth information to find the
   515  // exits from a loop.
   516  func (ln *loopnest) findExits() {
   517  	if ln.initializedExits {
   518  		return
   519  	}
   520  	ln.calculateDepths()
   521  	b2l := ln.b2l
   522  	for _, b := range ln.po {
   523  		l := b2l[b.ID]
   524  		if l != nil && len(b.Succs) == 2 {
   525  			sl := b2l[b.Succs[0].b.ID]
   526  			if recordIfExit(l, sl, b.Succs[0].b) {
   527  				continue
   528  			}
   529  			sl = b2l[b.Succs[1].b.ID]
   530  			if recordIfExit(l, sl, b.Succs[1].b) {
   531  				continue
   532  			}
   533  		}
   534  	}
   535  	ln.initializedExits = true
   536  }
   537  
   538  // depth returns the loop nesting level of block b.
   539  func (ln *loopnest) depth(b ID) int16 {
   540  	if l := ln.b2l[b]; l != nil {
   541  		return l.depth
   542  	}
   543  	return 0
   544  }
   545  
   546  // recordIfExit checks sl (the loop containing b) to see if it
   547  // is outside of loop l, and if so, records b as an exit block
   548  // from l and returns true.
   549  func recordIfExit(l, sl *loop, b *Block) bool {
   550  	if sl != l {
   551  		if sl == nil || sl.depth <= l.depth {
   552  			l.exits = append(l.exits, b)
   553  			return true
   554  		}
   555  		// sl is not nil, and is deeper than l
   556  		// it's possible for this to be a goto into an irreducible loop made from gotos.
   557  		for sl.depth > l.depth {
   558  			sl = sl.outer
   559  		}
   560  		if sl != l {
   561  			l.exits = append(l.exits, b)
   562  			return true
   563  		}
   564  	}
   565  	return false
   566  }
   567  
   568  func (l *loop) setDepth(d int16) {
   569  	l.depth = d
   570  	for _, c := range l.children {
   571  		c.setDepth(d + 1)
   572  	}
   573  }
   574  
   575  // iterationEnd checks if block b ends iteration of loop l.
   576  // Ending iteration means either escaping to outer loop/code or
   577  // going back to header
   578  func (l *loop) iterationEnd(b *Block, b2l []*loop) bool {
   579  	return b == l.header || b2l[b.ID] == nil || (b2l[b.ID] != l && b2l[b.ID].depth <= l.depth)
   580  }