// inlinable; if it is over the default hairyness limit and it
// doesn't have any interesting properties, then we don't want
// the overhead of writing out its inline body.
+ nameFinder := newNameFinder(fn)
for i := len(funcs) - 1; i >= 0; i-- {
f := funcs[i]
if f.OClosure != nil && !f.InlinabilityChecked() {
canInline(f)
}
- funcProps := analyzeFunc(f, inlineMaxBudget)
+ funcProps := analyzeFunc(f, inlineMaxBudget, nameFinder)
revisitInlinability(f, funcProps, budgetForFunc)
if f.Inl != nil {
f.Inl.Properties = funcProps.SerializeToString()
scoreCallsCache.csl = nil
}
-func analyzeFunc(fn *ir.Func, inlineMaxBudget int) *FuncProps {
+func analyzeFunc(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) *FuncProps {
if funcInlHeur, ok := fpmap[fn]; ok {
return funcInlHeur.props
}
- funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget)
+ funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget, nf)
file, line := fnFileLine(fn)
entry := fnInlHeur{
fname: fn.Sym().Name,
// computeFuncProps examines the Go function 'fn' and computes for it
// a function "properties" object, to be used to drive inlining
// heuristics. See comments on the FuncProps type for more info.
-func computeFuncProps(fn *ir.Func, inlineMaxBudget int) (*FuncProps, CallSiteTab) {
+func computeFuncProps(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) (*FuncProps, CallSiteTab) {
if debugTrace&debugTraceFuncs != 0 {
fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
fn, fn)
funcProps := new(FuncProps)
ffa := makeFuncFlagsAnalyzer(fn)
analyzers := []propAnalyzer{ffa}
- analyzers = addResultsAnalyzer(fn, analyzers, funcProps, inlineMaxBudget)
- analyzers = addParamsAnalyzer(fn, analyzers, funcProps)
+ analyzers = addResultsAnalyzer(fn, analyzers, funcProps, inlineMaxBudget, nf)
+ analyzers = addParamsAnalyzer(fn, analyzers, funcProps, nf)
runAnalyzersOnFunction(fn, analyzers)
for _, a := range analyzers {
a.setResults(funcProps)
}
- cstab := computeCallSiteTable(fn, fn.Body, nil, ffa.panicPathTable(), 0)
+ cstab := computeCallSiteTable(fn, fn.Body, nil, ffa.panicPathTable(), 0, nf)
return funcProps, cstab
}
)
type callSiteAnalyzer struct {
+ fn *ir.Func
+ *nameFinder
+}
+
+type callSiteTableBuilder struct {
+ fn *ir.Func
+ *nameFinder
cstab CallSiteTab
- fn *ir.Func
ptab map[ir.Node]pstate
nstack []ir.Node
loopNest int
isInit bool
}
-func makeCallSiteAnalyzer(fn *ir.Func, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int) *callSiteAnalyzer {
- isInit := fn.IsPackageInit() || strings.HasPrefix(fn.Sym().Name, "init.")
+func makeCallSiteAnalyzer(fn *ir.Func) *callSiteAnalyzer {
return &callSiteAnalyzer{
- fn: fn,
- cstab: cstab,
- ptab: ptab,
- isInit: isInit,
- loopNest: loopNestingLevel,
- nstack: []ir.Node{fn},
+ fn: fn,
+ nameFinder: newNameFinder(fn),
+ }
+}
+
+func makeCallSiteTableBuilder(fn *ir.Func, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int, nf *nameFinder) *callSiteTableBuilder {
+ isInit := fn.IsPackageInit() || strings.HasPrefix(fn.Sym().Name, "init.")
+ return &callSiteTableBuilder{
+ fn: fn,
+ cstab: cstab,
+ ptab: ptab,
+ isInit: isInit,
+ loopNest: loopNestingLevel,
+ nstack: []ir.Node{fn},
+ nameFinder: nf,
}
}
// specific subtree within the AST for a function. The main intended
// use cases are for 'region' to be either A) an entire function body,
// or B) an inlined call expression.
-func computeCallSiteTable(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int) CallSiteTab {
- csa := makeCallSiteAnalyzer(fn, cstab, ptab, loopNestingLevel)
+func computeCallSiteTable(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int, nf *nameFinder) CallSiteTab {
+ cstb := makeCallSiteTableBuilder(fn, cstab, ptab, loopNestingLevel, nf)
var doNode func(ir.Node) bool
doNode = func(n ir.Node) bool {
- csa.nodeVisitPre(n)
+ cstb.nodeVisitPre(n)
ir.DoChildren(n, doNode)
- csa.nodeVisitPost(n)
+ cstb.nodeVisitPost(n)
return false
}
for _, n := range region {
doNode(n)
}
- return csa.cstab
+ return cstb.cstab
}
-func (csa *callSiteAnalyzer) flagsForNode(call *ir.CallExpr) CSPropBits {
+func (cstb *callSiteTableBuilder) flagsForNode(call *ir.CallExpr) CSPropBits {
var r CSPropBits
if debugTrace&debugTraceCalls != 0 {
}
// Set a bit if this call is within a loop.
- if csa.loopNest > 0 {
+ if cstb.loopNest > 0 {
r |= CallSiteInLoop
}
// Set a bit if the call is within an init function (either
// compiler-generated or user-written).
- if csa.isInit {
+ if cstb.isInit {
r |= CallSiteInInitFunc
}
// Decide whether to apply the panic path heuristic. Hack: don't
// apply this heuristic in the function "main.main" (mostly just
// to avoid annoying users).
- if !isMainMain(csa.fn) {
- r = csa.determinePanicPathBits(call, r)
+ if !isMainMain(cstb.fn) {
+ r = cstb.determinePanicPathBits(call, r)
}
return r
// panic/exit. Do this by walking back up the node stack to see if we
// can find either A) an enclosing panic, or B) a statement node that
// we've determined leads to a panic/exit.
-func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
- csa.nstack = append(csa.nstack, call)
+func (cstb *callSiteTableBuilder) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
+ cstb.nstack = append(cstb.nstack, call)
defer func() {
- csa.nstack = csa.nstack[:len(csa.nstack)-1]
+ cstb.nstack = cstb.nstack[:len(cstb.nstack)-1]
}()
- for ri := range csa.nstack[:len(csa.nstack)-1] {
- i := len(csa.nstack) - ri - 1
- n := csa.nstack[i]
+ for ri := range cstb.nstack[:len(cstb.nstack)-1] {
+ i := len(cstb.nstack) - ri - 1
+ n := cstb.nstack[i]
_, isCallExpr := n.(*ir.CallExpr)
_, isStmt := n.(ir.Stmt)
if isCallExpr {
}
if debugTrace&debugTraceCalls != 0 {
- ps, inps := csa.ptab[n]
+ ps, inps := cstb.ptab[n]
fmt.Fprintf(os.Stderr, "=-= callpar %d op=%s ps=%s inptab=%v stmt=%v\n", i, n.Op().String(), ps.String(), inps, isStmt)
}
r |= CallSiteOnPanicPath
break
}
- if v, ok := csa.ptab[n]; ok {
+ if v, ok := cstb.ptab[n]; ok {
if v == psCallsPanic {
r |= CallSiteOnPanicPath
break
}
// propsForArg returns property bits for a given call argument expression arg.
-func (csa *callSiteAnalyzer) propsForArg(arg ir.Node) ActualExprPropBits {
- _, islit := isLiteral(arg)
- if islit {
+func (cstb *callSiteTableBuilder) propsForArg(arg ir.Node) ActualExprPropBits {
+ if cval := cstb.constValue(arg); cval != nil {
return ActualExprConstant
}
- if isConcreteConvIface(arg) {
+ if cstb.isConcreteConvIface(arg) {
return ActualExprIsConcreteConvIface
}
- fname, isfunc, _ := isFuncName(arg)
- if isfunc {
+ fname := cstb.funcName(arg)
+ if fname != nil {
if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
return ActualExprIsInlinableFunc
}
// expression; these will be stored in the CallSite object for a given
// call and then consulted when scoring. If no arg has any interesting
// properties we try to save some space and return a nil slice.
-func (csa *callSiteAnalyzer) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBits {
+func (cstb *callSiteTableBuilder) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBits {
rv := make([]ActualExprPropBits, len(ce.Args))
somethingInteresting := false
for idx := range ce.Args {
- argProp := csa.propsForArg(ce.Args[idx])
+ argProp := cstb.propsForArg(ce.Args[idx])
somethingInteresting = somethingInteresting || (argProp != 0)
rv[idx] = argProp
}
return rv
}
-func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
- flags := csa.flagsForNode(call)
- argProps := csa.argPropsForCall(call)
+func (cstb *callSiteTableBuilder) addCallSite(callee *ir.Func, call *ir.CallExpr) {
+ flags := cstb.flagsForNode(call)
+ argProps := cstb.argPropsForCall(call)
if debugTrace&debugTraceCalls != 0 {
fmt.Fprintf(os.Stderr, "=-= props %+v for call %v\n", argProps, call)
}
cs := &CallSite{
Call: call,
Callee: callee,
- Assign: csa.containingAssignment(call),
+ Assign: cstb.containingAssignment(call),
ArgProps: argProps,
Flags: flags,
- ID: uint(len(csa.cstab)),
+ ID: uint(len(cstb.cstab)),
}
- if _, ok := csa.cstab[call]; ok {
+ if _, ok := cstb.cstab[call]; ok {
fmt.Fprintf(os.Stderr, "*** cstab duplicate entry at: %s\n",
fmtFullPos(call.Pos()))
fmt.Fprintf(os.Stderr, "*** call: %+v\n", call)
// on heuristics.
cs.Score = int(callee.Inl.Cost)
- if csa.cstab == nil {
- csa.cstab = make(CallSiteTab)
+ if cstb.cstab == nil {
+ cstb.cstab = make(CallSiteTab)
}
- csa.cstab[call] = cs
+ cstb.cstab[call] = cs
if debugTrace&debugTraceCalls != 0 {
fmt.Fprintf(os.Stderr, "=-= added callsite: caller=%v callee=%v n=%s\n",
- csa.fn, callee, fmtFullPos(call.Pos()))
+ cstb.fn, callee, fmtFullPos(call.Pos()))
}
}
-func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {
+func (cstb *callSiteTableBuilder) nodeVisitPre(n ir.Node) {
switch n.Op() {
case ir.ORANGE, ir.OFOR:
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
- csa.loopNest++
+ cstb.loopNest++
}
case ir.OCALLFUNC:
ce := n.(*ir.CallExpr)
callee := pgo.DirectCallee(ce.Fun)
if callee != nil && callee.Inl != nil {
- csa.addCallSite(callee, ce)
+ cstb.addCallSite(callee, ce)
}
}
- csa.nstack = append(csa.nstack, n)
+ cstb.nstack = append(cstb.nstack, n)
}
-func (csa *callSiteAnalyzer) nodeVisitPost(n ir.Node) {
- csa.nstack = csa.nstack[:len(csa.nstack)-1]
+func (cstb *callSiteTableBuilder) nodeVisitPost(n ir.Node) {
+ cstb.nstack = cstb.nstack[:len(cstb.nstack)-1]
switch n.Op() {
case ir.ORANGE, ir.OFOR:
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
- csa.loopNest--
+ cstb.loopNest--
}
}
}
// call to a pair of auto-temps, then the second one assigning the
// auto-temps to the user-visible vars. This helper will return the
// second (outer) of these two.
-func (csa *callSiteAnalyzer) containingAssignment(n ir.Node) ir.Node {
- parent := csa.nstack[len(csa.nstack)-1]
+func (cstb *callSiteTableBuilder) containingAssignment(n ir.Node) ir.Node {
+ parent := cstb.nstack[len(cstb.nstack)-1]
// assignsOnlyAutoTemps returns TRUE of the specified OAS2FUNC
// node assigns only auto-temps.
// OAS1({x,y},OCONVNOP(OAS2FUNC({auto1,auto2},OCALLFUNC(bar))))
//
if assignsOnlyAutoTemps(parent) {
- par2 := csa.nstack[len(csa.nstack)-2]
+ par2 := cstb.nstack[len(cstb.nstack)-2]
if par2.Op() == ir.OAS2 {
return par2
}
if par2.Op() == ir.OCONVNOP {
- par3 := csa.nstack[len(csa.nstack)-3]
+ par3 := cstb.nstack[len(cstb.nstack)-3]
if par3.Op() == ir.OAS2 {
return par3
}
loopNestLevel = 1
}
ptab := map[ir.Node]pstate{ic: icp}
- icstab := computeCallSiteTable(callerfn, ic.Body, nil, ptab, loopNestLevel)
+ nf := newNameFinder(nil)
+ icstab := computeCallSiteTable(callerfn, ic.Body, nil, ptab, loopNestLevel, nf)
// Record parent callsite. This is primarily for debug output.
for _, cs := range icstab {
cs.parent = oldcs
}
- // Score the calls in the inlined body. Note the setting of "doCallResults"
- // to false here: at the moment there isn't any easy way to localize
- // or region-ize the work done by "rescoreBasedOnCallResultUses", which
- // currently does a walk over the entire function to look for uses
- // of a given set of results.
+ // Score the calls in the inlined body. Note the setting of
+ // "doCallResults" to false here: at the moment there isn't any
+ // easy way to localize or region-ize the work done by
+ // "rescoreBasedOnCallResultUses", which currently does a walk
+ // over the entire function to look for uses of a given set of
+ // results. Similarly we're passing nil to makeCallSiteAnalyzer,
+ // so as to run name finding without the use of static value &
+ // friends.
+ csa := makeCallSiteAnalyzer(nil)
const doCallResults = false
- scoreCallsRegion(callerfn, ic.Body, icstab, doCallResults, ic)
+ csa.scoreCallsRegion(callerfn, ic.Body, icstab, doCallResults, ic)
}
params []*ir.Name
top []bool
*condLevelTracker
+ *nameFinder
}
// getParams returns an *ir.Name slice containing all params for the
// new list. If the function in question doesn't have any interesting
// parameters then the analyzer list is returned unchanged, and the
// params flags in "fp" are updated accordingly.
-func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps) []propAnalyzer {
- pa, props := makeParamsAnalyzer(fn)
+func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, nf *nameFinder) []propAnalyzer {
+ pa, props := makeParamsAnalyzer(fn, nf)
if pa != nil {
analyzers = append(analyzers, pa)
} else {
// of function fn. If the function doesn't have any interesting
// params, a nil helper is returned along with a set of default param
// flags for the func.
-func makeParamsAnalyzer(fn *ir.Func) (*paramsAnalyzer, []ParamPropBits) {
+func makeParamsAnalyzer(fn *ir.Func, nf *nameFinder) (*paramsAnalyzer, []ParamPropBits) {
params := getParams(fn) // includes receiver if applicable
if len(params) == 0 {
return nil, nil
params: params,
top: top,
condLevelTracker: new(condLevelTracker),
+ nameFinder: nf,
}
return pa, nil
}
return
}
sel := ce.Fun.(*ir.SelectorExpr)
- r := ir.StaticValue(sel.X)
+ r := pa.staticValue(sel.X)
if r.Op() != ir.ONAME {
return
}
return name == p, false
})
} else {
- cname, isFunc, _ := isFuncName(called)
- if isFunc {
+ cname := pa.funcName(called)
+ if cname != nil {
pa.deriveFlagsFromCallee(ce, cname.Func)
}
}
}
// See if one of the caller's parameters is flowing unmodified
// into this actual expression.
- r := ir.StaticValue(arg)
+ r := pa.staticValue(arg)
if r.Op() != ir.ONAME {
return
}
props []ResultPropBits
values []resultVal
inlineMaxBudget int
+ *nameFinder
}
// resultVal captures information about a specific result returned from
// the same function, etc. This container stores info on a the specific
// scenarios we're looking for.
type resultVal struct {
- lit constant.Value
+ cval constant.Value
fn *ir.Name
fnClo bool
top bool
// new list. If the function in question doesn't have any returns (or
// any interesting returns) then the analyzer list is left as is, and
// the result flags in "fp" are updated accordingly.
-func addResultsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, inlineMaxBudget int) []propAnalyzer {
- ra, props := makeResultsAnalyzer(fn, inlineMaxBudget)
+func addResultsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, inlineMaxBudget int, nf *nameFinder) []propAnalyzer {
+ ra, props := makeResultsAnalyzer(fn, inlineMaxBudget, nf)
if ra != nil {
analyzers = append(analyzers, ra)
} else {
// in function fn. If the function doesn't have any interesting
// results, a nil helper is returned along with a set of default
// result flags for the func.
-func makeResultsAnalyzer(fn *ir.Func, inlineMaxBudget int) (*resultsAnalyzer, []ResultPropBits) {
+func makeResultsAnalyzer(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) (*resultsAnalyzer, []ResultPropBits) {
results := fn.Type().Results()
if len(results) == 0 {
return nil, nil
props: props,
values: vals,
inlineMaxBudget: inlineMaxBudget,
+ nameFinder: nf,
}
return ra, nil
}
}
}
-// isFuncName returns the *ir.Name for the func or method
-// corresponding to node 'n', along with a boolean indicating success,
-// and another boolean indicating whether the func is closure.
-func isFuncName(n ir.Node) (*ir.Name, bool, bool) {
- sv := ir.StaticValue(n)
- if sv.Op() == ir.ONAME {
- name := sv.(*ir.Name)
- if name.Sym() != nil && name.Class == ir.PFUNC {
- return name, true, false
- }
- }
- if sv.Op() == ir.OCLOSURE {
- cloex := sv.(*ir.ClosureExpr)
- return cloex.Func.Nname, true, true
- }
- if sv.Op() == ir.OMETHEXPR {
- if mn := ir.MethodExprName(sv); mn != nil {
- return mn, true, false
- }
- }
- return nil, false, false
-}
-
// analyzeResult examines the expression 'n' being returned as the
// 'ii'th argument in some return statement to see whether has
// interesting characteristics (for example, returns a constant), then
// previous result (for the given return slot) that we've already
// processed.
func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
- isAllocMem := isAllocatedMem(n)
- isConcConvItf := isConcreteConvIface(n)
- lit, isConst := isLiteral(n)
- rfunc, isFunc, isClo := isFuncName(n)
+ isAllocMem := ra.isAllocatedMem(n)
+ isConcConvItf := ra.isConcreteConvIface(n)
+ constVal := ra.constValue(n)
+ isConst := (constVal != nil)
+ isNil := ra.isNil(n)
+ rfunc := ra.funcName(n)
+ isFunc := (rfunc != nil)
+ isClo := (rfunc != nil && rfunc.Func.OClosure != nil)
curp := ra.props[ii]
- dprops, isDerivedFromCall := deriveReturnFlagsFromCallee(n)
+ dprops, isDerivedFromCall := ra.deriveReturnFlagsFromCallee(n)
newp := ResultNoInfo
- var newlit constant.Value
+ var newcval constant.Value
var newfunc *ir.Name
if debugTrace&debugTraceResults != 0 {
- fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult n=%s ismem=%v isconcconv=%v isconst=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isFunc, isClo)
+ fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult n=%s ismem=%v isconcconv=%v isconst=%v isnil=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isNil, isFunc, isClo)
}
if ra.values[ii].top {
newfunc = rfunc
case isConst:
newp = ResultAlwaysSameConstant
- newlit = lit
+ newcval = constVal
+ case isNil:
+ newp = ResultAlwaysSameConstant
+ newcval = nil
case isDerivedFromCall:
newp = dprops
ra.values[ii].derived = true
// the previous returns.
switch curp {
case ResultIsAllocatedMem:
- if isAllocatedMem(n) {
+ if isAllocMem {
newp = ResultIsAllocatedMem
}
case ResultIsConcreteTypeConvertedToInterface:
- if isConcreteConvIface(n) {
+ if isConcConvItf {
newp = ResultIsConcreteTypeConvertedToInterface
}
case ResultAlwaysSameConstant:
- if isConst && isSameLiteral(lit, ra.values[ii].lit) {
+ if isNil && ra.values[ii].cval == nil {
+ newp = ResultAlwaysSameConstant
+ newcval = nil
+ } else if isConst && constant.Compare(constVal, token.EQL, ra.values[ii].cval) {
newp = ResultAlwaysSameConstant
- newlit = lit
+ newcval = constVal
}
case ResultAlwaysSameFunc:
if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
}
ra.values[ii].fn = newfunc
ra.values[ii].fnClo = isClo
- ra.values[ii].lit = newlit
+ ra.values[ii].cval = newcval
ra.props[ii] = newp
if debugTrace&debugTraceResults != 0 {
}
}
-func isAllocatedMem(n ir.Node) bool {
- sv := ir.StaticValue(n)
- switch sv.Op() {
- case ir.OMAKESLICE, ir.ONEW, ir.OPTRLIT, ir.OSLICELIT:
- return true
- }
- return false
-}
-
// deriveReturnFlagsFromCallee tries to set properties for a given
// return result where we're returning call expression; return value
// is a return property value and a boolean indicating whether the
// set foo's return property to that of bar. In the case of "two", however,
// even though each return path returns a constant, we don't know
// whether the constants are identical, hence we need to be conservative.
-func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
+func (ra *resultsAnalyzer) deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
if n.Op() != ir.OCALLFUNC {
return 0, false
}
if called.Op() != ir.ONAME {
return 0, false
}
- cname, isFunc, _ := isFuncName(called)
- if !isFunc {
+ cname := ra.funcName(called)
+ if cname == nil {
return 0, false
}
calleeProps := propsForFunc(cname.Func)
}
return calleeProps.ResultFlags[0], true
}
-
-func isLiteral(n ir.Node) (constant.Value, bool) {
- sv := ir.StaticValue(n)
- switch sv.Op() {
- case ir.ONIL:
- return nil, true
- case ir.OLITERAL:
- return sv.Val(), true
- }
- return nil, false
-}
-
-// isSameLiteral checks to see if 'v1' and 'v2' correspond to the same
-// literal value, or if they are both nil.
-func isSameLiteral(v1, v2 constant.Value) bool {
- if v1 == nil && v2 == nil {
- return true
- }
- if v1 == nil || v2 == nil {
- return false
- }
- return constant.Compare(v1, token.EQL, v2)
-}
-
-func isConcreteConvIface(n ir.Node) bool {
- sv := ir.StaticValue(n)
- if sv.Op() != ir.OCONVIFACE {
- return false
- }
- return !sv.(*ir.ConvExpr).X.Type().IsInterface()
-}
-
-func isSameFuncName(v1, v2 *ir.Name) bool {
- // NB: there are a few corner cases where pointer equality
- // doesn't work here, but this should be good enough for
- // our purposes here.
- return v1 == v2
-}
--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package inlheur
+
+import (
+ "cmd/compile/internal/ir"
+ "go/constant"
+)
+
+// nameFinder provides a set of "isXXX" query methods for clients to
+// ask whether a given AST node corresponds to a function, a constant
+// value, and so on. These methods use an underlying ir.ReassignOracle
+// to return more precise results in cases where an "interesting"
+// value is assigned to a singly-defined local temp. Example:
+//
+// const q = 101
+// fq := func() int { return q }
+// copyOfConstant := q
+// copyOfFunc := f
+// interestingCall(copyOfConstant, copyOfFunc)
+//
+// A name finder query method invoked on the arguments being passed to
+// "interestingCall" will be able detect that 'copyOfConstant' always
+// evaluates to a constant (even though it is in fact a PAUTO local
+// variable). A given nameFinder can also operate without using
+// ir.ReassignOracle (in cases where it is not practical to look
+// at the entire function); in such cases queries will still work
+// for explicit constant values and functions.
+type nameFinder struct {
+ ro *ir.ReassignOracle
+}
+
+// newNameFinder returns a new nameFinder object with a reassignment
+// oracle initialized based on the function fn, or if fn is nil,
+// without an underlying ReassignOracle.
+func newNameFinder(fn *ir.Func) *nameFinder {
+ var ro *ir.ReassignOracle
+ if fn != nil {
+ ro = &ir.ReassignOracle{}
+ ro.Init(fn)
+ }
+ return &nameFinder{ro: ro}
+}
+
+// funcName returns the *ir.Name for the func or method
+// corresponding to node 'n', or nil if n can't be proven
+// to contain a function value.
+func (nf *nameFinder) funcName(n ir.Node) *ir.Name {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if name := ir.StaticCalleeName(sv); name != nil {
+ return name
+ }
+ return nil
+}
+
+// isAllocatedMem returns true if node n corresponds to a memory
+// allocation expression (make, new, or equivalent).
+func (nf *nameFinder) isAllocatedMem(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ switch sv.Op() {
+ case ir.OMAKESLICE, ir.ONEW, ir.OPTRLIT, ir.OSLICELIT:
+ return true
+ }
+ return false
+}
+
+// constValue returns the underlying constant.Value for an AST node n
+// if n is itself a constant value/expr, or if n is a singly assigned
+// local containing constant expr/value (or nil not constant).
+func (nf *nameFinder) constValue(n ir.Node) constant.Value {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if sv.Op() == ir.OLITERAL {
+ return sv.Val()
+ }
+ return nil
+}
+
+// isNil returns whether n is nil (or singly
+// assigned local containing nil).
+func (nf *nameFinder) isNil(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ return sv.Op() == ir.ONIL
+}
+
+func (nf *nameFinder) staticValue(n ir.Node) ir.Node {
+ if nf.ro == nil {
+ return n
+ }
+ return nf.ro.StaticValue(n)
+}
+
+func (nf *nameFinder) reassigned(n *ir.Name) bool {
+ if nf.ro == nil {
+ return true
+ }
+ return nf.ro.Reassigned(n)
+}
+
+func (nf *nameFinder) isConcreteConvIface(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if sv.Op() != ir.OCONVIFACE {
+ return false
+ }
+ return !sv.(*ir.ConvExpr).X.Type().IsInterface()
+}
+
+func isSameFuncName(v1, v2 *ir.Name) bool {
+ // NB: there are a few corner cases where pointer equality
+ // doesn't work here, but this should be good enough for
+ // our purposes here.
+ return v1 == v2
+}
// rescoreBasedOnCallResultUses examines how call results are used,
// and tries to update the scores of calls based on how their results
// are used in the function.
-func rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) {
+func (csa *callSiteAnalyzer) rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) {
enableDebugTraceIfEnv()
rua := &resultUseAnalyzer{
resultNameTab: resultNameTab,
disableDebugTrace()
}
-func examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS) map[*ir.Name]resultPropAndCS {
+func (csa *callSiteAnalyzer) examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS) map[*ir.Name]resultPropAndCS {
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= examining call results for %q\n",
EncodeCallSiteKey(cs))
if rprop&interesting == 0 {
continue
}
- if ir.Reassigned(n) {
+ if csa.nameFinder.reassigned(n) {
continue
}
if resultNameTab == nil {
return 0
}
-// computeCallSiteScore takes a given call site whose ir node is 'call' and
-// callee function is 'callee' and with previously computed call site
-// properties 'csflags', then computes a score for the callsite that
-// combines the size cost of the callee with heuristics based on
-// previously parameter and function properties, then stores the score
-// and the adjustment mask in the appropriate fields in 'cs'
-func (cs *CallSite) computeCallSiteScore(calleeProps *FuncProps) {
+// computeCallSiteScore takes a given call site whose ir node is
+// 'call' and callee function is 'callee' and with previously computed
+// call site properties 'csflags', then computes a score for the
+// callsite that combines the size cost of the callee with heuristics
+// based on previously computed argument and function properties,
+// then stores the score and the adjustment mask in the appropriate
+// fields in 'cs'
+func (cs *CallSite) computeCallSiteScore(csa *callSiteAnalyzer, calleeProps *FuncProps) {
callee := cs.Callee
csflags := cs.Flags
call := cs.Call
// after foo has been analyzed, but it's conceivable that CanInline
// might visit bar before foo for this SCC.
func ScoreCalls(fn *ir.Func) {
+ if len(fn.Body) == 0 {
+ return
+ }
enableDebugTraceIfEnv()
+ nameFinder := newNameFinder(fn)
+
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
}
fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
ir.FuncName(fn))
}
- cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0)
+ cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0,
+ nameFinder)
}
+ csa := makeCallSiteAnalyzer(fn)
const doCallResults = true
- scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
+ csa.scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
+
+ disableDebugTrace()
}
// scoreCallsRegion assigns numeric scores to each of the callsites in
// region 'region' within function 'fn'. This can be called on
// an entire function, or with 'region' set to a chunk of
// code corresponding to an inlined call.
-func scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
+func (csa *callSiteAnalyzer) scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
if debugTrace&debugTraceScoring != 0 {
- fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s)\n",
- ir.FuncName(fn), region[0].Op().String())
+ fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s) len(cstab)=%d\n",
+ ir.FuncName(fn), region[0].Op().String(), len(cstab))
}
// Sort callsites to avoid any surprises with non deterministic
continue
}
}
- cs.computeCallSiteScore(cprops)
+ cs.computeCallSiteScore(csa, cprops)
if doCallResults {
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= examineCallResults at %s: flags=%d score=%d funcInlHeur=%v deser=%v\n", fmtFullPos(cs.Call.Pos()), cs.Flags, cs.Score, fihcprops, desercprops)
}
- resultNameTab = examineCallResults(cs, resultNameTab)
+ resultNameTab = csa.examineCallResults(cs, resultNameTab)
}
if debugTrace&debugTraceScoring != 0 {
}
if resultNameTab != nil {
- rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
+ csa.rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
}
disableDebugTrace()