1  
     2  
     3  
     4  
     5  
     6  
     7  
     8  
     9  
    10  
    11  
    12  
    13  
    14  
    15  
    16  
    17  
    18  
    19  
    20  
    21  
    22  
    23  
    24  
    25  
    26  
    27  package inline
    28  
    29  import (
    30  	"fmt"
    31  	"go/constant"
    32  	"internal/buildcfg"
    33  	"strconv"
    34  	"strings"
    35  
    36  	"cmd/compile/internal/base"
    37  	"cmd/compile/internal/inline/inlheur"
    38  	"cmd/compile/internal/ir"
    39  	"cmd/compile/internal/logopt"
    40  	"cmd/compile/internal/pgoir"
    41  	"cmd/compile/internal/typecheck"
    42  	"cmd/compile/internal/types"
    43  	"cmd/internal/obj"
    44  	"cmd/internal/pgo"
    45  	"cmd/internal/src"
    46  )
    47  
    48  
    49  const (
    50  	inlineMaxBudget       = 80
    51  	inlineExtraAppendCost = 0
    52  	
    53  	inlineExtraCallCost  = 57              
    54  	inlineParamCallCost  = 17              
    55  	inlineExtraPanicCost = 1               
    56  	inlineExtraThrowCost = inlineMaxBudget 
    57  
    58  	inlineBigFunctionNodes      = 5000                 
    59  	inlineBigFunctionMaxCost    = 20                   
    60  	inlineClosureCalledOnceCost = 10 * inlineMaxBudget 
    61  )
    62  
    63  var (
    64  	
    65  	
    66  	candHotCalleeMap = make(map[*pgoir.IRNode]struct{})
    67  
    68  	
    69  	hasHotCall = make(map[*ir.Func]struct{})
    70  
    71  	
    72  	
    73  	candHotEdgeMap = make(map[pgoir.CallSiteInfo]struct{})
    74  
    75  	
    76  	inlineHotCallSiteThresholdPercent float64
    77  
    78  	
    79  	
    80  	
    81  	
    82  	inlineCDFHotCallSiteThresholdPercent = float64(99)
    83  
    84  	
    85  	inlineHotMaxBudget int32 = 2000
    86  )
    87  
    88  func IsPgoHotFunc(fn *ir.Func, profile *pgoir.Profile) bool {
    89  	if profile == nil {
    90  		return false
    91  	}
    92  	if n, ok := profile.WeightedCG.IRNodes[ir.LinkFuncName(fn)]; ok {
    93  		_, ok := candHotCalleeMap[n]
    94  		return ok
    95  	}
    96  	return false
    97  }
    98  
    99  func HasPgoHotInline(fn *ir.Func) bool {
   100  	_, has := hasHotCall[fn]
   101  	return has
   102  }
   103  
   104  
   105  func PGOInlinePrologue(p *pgoir.Profile) {
   106  	if base.Debug.PGOInlineCDFThreshold != "" {
   107  		if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 {
   108  			inlineCDFHotCallSiteThresholdPercent = s
   109  		} else {
   110  			base.Fatalf("invalid PGOInlineCDFThreshold, must be between 0 and 100")
   111  		}
   112  	}
   113  	var hotCallsites []pgo.NamedCallEdge
   114  	inlineHotCallSiteThresholdPercent, hotCallsites = hotNodesFromCDF(p)
   115  	if base.Debug.PGODebug > 0 {
   116  		fmt.Printf("hot-callsite-thres-from-CDF=%v\n", inlineHotCallSiteThresholdPercent)
   117  	}
   118  
   119  	if x := base.Debug.PGOInlineBudget; x != 0 {
   120  		inlineHotMaxBudget = int32(x)
   121  	}
   122  
   123  	for _, n := range hotCallsites {
   124  		
   125  		if callee := p.WeightedCG.IRNodes[n.CalleeName]; callee != nil {
   126  			candHotCalleeMap[callee] = struct{}{}
   127  		}
   128  		
   129  		if caller := p.WeightedCG.IRNodes[n.CallerName]; caller != nil && caller.AST != nil {
   130  			csi := pgoir.CallSiteInfo{LineOffset: n.CallSiteOffset, Caller: caller.AST}
   131  			candHotEdgeMap[csi] = struct{}{}
   132  		}
   133  	}
   134  
   135  	if base.Debug.PGODebug >= 3 {
   136  		fmt.Printf("hot-cg before inline in dot format:")
   137  		p.PrintWeightedCallGraphDOT(inlineHotCallSiteThresholdPercent)
   138  	}
   139  }
   140  
   141  
   142  
   143  
   144  
   145  
   146  
   147  func hotNodesFromCDF(p *pgoir.Profile) (float64, []pgo.NamedCallEdge) {
   148  	cum := int64(0)
   149  	for i, n := range p.NamedEdgeMap.ByWeight {
   150  		w := p.NamedEdgeMap.Weight[n]
   151  		cum += w
   152  		if pgo.WeightInPercentage(cum, p.TotalWeight) > inlineCDFHotCallSiteThresholdPercent {
   153  			
   154  			
   155  			
   156  			return pgo.WeightInPercentage(w, p.TotalWeight), p.NamedEdgeMap.ByWeight[:i+1]
   157  		}
   158  	}
   159  	return 0, p.NamedEdgeMap.ByWeight
   160  }
   161  
   162  
   163  func CanInlineFuncs(funcs []*ir.Func, profile *pgoir.Profile) {
   164  	if profile != nil {
   165  		PGOInlinePrologue(profile)
   166  	}
   167  
   168  	if base.Flag.LowerL == 0 {
   169  		return
   170  	}
   171  
   172  	ir.VisitFuncsBottomUp(funcs, func(funcs []*ir.Func, recursive bool) {
   173  		for _, fn := range funcs {
   174  			CanInline(fn, profile)
   175  			if inlheur.Enabled() {
   176  				analyzeFuncProps(fn, profile)
   177  			}
   178  		}
   179  	})
   180  }
   181  
   182  
   183  
   184  
   185  
   186  
   187  
   188  
   189  func inlineBudget(fn *ir.Func, profile *pgoir.Profile, relaxed bool, verbose bool) int32 {
   190  	
   191  	budget := int32(inlineMaxBudget)
   192  	if IsPgoHotFunc(fn, profile) {
   193  		budget = inlineHotMaxBudget
   194  		if verbose {
   195  			fmt.Printf("hot-node enabled increased budget=%v for func=%v\n", budget, ir.PkgFuncName(fn))
   196  		}
   197  	}
   198  	if relaxed {
   199  		budget += inlheur.BudgetExpansion(inlineMaxBudget)
   200  	}
   201  	if fn.ClosureParent != nil {
   202  		
   203  		budget = max(budget, inlineClosureCalledOnceCost)
   204  	}
   205  	return budget
   206  }
   207  
   208  
   209  
   210  
   211  func CanInline(fn *ir.Func, profile *pgoir.Profile) {
   212  	if fn.Nname == nil {
   213  		base.Fatalf("CanInline no nname %+v", fn)
   214  	}
   215  
   216  	var reason string 
   217  	if base.Flag.LowerM > 1 || logopt.Enabled() {
   218  		defer func() {
   219  			if reason != "" {
   220  				if base.Flag.LowerM > 1 {
   221  					fmt.Printf("%v: cannot inline %v: %s\n", ir.Line(fn), fn.Nname, reason)
   222  				}
   223  				if logopt.Enabled() {
   224  					logopt.LogOpt(fn.Pos(), "cannotInlineFunction", "inline", ir.FuncName(fn), reason)
   225  				}
   226  			}
   227  		}()
   228  	}
   229  
   230  	reason = InlineImpossible(fn)
   231  	if reason != "" {
   232  		return
   233  	}
   234  	if fn.Typecheck() == 0 {
   235  		base.Fatalf("CanInline on non-typechecked function %v", fn)
   236  	}
   237  
   238  	n := fn.Nname
   239  	if n.Func.InlinabilityChecked() {
   240  		return
   241  	}
   242  	defer n.Func.SetInlinabilityChecked(true)
   243  
   244  	cc := int32(inlineExtraCallCost)
   245  	if base.Flag.LowerL == 4 {
   246  		cc = 1 
   247  	}
   248  
   249  	
   250  	relaxed := inlheur.Enabled()
   251  
   252  	
   253  	budget := inlineBudget(fn, profile, relaxed, base.Debug.PGODebug > 0)
   254  
   255  	
   256  	
   257  	
   258  	
   259  	
   260  	
   261  	
   262  	
   263  
   264  	visitor := hairyVisitor{
   265  		curFunc:       fn,
   266  		isBigFunc:     IsBigFunc(fn),
   267  		budget:        budget,
   268  		maxBudget:     budget,
   269  		extraCallCost: cc,
   270  		profile:       profile,
   271  	}
   272  	if visitor.tooHairy(fn) {
   273  		reason = visitor.reason
   274  		return
   275  	}
   276  
   277  	n.Func.Inl = &ir.Inline{
   278  		Cost:            budget - visitor.budget,
   279  		Dcl:             pruneUnusedAutos(n.Func.Dcl, &visitor),
   280  		HaveDcl:         true,
   281  		CanDelayResults: canDelayResults(fn),
   282  	}
   283  	if base.Flag.LowerM != 0 || logopt.Enabled() {
   284  		noteInlinableFunc(n, fn, budget-visitor.budget)
   285  	}
   286  }
   287  
   288  
   289  
   290  func noteInlinableFunc(n *ir.Name, fn *ir.Func, cost int32) {
   291  	if base.Flag.LowerM > 1 {
   292  		fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, cost, fn.Type(), fn.Body)
   293  	} else if base.Flag.LowerM != 0 {
   294  		fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
   295  	}
   296  	
   297  	if logopt.Enabled() {
   298  		logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", cost))
   299  	}
   300  }
   301  
   302  
   303  
   304  func InlineImpossible(fn *ir.Func) string {
   305  	var reason string 
   306  	if fn.Nname == nil {
   307  		reason = "no name"
   308  		return reason
   309  	}
   310  
   311  	
   312  	if fn.Pragma&ir.Noinline != 0 {
   313  		reason = "marked go:noinline"
   314  		return reason
   315  	}
   316  
   317  	
   318  	if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
   319  		reason = "marked go:norace with -race compilation"
   320  		return reason
   321  	}
   322  
   323  	
   324  	if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
   325  		reason = "marked go:nocheckptr"
   326  		return reason
   327  	}
   328  
   329  	
   330  	
   331  	if fn.Pragma&ir.CgoUnsafeArgs != 0 {
   332  		reason = "marked go:cgo_unsafe_args"
   333  		return reason
   334  	}
   335  
   336  	
   337  	
   338  	
   339  	
   340  	
   341  	
   342  	if fn.Pragma&ir.UintptrKeepAlive != 0 {
   343  		reason = "marked as having a keep-alive uintptr argument"
   344  		return reason
   345  	}
   346  
   347  	
   348  	
   349  	if fn.Pragma&ir.UintptrEscapes != 0 {
   350  		reason = "marked as having an escaping uintptr argument"
   351  		return reason
   352  	}
   353  
   354  	
   355  	
   356  	
   357  	if fn.Pragma&ir.Yeswritebarrierrec != 0 {
   358  		reason = "marked go:yeswritebarrierrec"
   359  		return reason
   360  	}
   361  
   362  	
   363  	
   364  	if len(fn.Body) == 0 && !typecheck.HaveInlineBody(fn) {
   365  		reason = "no function body"
   366  		return reason
   367  	}
   368  
   369  	return ""
   370  }
   371  
   372  
   373  
   374  func canDelayResults(fn *ir.Func) bool {
   375  	
   376  	
   377  	
   378  	
   379  
   380  	nreturns := 0
   381  	ir.VisitList(fn.Body, func(n ir.Node) {
   382  		if n, ok := n.(*ir.ReturnStmt); ok {
   383  			nreturns++
   384  			if len(n.Results) == 0 {
   385  				nreturns++ 
   386  			}
   387  		}
   388  	})
   389  
   390  	if nreturns != 1 {
   391  		return false 
   392  	}
   393  
   394  	
   395  	for _, param := range fn.Type().Results() {
   396  		if sym := param.Sym; sym != nil && !sym.IsBlank() {
   397  			return false 
   398  		}
   399  	}
   400  
   401  	return true
   402  }
   403  
   404  
   405  
   406  type hairyVisitor struct {
   407  	
   408  	curFunc       *ir.Func
   409  	isBigFunc     bool
   410  	budget        int32
   411  	maxBudget     int32
   412  	reason        string
   413  	extraCallCost int32
   414  	usedLocals    ir.NameSet
   415  	do            func(ir.Node) bool
   416  	profile       *pgoir.Profile
   417  }
   418  
   419  func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
   420  	v.do = v.doNode 
   421  	if ir.DoChildren(fn, v.do) {
   422  		return true
   423  	}
   424  	if v.budget < 0 {
   425  		v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", v.maxBudget-v.budget, v.maxBudget)
   426  		return true
   427  	}
   428  	return false
   429  }
   430  
   431  
   432  
   433  func (v *hairyVisitor) doNode(n ir.Node) bool {
   434  	if n == nil {
   435  		return false
   436  	}
   437  opSwitch:
   438  	switch n.Op() {
   439  	
   440  	case ir.OCALLFUNC:
   441  		n := n.(*ir.CallExpr)
   442  		var cheap bool
   443  		if n.Fun.Op() == ir.ONAME {
   444  			name := n.Fun.(*ir.Name)
   445  			if name.Class == ir.PFUNC {
   446  				s := name.Sym()
   447  				fn := s.Name
   448  				switch s.Pkg.Path {
   449  				case "internal/abi":
   450  					switch fn {
   451  					case "NoEscape":
   452  						
   453  						
   454  						
   455  						cheap = true
   456  					}
   457  					if strings.HasPrefix(fn, "EscapeNonString[") {
   458  						
   459  						
   460  						cheap = true
   461  					}
   462  				case "internal/runtime/sys":
   463  					switch fn {
   464  					case "GetCallerPC", "GetCallerSP":
   465  						
   466  						
   467  						
   468  						v.reason = "call to " + fn
   469  						return true
   470  					}
   471  				case "go.runtime":
   472  					switch fn {
   473  					case "throw":
   474  						
   475  						v.budget -= inlineExtraThrowCost
   476  						break opSwitch
   477  					case "panicrangestate":
   478  						cheap = true
   479  					}
   480  				}
   481  			}
   482  			
   483  			
   484  			
   485  			
   486  			
   487  			
   488  			
   489  			
   490  			
   491  			
   492  			if isAtomicCoverageCounterUpdate(n) {
   493  				return false
   494  			}
   495  		}
   496  		if n.Fun.Op() == ir.OMETHEXPR {
   497  			if meth := ir.MethodExprName(n.Fun); meth != nil {
   498  				if fn := meth.Func; fn != nil {
   499  					s := fn.Sym()
   500  					if types.RuntimeSymName(s) == "heapBits.nextArena" {
   501  						
   502  						
   503  						
   504  						cheap = true
   505  					}
   506  					
   507  					
   508  					
   509  					
   510  					if base.Ctxt.Arch.CanMergeLoads && s.Pkg.Path == "encoding/binary" {
   511  						switch s.Name {
   512  						case "littleEndian.Uint64", "littleEndian.Uint32", "littleEndian.Uint16",
   513  							"bigEndian.Uint64", "bigEndian.Uint32", "bigEndian.Uint16",
   514  							"littleEndian.PutUint64", "littleEndian.PutUint32", "littleEndian.PutUint16",
   515  							"bigEndian.PutUint64", "bigEndian.PutUint32", "bigEndian.PutUint16",
   516  							"littleEndian.AppendUint64", "littleEndian.AppendUint32", "littleEndian.AppendUint16",
   517  							"bigEndian.AppendUint64", "bigEndian.AppendUint32", "bigEndian.AppendUint16":
   518  							cheap = true
   519  						}
   520  					}
   521  				}
   522  			}
   523  		}
   524  
   525  		
   526  		
   527  		extraCost := v.extraCallCost
   528  
   529  		if n.Fun.Op() == ir.ONAME {
   530  			name := n.Fun.(*ir.Name)
   531  			if name.Class == ir.PFUNC {
   532  				
   533  				
   534  				
   535  				
   536  				if base.Ctxt.Arch.CanMergeLoads && name.Sym().Pkg.Path == "internal/byteorder" {
   537  					switch name.Sym().Name {
   538  					case "LEUint64", "LEUint32", "LEUint16",
   539  						"BEUint64", "BEUint32", "BEUint16",
   540  						"LEPutUint64", "LEPutUint32", "LEPutUint16",
   541  						"BEPutUint64", "BEPutUint32", "BEPutUint16",
   542  						"LEAppendUint64", "LEAppendUint32", "LEAppendUint16",
   543  						"BEAppendUint64", "BEAppendUint32", "BEAppendUint16":
   544  						cheap = true
   545  					}
   546  				}
   547  			}
   548  			if name.Class == ir.PPARAM || name.Class == ir.PAUTOHEAP && name.IsClosureVar() {
   549  				extraCost = min(extraCost, inlineParamCallCost)
   550  			}
   551  		}
   552  
   553  		if cheap {
   554  			break 
   555  		}
   556  
   557  		if ir.IsIntrinsicCall(n) {
   558  			
   559  			break
   560  		}
   561  
   562  		if callee := inlCallee(v.curFunc, n.Fun, v.profile, false); callee != nil && typecheck.HaveInlineBody(callee) {
   563  			
   564  			
   565  			
   566  			if ok, _, _ := canInlineCallExpr(v.curFunc, n, callee, v.isBigFunc, false, false); ok {
   567  				
   568  				
   569  				
   570  				
   571  				
   572  				
   573  				
   574  				
   575  				
   576  				
   577  				
   578  				
   579  				
   580  				
   581  				v.budget -= callee.Inl.Cost
   582  				break
   583  			}
   584  		}
   585  
   586  		
   587  		v.budget -= extraCost
   588  
   589  	case ir.OCALLMETH:
   590  		base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
   591  
   592  	
   593  	case ir.OCALL, ir.OCALLINTER:
   594  		
   595  		v.budget -= v.extraCallCost
   596  
   597  	case ir.OPANIC:
   598  		n := n.(*ir.UnaryExpr)
   599  		if n.X.Op() == ir.OCONVIFACE && n.X.(*ir.ConvExpr).Implicit() {
   600  			
   601  			
   602  			
   603  			v.budget++
   604  		}
   605  		v.budget -= inlineExtraPanicCost
   606  
   607  	case ir.ORECOVER:
   608  		
   609  		v.reason = "call to recover"
   610  		return true
   611  
   612  	case ir.OCLOSURE:
   613  		if base.Debug.InlFuncsWithClosures == 0 {
   614  			v.reason = "not inlining functions with closures"
   615  			return true
   616  		}
   617  
   618  		
   619  		
   620  		
   621  		
   622  		
   623  		
   624  		v.budget -= 15
   625  
   626  	case ir.OGO, ir.ODEFER, ir.OTAILCALL:
   627  		v.reason = "unhandled op " + n.Op().String()
   628  		return true
   629  
   630  	case ir.OAPPEND:
   631  		v.budget -= inlineExtraAppendCost
   632  
   633  	case ir.OADDR:
   634  		n := n.(*ir.AddrExpr)
   635  		
   636  		if dot, ok := n.X.(*ir.SelectorExpr); ok && (dot.Op() == ir.ODOT || dot.Op() == ir.ODOTPTR) {
   637  			if _, ok := dot.X.(*ir.Name); ok && dot.Selection.Offset == 0 {
   638  				v.budget += 2 
   639  			}
   640  		}
   641  
   642  	case ir.ODEREF:
   643  		
   644  		n := n.(*ir.StarExpr)
   645  
   646  		ptr := n.X
   647  		for ptr.Op() == ir.OCONVNOP {
   648  			ptr = ptr.(*ir.ConvExpr).X
   649  		}
   650  		if ptr.Op() == ir.OADDR {
   651  			v.budget += 1 
   652  		}
   653  
   654  	case ir.OCONVNOP:
   655  		
   656  		v.budget++ 
   657  
   658  	case ir.OFALL, ir.OTYPE:
   659  		
   660  		return false
   661  
   662  	case ir.OIF:
   663  		n := n.(*ir.IfStmt)
   664  		if ir.IsConst(n.Cond, constant.Bool) {
   665  			
   666  			if doList(n.Init(), v.do) {
   667  				return true
   668  			}
   669  			if ir.BoolVal(n.Cond) {
   670  				return doList(n.Body, v.do)
   671  			} else {
   672  				return doList(n.Else, v.do)
   673  			}
   674  		}
   675  
   676  	case ir.ONAME:
   677  		n := n.(*ir.Name)
   678  		if n.Class == ir.PAUTO {
   679  			v.usedLocals.Add(n)
   680  		}
   681  
   682  	case ir.OBLOCK:
   683  		
   684  		
   685  		
   686  		v.budget++
   687  
   688  	case ir.OMETHVALUE, ir.OSLICELIT:
   689  		v.budget-- 
   690  
   691  	case ir.OMETHEXPR:
   692  		v.budget++ 
   693  
   694  	case ir.OAS2:
   695  		n := n.(*ir.AssignListStmt)
   696  
   697  		
   698  		
   699  		
   700  		
   701  		
   702  		
   703  		
   704  		
   705  		
   706  		
   707  		
   708  		
   709  		
   710  		
   711  		
   712  		
   713  		
   714  		if len(n.Rhs) > 0 {
   715  			if init := n.Rhs[0].Init(); len(init) == 1 {
   716  				if _, ok := init[0].(*ir.AssignListStmt); ok {
   717  					
   718  					
   719  					
   720  					
   721  					v.budget += 4*int32(len(n.Lhs)) + 1
   722  				}
   723  			}
   724  		}
   725  
   726  	case ir.OAS:
   727  		
   728  		
   729  		
   730  		
   731  		
   732  		
   733  		
   734  		
   735  		
   736  		
   737  		n := n.(*ir.AssignStmt)
   738  		if n.X.Op() == ir.OINDEX && isIndexingCoverageCounter(n.X) {
   739  			return false
   740  		}
   741  
   742  	case ir.OSLICE, ir.OSLICEARR, ir.OSLICESTR, ir.OSLICE3, ir.OSLICE3ARR:
   743  		n := n.(*ir.SliceExpr)
   744  
   745  		
   746  		if n.Low != nil && n.Low.Op() == ir.OLITERAL && ir.Int64Val(n.Low) == 0 {
   747  			v.budget++
   748  		}
   749  		if n.High != nil && n.High.Op() == ir.OLEN && n.High.(*ir.UnaryExpr).X == n.X {
   750  			v.budget += 2
   751  		}
   752  	}
   753  
   754  	v.budget--
   755  
   756  	
   757  	if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
   758  		v.reason = "too expensive"
   759  		return true
   760  	}
   761  
   762  	return ir.DoChildren(n, v.do)
   763  }
   764  
   765  
   766  
   767  
   768  func IsBigFunc(fn *ir.Func) bool {
   769  	budget := inlineBigFunctionNodes
   770  	return ir.Any(fn, func(n ir.Node) bool {
   771  		
   772  		
   773  		if n, ok := n.(*ir.AssignListStmt); ok && n.Op() == ir.OAS2 && len(n.Rhs) > 0 {
   774  			if init := n.Rhs[0].Init(); len(init) == 1 {
   775  				if _, ok := init[0].(*ir.AssignListStmt); ok {
   776  					budget += 4*len(n.Lhs) + 1
   777  				}
   778  			}
   779  		}
   780  
   781  		budget--
   782  		return budget <= 0
   783  	})
   784  }
   785  
   786  
   787  
   788  
   789  func inlineCallCheck(callerfn *ir.Func, call *ir.CallExpr) (bool, bool) {
   790  	if base.Flag.LowerL == 0 {
   791  		return false, false
   792  	}
   793  	if call.Op() != ir.OCALLFUNC {
   794  		return false, false
   795  	}
   796  	if call.GoDefer || call.NoInline {
   797  		return false, false
   798  	}
   799  
   800  	
   801  	
   802  	if base.Debug.Checkptr != 0 && call.Fun.Op() == ir.OMETHEXPR {
   803  		if method := ir.MethodExprName(call.Fun); method != nil {
   804  			switch types.ReflectSymName(method.Sym()) {
   805  			case "Value.UnsafeAddr", "Value.Pointer":
   806  				return false, false
   807  			}
   808  		}
   809  	}
   810  
   811  	
   812  	
   813  	if fn := ir.StaticCalleeName(call.Fun); fn != nil && fn.Sym().Pkg.Path == "internal/abi" &&
   814  		strings.HasPrefix(fn.Sym().Name, "EscapeNonString[") {
   815  		return false, true
   816  	}
   817  
   818  	if ir.IsIntrinsicCall(call) {
   819  		return false, true
   820  	}
   821  	return true, false
   822  }
   823  
   824  
   825  
   826  
   827  func InlineCallTarget(callerfn *ir.Func, call *ir.CallExpr, profile *pgoir.Profile) *ir.Func {
   828  	if mightInline, _ := inlineCallCheck(callerfn, call); !mightInline {
   829  		return nil
   830  	}
   831  	return inlCallee(callerfn, call.Fun, profile, true)
   832  }
   833  
   834  
   835  
   836  func TryInlineCall(callerfn *ir.Func, call *ir.CallExpr, bigCaller bool, profile *pgoir.Profile, closureCalledOnce bool) *ir.InlinedCallExpr {
   837  	mightInline, isIntrinsic := inlineCallCheck(callerfn, call)
   838  
   839  	
   840  	if (mightInline || isIntrinsic) && base.Flag.LowerM > 3 {
   841  		fmt.Printf("%v:call to func %+v\n", ir.Line(call), call.Fun)
   842  	}
   843  	if !mightInline {
   844  		return nil
   845  	}
   846  
   847  	if fn := inlCallee(callerfn, call.Fun, profile, false); fn != nil && typecheck.HaveInlineBody(fn) {
   848  		return mkinlcall(callerfn, call, fn, bigCaller, closureCalledOnce)
   849  	}
   850  	return nil
   851  }
   852  
   853  
   854  
   855  
   856  func inlCallee(caller *ir.Func, fn ir.Node, profile *pgoir.Profile, resolveOnly bool) (res *ir.Func) {
   857  	fn = ir.StaticValue(fn)
   858  	switch fn.Op() {
   859  	case ir.OMETHEXPR:
   860  		fn := fn.(*ir.SelectorExpr)
   861  		n := ir.MethodExprName(fn)
   862  		
   863  		
   864  		
   865  		if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
   866  			return nil
   867  		}
   868  		return n.Func
   869  	case ir.ONAME:
   870  		fn := fn.(*ir.Name)
   871  		if fn.Class == ir.PFUNC {
   872  			return fn.Func
   873  		}
   874  	case ir.OCLOSURE:
   875  		fn := fn.(*ir.ClosureExpr)
   876  		c := fn.Func
   877  		if len(c.ClosureVars) != 0 && c.ClosureVars[0].Outer.Curfn != caller {
   878  			return nil 
   879  		}
   880  		if !resolveOnly {
   881  			CanInline(c, profile)
   882  		}
   883  		return c
   884  	}
   885  	return nil
   886  }
   887  
   888  var inlgen int
   889  
   890  
   891  
   892  var SSADumpInline = func(*ir.Func) {}
   893  
   894  
   895  
   896  var InlineCall = func(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCallExpr {
   897  	base.Fatalf("inline.InlineCall not overridden")
   898  	panic("unreachable")
   899  }
   900  
   901  
   902  
   903  
   904  
   905  
   906  
   907  
   908  func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCalledOnce bool) (bool, int32, int32, bool) {
   909  	maxCost := int32(inlineMaxBudget)
   910  
   911  	if bigCaller {
   912  		
   913  		
   914  		maxCost = inlineBigFunctionMaxCost
   915  	}
   916  
   917  	if callee.ClosureParent != nil {
   918  		maxCost *= 2           
   919  		if closureCalledOnce { 
   920  			maxCost = max(maxCost, inlineClosureCalledOnceCost)
   921  		}
   922  	}
   923  
   924  	metric := callee.Inl.Cost
   925  	if inlheur.Enabled() {
   926  		score, ok := inlheur.GetCallSiteScore(caller, n)
   927  		if ok {
   928  			metric = int32(score)
   929  		}
   930  	}
   931  
   932  	lineOffset := pgoir.NodeLineOffset(n, caller)
   933  	csi := pgoir.CallSiteInfo{LineOffset: lineOffset, Caller: caller}
   934  	_, hot := candHotEdgeMap[csi]
   935  
   936  	if metric <= maxCost {
   937  		
   938  		return true, 0, metric, hot
   939  	}
   940  
   941  	
   942  	
   943  
   944  	if !hot {
   945  		
   946  		return false, maxCost, metric, false
   947  	}
   948  
   949  	
   950  
   951  	if bigCaller {
   952  		if base.Debug.PGODebug > 0 {
   953  			fmt.Printf("hot-big check disallows inlining for call %s (cost %d) at %v in big function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
   954  		}
   955  		return false, maxCost, metric, false
   956  	}
   957  
   958  	if metric > inlineHotMaxBudget {
   959  		return false, inlineHotMaxBudget, metric, false
   960  	}
   961  
   962  	if !base.PGOHash.MatchPosWithInfo(n.Pos(), "inline", nil) {
   963  		
   964  		return false, maxCost, metric, false
   965  	}
   966  
   967  	if base.Debug.PGODebug > 0 {
   968  		fmt.Printf("hot-budget check allows inlining for call %s (cost %d) at %v in function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
   969  	}
   970  
   971  	return true, 0, metric, hot
   972  }
   973  
   974  
   975  func parsePos(pos src.XPos, posTmp []src.Pos) ([]src.Pos, src.Pos) {
   976  	ctxt := base.Ctxt
   977  	ctxt.AllPos(pos, func(p src.Pos) {
   978  		posTmp = append(posTmp, p)
   979  	})
   980  	l := len(posTmp) - 1
   981  	return posTmp[:l], posTmp[l]
   982  }
   983  
   984  
   985  
   986  
   987  
   988  
   989  
   990  
   991  func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCaller, closureCalledOnce bool, log bool) (bool, int32, bool) {
   992  	if callee.Inl == nil {
   993  		
   994  		if log && logopt.Enabled() {
   995  			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
   996  				fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(callee)))
   997  		}
   998  		return false, 0, false
   999  	}
  1000  
  1001  	ok, maxCost, callSiteScore, hot := inlineCostOK(n, callerfn, callee, bigCaller, closureCalledOnce)
  1002  	if !ok {
  1003  		
  1004  		if log && logopt.Enabled() {
  1005  			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
  1006  				fmt.Sprintf("cost %d of %s exceeds max caller cost %d", callee.Inl.Cost, ir.PkgFuncName(callee), maxCost))
  1007  		}
  1008  		return false, 0, false
  1009  	}
  1010  
  1011  	callees, calleeInner := parsePos(n.Pos(), make([]src.Pos, 0, 10))
  1012  
  1013  	for _, p := range callees {
  1014  		if p.Line() == calleeInner.Line() && p.Col() == calleeInner.Col() && p.AbsFilename() == calleeInner.AbsFilename() {
  1015  			if log && logopt.Enabled() {
  1016  				logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
  1017  			}
  1018  			return false, 0, false
  1019  		}
  1020  	}
  1021  
  1022  	if base.Flag.Cfg.Instrumenting && types.IsNoInstrumentPkg(callee.Sym().Pkg) {
  1023  		
  1024  		
  1025  		
  1026  		
  1027  		
  1028  		
  1029  		if log && logopt.Enabled() {
  1030  			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
  1031  				fmt.Sprintf("call to runtime function %s in instrumented build", ir.PkgFuncName(callee)))
  1032  		}
  1033  		return false, 0, false
  1034  	}
  1035  
  1036  	if base.Flag.Race && types.IsNoRacePkg(callee.Sym().Pkg) {
  1037  		if log && logopt.Enabled() {
  1038  			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
  1039  				fmt.Sprintf(`call to into "no-race" package function %s in race build`, ir.PkgFuncName(callee)))
  1040  		}
  1041  		return false, 0, false
  1042  	}
  1043  
  1044  	if base.Debug.Checkptr != 0 && types.IsRuntimePkg(callee.Sym().Pkg) {
  1045  		
  1046  		if log && logopt.Enabled() {
  1047  			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
  1048  				fmt.Sprintf(`call to into runtime package function %s in -d=checkptr build`, ir.PkgFuncName(callee)))
  1049  		}
  1050  		return false, 0, false
  1051  	}
  1052  
  1053  	
  1054  	
  1055  	
  1056  	
  1057  	
  1058  	
  1059  	
  1060  	parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
  1061  	sym := callee.Linksym()
  1062  	for inlIndex := parent; inlIndex >= 0; inlIndex = base.Ctxt.InlTree.Parent(inlIndex) {
  1063  		if base.Ctxt.InlTree.InlinedFunction(inlIndex) == sym {
  1064  			if log {
  1065  				if base.Flag.LowerM > 1 {
  1066  					fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), callee, ir.FuncName(callerfn))
  1067  				}
  1068  				if logopt.Enabled() {
  1069  					logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
  1070  						fmt.Sprintf("repeated recursive cycle to %s", ir.PkgFuncName(callee)))
  1071  				}
  1072  			}
  1073  			return false, 0, false
  1074  		}
  1075  	}
  1076  
  1077  	return true, callSiteScore, hot
  1078  }
  1079  
  1080  
  1081  
  1082  
  1083  
  1084  
  1085  
  1086  
  1087  func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller, closureCalledOnce bool) *ir.InlinedCallExpr {
  1088  	ok, score, hot := canInlineCallExpr(callerfn, n, fn, bigCaller, closureCalledOnce, true)
  1089  	if !ok {
  1090  		return nil
  1091  	}
  1092  	if hot {
  1093  		hasHotCall[callerfn] = struct{}{}
  1094  	}
  1095  	typecheck.AssertFixedCall(n)
  1096  
  1097  	parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
  1098  	sym := fn.Linksym()
  1099  	inlIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym, ir.FuncName(fn))
  1100  
  1101  	closureInitLSym := func(n *ir.CallExpr, fn *ir.Func) {
  1102  		
  1103  		
  1104  		
  1105  		
  1106  		
  1107  		
  1108  		
  1109  		
  1110  		
  1111  		
  1112  		
  1113  		
  1114  		
  1115  		
  1116  		
  1117  		
  1118  		
  1119  		
  1120  		
  1121  		
  1122  
  1123  		if n.Op() != ir.OCALLFUNC {
  1124  			
  1125  			return
  1126  		}
  1127  
  1128  		var nf = n.Fun
  1129  		
  1130  		for nf.Op() == ir.OCONVNOP {
  1131  			nf = nf.(*ir.ConvExpr).X
  1132  		}
  1133  		if nf.Op() != ir.OCLOSURE {
  1134  			
  1135  			return
  1136  		}
  1137  
  1138  		clo := nf.(*ir.ClosureExpr)
  1139  		if !clo.Func.IsClosure() {
  1140  			
  1141  			return
  1142  		}
  1143  
  1144  		ir.InitLSym(fn, true)
  1145  	}
  1146  
  1147  	closureInitLSym(n, fn)
  1148  
  1149  	if base.Flag.GenDwarfInl > 0 {
  1150  		if !sym.WasInlined() {
  1151  			base.Ctxt.DwFixups.SetPrecursorFunc(sym, fn)
  1152  			sym.Set(obj.AttrWasInlined, true)
  1153  		}
  1154  	}
  1155  
  1156  	if base.Flag.LowerM != 0 {
  1157  		if buildcfg.Experiment.NewInliner {
  1158  			fmt.Printf("%v: inlining call to %v with score %d\n",
  1159  				ir.Line(n), fn, score)
  1160  		} else {
  1161  			fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
  1162  		}
  1163  	}
  1164  	if base.Flag.LowerM > 2 {
  1165  		fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n)
  1166  	}
  1167  
  1168  	res := InlineCall(callerfn, n, fn, inlIndex)
  1169  
  1170  	if res == nil {
  1171  		base.FatalfAt(n.Pos(), "inlining call to %v failed", fn)
  1172  	}
  1173  
  1174  	if base.Flag.LowerM > 2 {
  1175  		fmt.Printf("%v: After inlining %+v\n\n", ir.Line(res), res)
  1176  	}
  1177  
  1178  	if inlheur.Enabled() {
  1179  		inlheur.UpdateCallsiteTable(callerfn, n, res)
  1180  	}
  1181  
  1182  	return res
  1183  }
  1184  
  1185  
  1186  func CalleeEffects(init *ir.Nodes, callee ir.Node) {
  1187  	for {
  1188  		init.Append(ir.TakeInit(callee)...)
  1189  
  1190  		switch callee.Op() {
  1191  		case ir.ONAME, ir.OCLOSURE, ir.OMETHEXPR:
  1192  			return 
  1193  
  1194  		case ir.OCONVNOP:
  1195  			conv := callee.(*ir.ConvExpr)
  1196  			callee = conv.X
  1197  
  1198  		case ir.OINLCALL:
  1199  			ic := callee.(*ir.InlinedCallExpr)
  1200  			init.Append(ic.Body.Take()...)
  1201  			callee = ic.SingleResult()
  1202  
  1203  		default:
  1204  			base.FatalfAt(callee.Pos(), "unexpected callee expression: %v", callee)
  1205  		}
  1206  	}
  1207  }
  1208  
  1209  func pruneUnusedAutos(ll []*ir.Name, vis *hairyVisitor) []*ir.Name {
  1210  	s := make([]*ir.Name, 0, len(ll))
  1211  	for _, n := range ll {
  1212  		if n.Class == ir.PAUTO {
  1213  			if !vis.usedLocals.Has(n) {
  1214  				
  1215  				
  1216  				base.FatalfAt(n.Pos(), "unused auto: %v", n)
  1217  				continue
  1218  			}
  1219  		}
  1220  		s = append(s, n)
  1221  	}
  1222  	return s
  1223  }
  1224  
  1225  func doList(list []ir.Node, do func(ir.Node) bool) bool {
  1226  	for _, x := range list {
  1227  		if x != nil {
  1228  			if do(x) {
  1229  				return true
  1230  			}
  1231  		}
  1232  	}
  1233  	return false
  1234  }
  1235  
  1236  
  1237  
  1238  func isIndexingCoverageCounter(n ir.Node) bool {
  1239  	if n.Op() != ir.OINDEX {
  1240  		return false
  1241  	}
  1242  	ixn := n.(*ir.IndexExpr)
  1243  	if ixn.X.Op() != ir.ONAME || !ixn.X.Type().IsArray() {
  1244  		return false
  1245  	}
  1246  	nn := ixn.X.(*ir.Name)
  1247  	
  1248  	
  1249  	
  1250  	return nn.CoverageAuxVar()
  1251  }
  1252  
  1253  
  1254  
  1255  
  1256  func isAtomicCoverageCounterUpdate(cn *ir.CallExpr) bool {
  1257  	if cn.Fun.Op() != ir.ONAME {
  1258  		return false
  1259  	}
  1260  	name := cn.Fun.(*ir.Name)
  1261  	if name.Class != ir.PFUNC {
  1262  		return false
  1263  	}
  1264  	fn := name.Sym().Name
  1265  	if name.Sym().Pkg.Path != "sync/atomic" ||
  1266  		(fn != "AddUint32" && fn != "StoreUint32") {
  1267  		return false
  1268  	}
  1269  	if len(cn.Args) != 2 || cn.Args[0].Op() != ir.OADDR {
  1270  		return false
  1271  	}
  1272  	adn := cn.Args[0].(*ir.AddrExpr)
  1273  	v := isIndexingCoverageCounter(adn.X)
  1274  	return v
  1275  }
  1276  
  1277  func PostProcessCallSites(profile *pgoir.Profile) {
  1278  	if base.Debug.DumpInlCallSiteScores != 0 {
  1279  		budgetCallback := func(fn *ir.Func, prof *pgoir.Profile) (int32, bool) {
  1280  			v := inlineBudget(fn, prof, false, false)
  1281  			return v, v == inlineHotMaxBudget
  1282  		}
  1283  		inlheur.DumpInlCallSiteScores(profile, budgetCallback)
  1284  	}
  1285  }
  1286  
  1287  func analyzeFuncProps(fn *ir.Func, p *pgoir.Profile) {
  1288  	canInline := func(fn *ir.Func) { CanInline(fn, p) }
  1289  	budgetForFunc := func(fn *ir.Func) int32 {
  1290  		return inlineBudget(fn, p, true, false)
  1291  	}
  1292  	inlheur.AnalyzeFunc(fn, canInline, budgetForFunc, inlineMaxBudget)
  1293  }
  1294  
View as plain text