Source file src/cmd/compile/internal/inline/interleaved/interleaved.go

     1  // Copyright 2023 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 interleaved implements the interleaved devirtualization and
     6  // inlining pass.
     7  package interleaved
     8  
     9  import (
    10  	"cmd/compile/internal/base"
    11  	"cmd/compile/internal/devirtualize"
    12  	"cmd/compile/internal/inline"
    13  	"cmd/compile/internal/inline/inlheur"
    14  	"cmd/compile/internal/ir"
    15  	"cmd/compile/internal/pgoir"
    16  	"cmd/compile/internal/typecheck"
    17  	"fmt"
    18  )
    19  
    20  // DevirtualizeAndInlinePackage interleaves devirtualization and inlining on
    21  // all functions within pkg.
    22  func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) {
    23  	if profile != nil && base.Debug.PGODevirtualize > 0 {
    24  		// TODO(mdempsky): Integrate into DevirtualizeAndInlineFunc below.
    25  		ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
    26  			for _, fn := range list {
    27  				devirtualize.ProfileGuided(fn, profile)
    28  			}
    29  		})
    30  		ir.CurFunc = nil
    31  	}
    32  
    33  	if base.Flag.LowerL != 0 {
    34  		inlheur.SetupScoreAdjustments()
    35  	}
    36  
    37  	var inlProfile *pgoir.Profile // copy of profile for inlining
    38  	if base.Debug.PGOInline != 0 {
    39  		inlProfile = profile
    40  	}
    41  
    42  	// First compute inlinability of all functions in the package.
    43  	inline.CanInlineFuncs(pkg.Funcs, inlProfile)
    44  
    45  	// Now we make a second pass to do devirtualization and inlining of
    46  	// calls. Order here should not matter.
    47  	for _, fn := range pkg.Funcs {
    48  		DevirtualizeAndInlineFunc(fn, inlProfile)
    49  	}
    50  
    51  	if base.Flag.LowerL != 0 {
    52  		if base.Debug.DumpInlFuncProps != "" {
    53  			inlheur.DumpFuncProps(nil, base.Debug.DumpInlFuncProps)
    54  		}
    55  		if inlheur.Enabled() {
    56  			inline.PostProcessCallSites(inlProfile)
    57  			inlheur.TearDown()
    58  		}
    59  	}
    60  }
    61  
    62  // DevirtualizeAndInlineFunc interleaves devirtualization and inlining
    63  // on a single function.
    64  func DevirtualizeAndInlineFunc(fn *ir.Func, profile *pgoir.Profile) {
    65  	ir.WithFunc(fn, func() {
    66  		if base.Flag.LowerL != 0 {
    67  			if inlheur.Enabled() && !fn.Wrapper() {
    68  				inlheur.ScoreCalls(fn)
    69  				defer inlheur.ScoreCallsCleanup()
    70  			}
    71  			if base.Debug.DumpInlFuncProps != "" && !fn.Wrapper() {
    72  				inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps)
    73  			}
    74  		}
    75  
    76  		bigCaller := base.Flag.LowerL != 0 && inline.IsBigFunc(fn)
    77  		if bigCaller && base.Flag.LowerM > 1 {
    78  			fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn)
    79  		}
    80  
    81  		match := func(n ir.Node) bool {
    82  			switch n := n.(type) {
    83  			case *ir.CallExpr:
    84  				return true
    85  			case *ir.TailCallStmt:
    86  				n.Call.NoInline = true // can't inline yet
    87  			}
    88  			return false
    89  		}
    90  
    91  		edit := func(n ir.Node) ir.Node {
    92  			call, ok := n.(*ir.CallExpr)
    93  			if !ok { // previously inlined
    94  				return nil
    95  			}
    96  
    97  			devirtualize.StaticCall(call)
    98  			if inlCall := inline.TryInlineCall(fn, call, bigCaller, profile); inlCall != nil {
    99  				return inlCall
   100  			}
   101  			return nil
   102  		}
   103  
   104  		fixpoint(fn, match, edit)
   105  	})
   106  }
   107  
   108  // isTestingBLoop returns true if it matches the node as a
   109  // testing.(*B).Loop. See issue #61515.
   110  func isTestingBLoop(t ir.Node) bool {
   111  	if t.Op() != ir.OFOR {
   112  		return false
   113  	}
   114  	nFor, ok := t.(*ir.ForStmt)
   115  	if !ok || nFor.Cond == nil || nFor.Cond.Op() != ir.OCALLFUNC {
   116  		return false
   117  	}
   118  	n, ok := nFor.Cond.(*ir.CallExpr)
   119  	if !ok || n.Fun == nil || n.Fun.Op() != ir.OMETHEXPR {
   120  		return false
   121  	}
   122  	name := ir.MethodExprName(n.Fun)
   123  	if name == nil {
   124  		return false
   125  	}
   126  	if fSym := name.Sym(); fSym != nil && name.Class == ir.PFUNC && fSym.Pkg != nil &&
   127  		fSym.Name == "(*B).Loop" && fSym.Pkg.Path == "testing" {
   128  		// Attempting to match a function call to testing.(*B).Loop
   129  		return true
   130  	}
   131  	return false
   132  }
   133  
   134  // fixpoint repeatedly edits a function until it stabilizes.
   135  //
   136  // First, fixpoint applies match to every node n within fn. Then it
   137  // iteratively applies edit to each node satisfying match(n).
   138  //
   139  // If edit(n) returns nil, no change is made. Otherwise, the result
   140  // replaces n in fn's body, and fixpoint iterates at least once more.
   141  //
   142  // After an iteration where all edit calls return nil, fixpoint
   143  // returns.
   144  func fixpoint(fn *ir.Func, match func(ir.Node) bool, edit func(ir.Node) ir.Node) {
   145  	// Consider the expression "f(g())". We want to be able to replace
   146  	// "g()" in-place with its inlined representation. But if we first
   147  	// replace "f(...)" with its inlined representation, then "g()" will
   148  	// instead appear somewhere within this new AST.
   149  	//
   150  	// To mitigate this, each matched node n is wrapped in a ParenExpr,
   151  	// so we can reliably replace n in-place by assigning ParenExpr.X.
   152  	// It's safe to use ParenExpr here, because typecheck already
   153  	// removed them all.
   154  
   155  	var parens []*ir.ParenExpr
   156  	var mark func(ir.Node) ir.Node
   157  	mark = func(n ir.Node) ir.Node {
   158  		if _, ok := n.(*ir.ParenExpr); ok {
   159  			return n // already visited n.X before wrapping
   160  		}
   161  
   162  		if isTestingBLoop(n) {
   163  			// No inlining nor devirtualization performed on b.Loop body
   164  			if base.Flag.LowerM > 1 {
   165  				fmt.Printf("%v: skip inlining within testing.B.loop for %v\n", ir.Line(n), n)
   166  			}
   167  			return n
   168  		}
   169  
   170  		ok := match(n)
   171  
   172  		// can't wrap TailCall's child into ParenExpr
   173  		if t, ok := n.(*ir.TailCallStmt); ok {
   174  			ir.EditChildren(t.Call, mark)
   175  		} else {
   176  			ir.EditChildren(n, mark)
   177  		}
   178  
   179  		if ok {
   180  			paren := ir.NewParenExpr(n.Pos(), n)
   181  			paren.SetType(n.Type())
   182  			paren.SetTypecheck(n.Typecheck())
   183  
   184  			parens = append(parens, paren)
   185  			n = paren
   186  		}
   187  
   188  		return n
   189  	}
   190  	ir.EditChildren(fn, mark)
   191  
   192  	// Edit until stable.
   193  	for {
   194  		done := true
   195  
   196  		for i := 0; i < len(parens); i++ { // can't use "range parens" here
   197  			paren := parens[i]
   198  			if new := edit(paren.X); new != nil {
   199  				// Update AST and recursively mark nodes.
   200  				paren.X = new
   201  				ir.EditChildren(new, mark) // mark may append to parens
   202  				done = false
   203  			}
   204  		}
   205  
   206  		if done {
   207  			break
   208  		}
   209  	}
   210  
   211  	// Finally, remove any parens we inserted.
   212  	if len(parens) == 0 {
   213  		return // short circuit
   214  	}
   215  	var unparen func(ir.Node) ir.Node
   216  	unparen = func(n ir.Node) ir.Node {
   217  		if paren, ok := n.(*ir.ParenExpr); ok {
   218  			n = paren.X
   219  		}
   220  		ir.EditChildren(n, unparen)
   221  		return n
   222  	}
   223  	ir.EditChildren(fn, unparen)
   224  }
   225  

View as plain text