1
0
mirror of https://github.com/golang/go synced 2024-11-26 20:01:19 -07:00

cmd/compile/internal/inline/inlheur: assign scores to callsites

Assign scores to callsites based on previously computed function
properties and callsite properties. This currently works by taking the
size score for the function (as computed by CanInline) and then making
a series of adjustments, positive or negative based on various
function and callsite properties.

NB: much work also remaining on deciding what are the best score
adjustment values for specific heuristics. I've picked a bunch of
arbitrary constants, but they will almost certainly need tuning and
tweaking to arrive at something that has good performance.

Updates #61502.

Change-Id: I887403f95e76d7aa2708494b8686c6026861a6ed
Reviewed-on: https://go-review.googlesource.com/c/go/+/511566
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Than McIntosh 2023-07-18 16:17:12 -04:00
parent dc0548f92f
commit 746f7e1744
10 changed files with 574 additions and 73 deletions

View File

@ -293,15 +293,10 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
base.Fatalf("CanInline no nname %+v", fn)
}
canInline := func(fn *ir.Func) { CanInline(fn, profile) }
var funcProps *inlheur.FuncProps
if goexperiment.NewInliner {
funcProps = inlheur.AnalyzeFunc(fn, canInline)
}
if base.Debug.DumpInlFuncProps != "" {
inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps, canInline)
if goexperiment.NewInliner || inlheur.UnitTesting() {
funcProps = inlheur.AnalyzeFunc(fn,
func(fn *ir.Func) { CanInline(fn, profile) })
}
var reason string // reason, if any, that the function was not inlined
@ -803,6 +798,13 @@ func isBigFunc(fn *ir.Func) bool {
// InlineCalls/inlnode walks fn's statements and expressions and substitutes any
// calls made to inlineable functions. This is the external entry point.
func InlineCalls(fn *ir.Func, profile *pgo.Profile) {
if goexperiment.NewInliner && !fn.Wrapper() {
inlheur.ScoreCalls(fn)
}
if base.Debug.DumpInlFuncProps != "" && !fn.Wrapper() {
inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps,
func(fn *ir.Func) { CanInline(fn, profile) })
}
savefn := ir.CurFunc
ir.CurFunc = fn
bigCaller := isBigFunc(fn)

View File

@ -24,6 +24,7 @@ const (
debugTraceParams
debugTraceExprClassify
debugTraceCalls
debugTraceScoring
)
// propAnalyzer interface is used for defining one or more analyzer
@ -76,6 +77,9 @@ func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func)) *FuncProps {
base.FatalfAt(fn.Pos(), "%v", err)
}
fpmap[fn] = entry
if fn.Inl != nil && fn.Inl.Properties == "" {
fn.Inl.Properties = entry.props.SerializeToString()
}
return fp
}
@ -139,12 +143,26 @@ func UnitTesting() bool {
}
// DumpFuncProps computes and caches function properties for the func
// 'fn', or if fn is nil, writes out the cached set of properties to
// the file given in 'dumpfile'. Used for the "-d=dumpinlfuncprops=..."
// command line flag, intended for use primarily in unit testing.
// 'fn' and any closures it contains, or if fn is nil, it writes out the
// cached set of properties to the file given in 'dumpfile'. Used for
// the "-d=dumpinlfuncprops=..." command line flag, intended for use
// primarily in unit testing.
func DumpFuncProps(fn *ir.Func, dumpfile string, canInline func(*ir.Func)) {
if fn != nil {
dmp := func(fn *ir.Func) {
if !goexperiment.NewInliner {
ScoreCalls(fn)
}
captureFuncDumpEntry(fn, canInline)
}
captureFuncDumpEntry(fn, canInline)
dmp(fn)
ir.Visit(fn, func(n ir.Node) {
if clo, ok := n.(*ir.ClosureExpr); ok {
dmp(clo.Func)
}
})
} else {
emitDumpToFile(dumpfile)
}
@ -185,9 +203,16 @@ func emitDumpToFile(dumpfile string) {
dumpBuffer = nil
}
// captureFuncDumpEntry analyzes function 'fn' and adds a entry
// for it to 'dumpBuffer'. Used for unit testing.
// captureFuncDumpEntry grabs the function properties object for 'fn'
// and enqueues it for later dumping. Used for the
// "-d=dumpinlfuncprops=..." command line flag, intended for use
// primarily in unit testing.
func captureFuncDumpEntry(fn *ir.Func, canInline func(*ir.Func)) {
if debugTrace&debugTraceFuncs != 0 {
fmt.Fprintf(os.Stderr, "=-= capturing dump for %v:\n",
fn.Sym().Name)
}
// avoid capturing compiler-generated equality funcs.
if strings.HasPrefix(fn.Sym().Name, ".eq.") {
return

View File

@ -5,10 +5,12 @@
package inlheur
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/pgo"
"fmt"
"os"
"sort"
"strings"
)
@ -120,13 +122,14 @@ func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits)
}
func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
flags := csa.flagsForNode(call)
// FIXME: maybe bulk-allocate these?
cs := &CallSite{
Call: call,
Callee: callee,
Assign: csa.containingAssignment(call),
Flags: csa.flagsForNode(call),
Id: uint(len(csa.cstab)),
Flags: flags,
ID: uint(len(csa.cstab)),
}
if _, ok := csa.cstab[call]; ok {
fmt.Fprintf(os.Stderr, "*** cstab duplicate entry at: %s\n",
@ -134,12 +137,87 @@ func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
fmt.Fprintf(os.Stderr, "*** call: %+v\n", call)
panic("bad")
}
if callee.Inl != nil {
// Set initial score for callsite to the cost computed
// by CanInline; this score will be refined later based
// on heuristics.
cs.Score = int(callee.Inl.Cost)
}
csa.cstab[call] = cs
if debugTrace&debugTraceCalls != 0 {
fmt.Fprintf(os.Stderr, "=-= added callsite: callee=%s call=%v\n",
callee.Sym().Name, callee)
}
}
csa.cstab[call] = cs
// ScoreCalls assigns numeric scores to each of the callsites in
// function 'fn'; the lower the score, the more helpful we think it
// will be to inline.
//
// Unlike a lot of the other inline heuristics machinery, callsite
// scoring can't be done as part of the CanInline call for a function,
// due to fact that we may be working on a non-trivial SCC. So for
// example with this SCC:
//
// func foo(x int) { func bar(x int, f func()) {
// if x != 0 { f()
// bar(x, func(){}) foo(x-1)
// } }
// }
//
// We don't want to perform scoring for the 'foo' call in "bar" until
// after foo has been analyzed, but it's conceivable that CanInline
// might visit bar before foo for this SCC.
func ScoreCalls(fn *ir.Func) {
enableDebugTraceIfEnv()
defer disableDebugTrace()
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
}
fih, ok := fpmap[fn]
if !ok {
// TODO: add an assert/panic here.
return
}
// Sort callsites to avoid any surprises with non deterministic
// map iteration order (this is probably not needed, but here just
// in case).
csl := make([]*CallSite, 0, len(fih.cstab))
for _, cs := range fih.cstab {
csl = append(csl, cs)
}
sort.Slice(csl, func(i, j int) bool {
return csl[i].ID < csl[j].ID
})
// Score each call site.
for _, cs := range csl {
var cprops *FuncProps
fihcprops := false
desercprops := false
if fih, ok := fpmap[cs.Callee]; ok {
cprops = fih.props
fihcprops = true
} else if cs.Callee.Inl != nil {
cprops = DeserializeFromString(cs.Callee.Inl.Properties)
desercprops = true
} else {
if base.Debug.DumpInlFuncProps != "" {
fmt.Fprintf(os.Stderr, "=-= *** unable to score call to %s from %s\n", cs.Callee.Sym().Name, fmtFullPos(cs.Call.Pos()))
panic("should never happen")
} else {
continue
}
}
cs.Score, cs.ScoreMask = computeCallSiteScore(cs.Callee, cprops, cs.Call, cs.Flags)
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= scoring call at %s: flags=%d score=%d fih=%v deser=%v\n", fmtFullPos(cs.Call.Pos()), cs.Flags, cs.Score, fihcprops, desercprops)
}
}
}
func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {

View File

@ -22,15 +22,16 @@ import (
// appears in the form of a top-level statement, e.g. "x := foo()"),
// "Flags" contains properties of the call that might be useful for
// making inlining decisions, "Score" is the final score assigned to
// the site, and "Id" is a numeric ID for the site within its
// the site, and "ID" is a numeric ID for the site within its
// containing function.
type CallSite struct {
Callee *ir.Func
Call *ir.CallExpr
Assign ir.Node
Flags CSPropBits
Score int
Id uint
Callee *ir.Func
Call *ir.CallExpr
Assign ir.Node
Flags CSPropBits
Score int
ScoreMask scoreAdjustTyp
ID uint
}
// CallSiteTab is a table of call sites, keyed by call expr.
@ -53,8 +54,19 @@ const (
// encodedCallSiteTab is a table keyed by "encoded" callsite
// (stringified src.XPos plus call site ID) mapping to a value of call
// property bits.
type encodedCallSiteTab map[string]CSPropBits
// property bits and score.
type encodedCallSiteTab map[string]propsAndScore
type propsAndScore struct {
props CSPropBits
score int
mask scoreAdjustTyp
}
func (pas propsAndScore) String() string {
return fmt.Sprintf("P=%s|S=%d|M=%s", pas.props.String(),
pas.score, pas.mask.String())
}
func (cst CallSiteTab) merge(other CallSiteTab) error {
for k, v := range other {
@ -80,9 +92,9 @@ func fmtFullPos(p src.XPos) string {
func encodeCallSiteKey(cs *CallSite) string {
var sb strings.Builder
// FIXME: rewrite line offsets relative to function start
// FIXME: maybe rewrite line offsets relative to function start?
sb.WriteString(fmtFullPos(cs.Call.Pos()))
fmt.Fprintf(&sb, "|%d", cs.Id)
fmt.Fprintf(&sb, "|%d", cs.ID)
return sb.String()
}
@ -90,7 +102,11 @@ func buildEncodedCallSiteTab(tab CallSiteTab) encodedCallSiteTab {
r := make(encodedCallSiteTab)
for _, cs := range tab {
k := encodeCallSiteKey(cs)
r[k] = cs.Flags
r[k] = propsAndScore{
props: cs.Flags,
score: cs.Score,
mask: cs.ScoreMask,
}
}
return r
}
@ -109,7 +125,7 @@ func dumpCallSiteComments(w io.Writer, tab CallSiteTab, ecst encodedCallSiteTab)
sort.Strings(tags)
for _, s := range tags {
v := ecst[s]
fmt.Fprintf(w, "// callsite: %s flagstr %q flagval %d\n", s, v.String(), v)
fmt.Fprintf(w, "// callsite: %s flagstr %q flagval %d score %d mask %d maskstr %q\n", s, v.props.String(), v.props, v.score, v.mask, v.mask.String())
}
fmt.Fprintf(w, "// %s\n", csDelimiter)
}

View File

@ -72,8 +72,7 @@ func TestFuncProperties(t *testing.T) {
continue
}
if eidx >= len(eentries) {
t.Errorf("missing expected entry for %s, skipping",
dentry.fname)
t.Errorf("testcase %s missing expected entry for %s, skipping", tc, dentry.fname)
continue
}
eentry := eentries[eidx]
@ -124,20 +123,18 @@ func compareEntries(t *testing.T, tc string, dentry *fnInlHeur, dcsites encodedC
// Compare call sites.
for k, ve := range ecsites {
if vd, ok := dcsites[k]; !ok {
t.Errorf("missing expected callsite %q in func %q",
dfn, k)
t.Errorf("testcase %q missing expected callsite %q in func %q", tc, k, dfn)
continue
} else {
if vd != ve {
t.Errorf("callsite %q in func %q: got %s want %s",
k, dfn, vd.String(), ve.String())
t.Errorf("testcase %q callsite %q in func %q: got %+v want %+v",
tc, k, dfn, vd.String(), ve.String())
}
}
}
for k := range dcsites {
if _, ok := ecsites[k]; !ok {
t.Errorf("unexpected extra callsite %q in func %q",
dfn, k)
t.Errorf("testcase %q unexpected extra callsite %q in func %q", tc, k, dfn)
}
}
}
@ -276,13 +273,12 @@ func (dr *dumpReader) readEntry() (fnInlHeur, encodedCallSiteTab, error) {
if line == csDelimiter {
break
}
// expected format: "// callsite: <expanded pos> flagstr <desc> flagval <flags>"
// expected format: "// callsite: <expanded pos> flagstr <desc> flagval <flags> score <score> mask <scoremask> maskstr <scoremaskstring>"
fields := strings.Fields(line)
if len(fields) != 6 {
return fih, nil, fmt.Errorf("malformed callsite %s line %d: %s",
dr.p, dr.ln, line)
if len(fields) != 12 {
return fih, nil, fmt.Errorf("malformed callsite (nf=%d) %s line %d: %s", len(fields), dr.p, dr.ln, line)
}
if fields[2] != "flagstr" || fields[4] != "flagval" {
if fields[2] != "flagstr" || fields[4] != "flagval" || fields[6] != "score" || fields[8] != "mask" || fields[10] != "maskstr" {
return fih, nil, fmt.Errorf("malformed callsite %s line %d: %s",
dr.p, dr.ln, line)
}
@ -293,7 +289,23 @@ func (dr *dumpReader) readEntry() (fnInlHeur, encodedCallSiteTab, error) {
return fih, nil, fmt.Errorf("bad flags val %s line %d: %q err=%v",
dr.p, dr.ln, line, err)
}
callsites[tag] = CSPropBits(flags)
scorestr := fields[7]
score, err2 := strconv.Atoi(scorestr)
if err2 != nil {
return fih, nil, fmt.Errorf("bad score val %s line %d: %q err=%v",
dr.p, dr.ln, line, err2)
}
maskstr := fields[9]
mask, err3 := strconv.Atoi(maskstr)
if err3 != nil {
return fih, nil, fmt.Errorf("bad mask val %s line %d: %q err=%v",
dr.p, dr.ln, line, err3)
}
callsites[tag] = propsAndScore{
props: CSPropBits(flags),
score: score,
mask: scoreAdjustTyp(mask),
}
}
// Consume function delimiter.

View File

@ -0,0 +1,74 @@
// Code generated by "stringer -bitset -type scoreAdjustTyp"; DO NOT EDIT.
package inlheur
import "strconv"
import "bytes"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[panicPathAdj-1]
_ = x[initFuncAdj-2]
_ = x[inLoopAdj-4]
_ = x[passConstToIfAdj-8]
_ = x[passConstToNestedIfAdj-16]
_ = x[passConcreteToItfCallAdj-32]
_ = x[passConcreteToNestedItfCallAdj-64]
_ = x[passFuncToIndCallAdj-128]
_ = x[passFuncToNestedIndCallAdj-256]
_ = x[passInlinableFuncToIndCallAdj-512]
_ = x[passInlinableFuncToNestedIndCallAdj-1024]
_ = x[lastAdj-1024]
}
var _scoreAdjustTyp_value = [...]uint64{
0x1, /* panicPathAdj */
0x2, /* initFuncAdj */
0x4, /* inLoopAdj */
0x8, /* passConstToIfAdj */
0x10, /* passConstToNestedIfAdj */
0x20, /* passConcreteToItfCallAdj */
0x40, /* passConcreteToNestedItfCallAdj */
0x80, /* passFuncToIndCallAdj */
0x100, /* passFuncToNestedIndCallAdj */
0x200, /* passInlinableFuncToIndCallAdj */
0x400, /* passInlinableFuncToNestedIndCallAdj */
0x400, /* lastAdj */
}
const _scoreAdjustTyp_name = "panicPathAdjinitFuncAdjinLoopAdjpassConstToIfAdjpassConstToNestedIfAdjpassConcreteToItfCallAdjpassConcreteToNestedItfCallAdjpassFuncToIndCallAdjpassFuncToNestedIndCallAdjpassInlinableFuncToIndCallAdjpassInlinableFuncToNestedIndCallAdjlastAdj"
var _scoreAdjustTyp_index = [...]uint8{0, 12, 23, 32, 48, 70, 94, 124, 144, 170, 199, 234, 241}
func (i scoreAdjustTyp) String() string {
var b bytes.Buffer
remain := uint64(i)
seen := false
for k, v := range _scoreAdjustTyp_value {
x := _scoreAdjustTyp_name[_scoreAdjustTyp_index[k]:_scoreAdjustTyp_index[k+1]]
if v == 0 {
if i == 0 {
b.WriteString(x)
return b.String()
}
continue
}
if (v & remain) == v {
remain &^= v
x := _scoreAdjustTyp_name[_scoreAdjustTyp_index[k]:_scoreAdjustTyp_index[k+1]]
if seen {
b.WriteString("|")
}
seen = true
b.WriteString(x)
}
}
if remain == 0 {
return b.String()
}
return "scoreAdjustTyp(0x" + strconv.FormatInt(int64(i), 16) + ")"
}

View File

@ -0,0 +1,232 @@
// 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"
"cmd/compile/internal/typecheck"
"fmt"
"os"
)
// These constants enumerate the set of possible ways/scenarios
// in which we'll adjust the score of a given callsite.
type scoreAdjustTyp uint
const (
panicPathAdj scoreAdjustTyp = (1 << iota)
initFuncAdj
inLoopAdj
passConstToIfAdj
passConstToNestedIfAdj
passConcreteToItfCallAdj
passConcreteToNestedItfCallAdj
passFuncToIndCallAdj
passFuncToNestedIndCallAdj
passInlinableFuncToIndCallAdj
passInlinableFuncToNestedIndCallAdj
lastAdj scoreAdjustTyp = passInlinableFuncToNestedIndCallAdj
)
// This table records the specific values we use to adjust call
// site scores in a given scenario.
// NOTE: these numbers are chosen very arbitrarily; ideally
// we will go through some sort of turning process to decide
// what value for each one produces the best performance.
var adjValues = map[scoreAdjustTyp]int{
panicPathAdj: 40,
initFuncAdj: 20,
inLoopAdj: -5,
passConstToIfAdj: -20,
passConstToNestedIfAdj: -15,
passConcreteToItfCallAdj: -30,
passConcreteToNestedItfCallAdj: -25,
passFuncToIndCallAdj: -25,
passFuncToNestedIndCallAdj: -20,
passInlinableFuncToIndCallAdj: -45,
passInlinableFuncToNestedIndCallAdj: -40,
}
func adjValue(x scoreAdjustTyp) int {
if val, ok := adjValues[x]; ok {
return val
} else {
panic("internal error unregistered adjustment type")
}
}
var mayMust = [...]struct{ may, must scoreAdjustTyp }{
{may: passConstToNestedIfAdj, must: passConstToIfAdj},
{may: passConcreteToNestedItfCallAdj, must: passConcreteToItfCallAdj},
{may: passFuncToNestedIndCallAdj, must: passFuncToNestedIndCallAdj},
{may: passInlinableFuncToNestedIndCallAdj, must: passInlinableFuncToIndCallAdj},
}
func isMay(x scoreAdjustTyp) bool {
return mayToMust(x) != 0
}
func isMust(x scoreAdjustTyp) bool {
return mustToMay(x) != 0
}
func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
for _, v := range mayMust {
if x == v.may {
return v.must
}
}
return 0
}
func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
for _, v := range mayMust {
if x == v.must {
return v.may
}
}
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.
func computeCallSiteScore(callee *ir.Func, calleeProps *FuncProps, call ir.Node, csflags CSPropBits) (int, scoreAdjustTyp) {
// Start with the size-based score for the callee.
score := int(callee.Inl.Cost)
var tmask scoreAdjustTyp
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= scoring call to %s at %s , initial=%d\n",
callee.Sym().Name, fmtFullPos(call.Pos()), score)
}
// First some score adjustments to discourage inlining in selected cases.
if csflags&CallSiteOnPanicPath != 0 {
score, tmask = adjustScore(panicPathAdj, score, tmask)
}
if csflags&CallSiteInInitFunc != 0 {
score, tmask = adjustScore(initFuncAdj, score, tmask)
}
// Then adjustments to encourage inlining in selected cases.
if csflags&CallSiteInLoop != 0 {
score, tmask = adjustScore(inLoopAdj, score, tmask)
}
// Walk through the actual expressions being passed at the call.
calleeRecvrParms := callee.Type().RecvParams()
ce := call.(*ir.CallExpr)
for idx := range ce.Args {
// ignore blanks
if calleeRecvrParms[idx].Sym == nil ||
calleeRecvrParms[idx].Sym.IsBlank() {
continue
}
arg := ce.Args[idx]
pflag := calleeProps.ParamFlags[idx]
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= arg %d of %d: val %v flags=%s\n",
idx, len(ce.Args), arg, pflag.String())
}
_, islit := isLiteral(arg)
iscci := isConcreteConvIface(arg)
fname, isfunc, _ := isFuncName(arg)
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= isLit=%v iscci=%v isfunc=%v for arg %v\n", islit, iscci, isfunc, arg)
}
if islit {
if pflag&ParamMayFeedIfOrSwitch != 0 {
score, tmask = adjustScore(passConstToNestedIfAdj, score, tmask)
}
if pflag&ParamFeedsIfOrSwitch != 0 {
score, tmask = adjustScore(passConstToIfAdj, score, tmask)
}
}
if iscci {
// FIXME: ideally here it would be nice to make a
// distinction between the inlinable case and the
// non-inlinable case, but this is hard to do. Example:
//
// type I interface { Tiny() int; Giant() }
// type Conc struct { x int }
// func (c *Conc) Tiny() int { return 42 }
// func (c *Conc) Giant() { <huge amounts of code> }
//
// func passConcToItf(c *Conc) {
// makesItfMethodCall(c)
// }
//
// In the code above, function properties will only tell
// us that 'makesItfMethodCall' invokes a method on its
// interface parameter, but we don't know whether it calls
// "Tiny" or "Giant". If we knew if called "Tiny", then in
// theory in addition to converting the interface call to
// a direct call, we could also inline (in which case
// we'd want to decrease the score even more).
//
// One thing we could do (not yet implemented) is iterate
// through all of the methods of "*Conc" that allow it to
// satisfy I, and if all are inlinable, then exploit that.
if pflag&ParamMayFeedInterfaceMethodCall != 0 {
score, tmask = adjustScore(passConcreteToNestedItfCallAdj, score, tmask)
}
if pflag&ParamFeedsInterfaceMethodCall != 0 {
score, tmask = adjustScore(passConcreteToItfCallAdj, score, tmask)
}
}
if isfunc {
mayadj := passFuncToNestedIndCallAdj
mustadj := passFuncToIndCallAdj
if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
mayadj = passInlinableFuncToNestedIndCallAdj
mustadj = passInlinableFuncToIndCallAdj
}
if pflag&ParamMayFeedIndirectCall != 0 {
score, tmask = adjustScore(mayadj, score, tmask)
}
if pflag&ParamFeedsIndirectCall != 0 {
score, tmask = adjustScore(mustadj, score, tmask)
}
}
}
return score, tmask
}
func adjustScore(typ scoreAdjustTyp, score int, mask scoreAdjustTyp) (int, scoreAdjustTyp) {
if isMust(typ) {
if mask&typ != 0 {
return score, mask
}
may := mustToMay(typ)
if mask&may != 0 {
// promote may to must, so undo may
score -= adjValue(may)
mask &^= may
}
} else if isMay(typ) {
must := mayToMust(typ)
if mask&(must|typ) != 0 {
return score, mask
}
}
if mask&typ == 0 {
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= applying adj %d for %s\n",
adjValue(typ), typ.String())
}
score += adjValue(typ)
mask |= typ
}
return score, mask
}

View File

@ -13,7 +13,7 @@ package params
// 0 ParamFeedsIndirectCall
// <endpropsdump>
// {"Flags":0,"ParamFlags":[8],"ResultFlags":[]}
// callsite: acrosscall.go:20:12|0 flagstr "" flagval 0
// callsite: acrosscall.go:20:12|0 flagstr "" flagval 0 score 60 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_indirect_call_via_call_toplevel(f func(int)) {
@ -25,7 +25,7 @@ func T_feeds_indirect_call_via_call_toplevel(f func(int)) {
// 0 ParamMayFeedIndirectCall
// <endpropsdump>
// {"Flags":0,"ParamFlags":[16],"ResultFlags":[]}
// callsite: acrosscall.go:33:13|0 flagstr "" flagval 0
// callsite: acrosscall.go:33:13|0 flagstr "" flagval 0 score 60 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_indirect_call_via_call_conditional(f func(int)) {
@ -39,7 +39,7 @@ func T_feeds_indirect_call_via_call_conditional(f func(int)) {
// 0 ParamMayFeedIndirectCall
// <endpropsdump>
// {"Flags":0,"ParamFlags":[16],"ResultFlags":[]}
// callsite: acrosscall.go:46:23|0 flagstr "" flagval 0
// callsite: acrosscall.go:46:23|0 flagstr "" flagval 0 score 64 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_conditional_indirect_call_via_call_toplevel(f func(int)) {
@ -51,7 +51,7 @@ func T_feeds_conditional_indirect_call_via_call_toplevel(f func(int)) {
// 0 ParamFeedsIfOrSwitch
// <endpropsdump>
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[]}
// callsite: acrosscall.go:58:9|0 flagstr "" flagval 0
// callsite: acrosscall.go:58:9|0 flagstr "" flagval 0 score 8 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_if_via_call(x int) {
@ -63,7 +63,7 @@ func T_feeds_if_via_call(x int) {
// 0 ParamMayFeedIfOrSwitch
// <endpropsdump>
// {"Flags":0,"ParamFlags":[64],"ResultFlags":[]}
// callsite: acrosscall.go:71:10|0 flagstr "" flagval 0
// callsite: acrosscall.go:71:10|0 flagstr "" flagval 0 score 8 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_if_via_call_conditional(x int) {
@ -77,7 +77,7 @@ func T_feeds_if_via_call_conditional(x int) {
// 0 ParamMayFeedIfOrSwitch
// <endpropsdump>
// {"Flags":0,"ParamFlags":[64],"ResultFlags":[]}
// callsite: acrosscall.go:84:20|0 flagstr "" flagval 0
// callsite: acrosscall.go:84:20|0 flagstr "" flagval 0 score 12 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_feeds_conditional_if_via_call(x int) {
@ -90,9 +90,9 @@ func T_feeds_conditional_if_via_call(x int) {
// 1 ParamFeedsIndirectCall
// <endpropsdump>
// {"Flags":0,"ParamFlags":[24,8],"ResultFlags":[]}
// callsite: acrosscall.go:100:23|1 flagstr "" flagval 0
// callsite: acrosscall.go:101:12|2 flagstr "" flagval 0
// callsite: acrosscall.go:99:12|0 flagstr "" flagval 0
// callsite: acrosscall.go:100:23|1 flagstr "" flagval 0 score 64 mask 0 maskstr ""
// callsite: acrosscall.go:101:12|2 flagstr "" flagval 0 score 60 mask 0 maskstr ""
// callsite: acrosscall.go:99:12|0 flagstr "" flagval 0 score 60 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_multifeeds(f1, f2 func(int)) {
@ -106,7 +106,7 @@ func T_multifeeds(f1, f2 func(int)) {
// 0 ResultAlwaysSameConstant
// <endpropsdump>
// {"Flags":0,"ParamFlags":[],"ResultFlags":[8]}
// callsite: acrosscall.go:113:24|0 flagstr "" flagval 0
// callsite: acrosscall.go:113:24|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_acrosscall_returnsconstant() int {
@ -118,7 +118,7 @@ func T_acrosscall_returnsconstant() int {
// 0 ResultIsAllocatedMem
// <endpropsdump>
// {"Flags":0,"ParamFlags":[],"ResultFlags":[2]}
// callsite: acrosscall.go:125:19|0 flagstr "" flagval 0
// callsite: acrosscall.go:125:19|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_acrosscall_returnsmem() *int {
@ -130,7 +130,7 @@ func T_acrosscall_returnsmem() *int {
// 0 ResultIsConcreteTypeConvertedToInterface
// <endpropsdump>
// {"Flags":0,"ParamFlags":[],"ResultFlags":[4]}
// callsite: acrosscall.go:137:19|0 flagstr "" flagval 0
// callsite: acrosscall.go:137:19|0 flagstr "" flagval 0 score 7 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_acrosscall_returnscci() I {
@ -140,7 +140,7 @@ func T_acrosscall_returnscci() I {
// acrosscall.go T_acrosscall_multiret 146 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
// callsite: acrosscall.go:148:25|0 flagstr "" flagval 0
// callsite: acrosscall.go:148:25|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_acrosscall_multiret(q int) int {
@ -153,8 +153,8 @@ func T_acrosscall_multiret(q int) int {
// acrosscall.go T_acrosscall_multiret2 160 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
// callsite: acrosscall.go:162:25|0 flagstr "" flagval 0
// callsite: acrosscall.go:164:25|1 flagstr "" flagval 0
// callsite: acrosscall.go:162:25|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: acrosscall.go:164:25|1 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_acrosscall_multiret2(q int) int {

View File

@ -13,7 +13,7 @@ import "os"
// calls.go T_call_in_panic_arg 19 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[]}
// callsite: calls.go:21:15|0 flagstr "CallSiteOnPanicPath" flagval 2
// callsite: calls.go:21:15|0 flagstr "CallSiteOnPanicPath" flagval 2 score 42 mask 1 maskstr "panicPathAdj"
// <endcallsites>
// <endfuncpreamble>
func T_call_in_panic_arg(x int) {
@ -25,8 +25,8 @@ func T_call_in_panic_arg(x int) {
// calls.go T_calls_in_loops 32 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[]}
// callsite: calls.go:34:9|0 flagstr "CallSiteInLoop" flagval 1
// callsite: calls.go:37:9|1 flagstr "CallSiteInLoop" flagval 1
// callsite: calls.go:34:9|0 flagstr "CallSiteInLoop" flagval 1 score -3 mask 4 maskstr "inLoopAdj"
// callsite: calls.go:37:9|1 flagstr "CallSiteInLoop" flagval 1 score -3 mask 4 maskstr "inLoopAdj"
// <endcallsites>
// <endfuncpreamble>
func T_calls_in_loops(x int, q []string) {
@ -41,8 +41,8 @@ func T_calls_in_loops(x int, q []string) {
// calls.go T_calls_in_pseudo_loop 48 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[]}
// callsite: calls.go:50:9|0 flagstr "" flagval 0
// callsite: calls.go:54:9|1 flagstr "" flagval 0
// callsite: calls.go:50:9|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:54:9|1 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_calls_in_pseudo_loop(x int, q []string) {
@ -59,9 +59,9 @@ func T_calls_in_pseudo_loop(x int, q []string) {
// calls.go T_calls_on_panic_paths 67 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[]}
// callsite: calls.go:69:9|0 flagstr "" flagval 0
// callsite: calls.go:73:9|1 flagstr "" flagval 0
// callsite: calls.go:77:12|2 flagstr "CallSiteOnPanicPath" flagval 2
// callsite: calls.go:69:9|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:73:9|1 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:77:12|2 flagstr "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
// <endcallsites>
// <endfuncpreamble>
func T_calls_on_panic_paths(x int, q []string) {
@ -84,10 +84,10 @@ func T_calls_on_panic_paths(x int, q []string) {
// 1 ParamNoInfo
// <endpropsdump>
// {"Flags":0,"ParamFlags":[96,0],"ResultFlags":[]}
// callsite: calls.go:103:9|0 flagstr "" flagval 0
// callsite: calls.go:112:9|1 flagstr "" flagval 0
// callsite: calls.go:115:9|2 flagstr "" flagval 0
// callsite: calls.go:119:12|3 flagstr "" flagval 0
// callsite: calls.go:103:9|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:112:9|1 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:115:9|2 flagstr "" flagval 0 score 2 mask 0 maskstr ""
// callsite: calls.go:119:12|3 flagstr "" flagval 0 score 62 mask 0 maskstr ""
// <endcallsites>
// <endfuncpreamble>
func T_calls_not_on_panic_paths(x int, q []string) {
@ -123,20 +123,82 @@ func T_calls_not_on_panic_paths(x int, q []string) {
// calls.go init.0 129 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[],"ResultFlags":[]}
// callsite: calls.go:130:16|0 flagstr "CallSiteInInitFunc" flagval 4
// callsite: calls.go:130:16|0 flagstr "CallSiteInInitFunc" flagval 4 score 22 mask 2 maskstr "initFuncAdj"
// <endcallsites>
// <endfuncpreamble>
func init() {
println(callee(5))
}
// calls.go T_pass_inlinable_func_to_param_feeding_indirect_call 139 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
// callsite: calls.go:140:19|0 flagstr "" flagval 0 score 16 mask 512 maskstr "passInlinableFuncToIndCallAdj"
// <endcallsites>
// <endfuncpreamble>
func T_pass_inlinable_func_to_param_feeding_indirect_call(x int) int {
return callsParam(x, callee)
}
// calls.go T_pass_noninlinable_func_to_param_feeding_indirect_call 149 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
// callsite: calls.go:152:19|0 flagstr "" flagval 0 score 36 mask 128 maskstr "passFuncToIndCallAdj"
// <endcallsites>
// <endfuncpreamble>
func T_pass_noninlinable_func_to_param_feeding_indirect_call(x int) int {
// if we inline callsParam we can convert the indirect call
// to a direct call, but we can't inline it.
return callsParam(x, calleeNoInline)
}
// calls.go T_pass_inlinable_func_to_param_feeding_nested_indirect_call 163 0 1
// ParamFlags
// 0 ParamFeedsIfOrSwitch
// <endpropsdump>
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
// callsite: calls.go:164:25|0 flagstr "" flagval 0 score 27 mask 1024 maskstr "passInlinableFuncToNestedIndCallAdj"
// <endcallsites>
// <endfuncpreamble>
func T_pass_inlinable_func_to_param_feeding_nested_indirect_call(x int) int {
return callsParamNested(x, callee)
}
// calls.go T_pass_noninlinable_func_to_param_feeding_nested_indirect_call 175 0 1
// ParamFlags
// 0 ParamFeedsIfOrSwitch
// <endpropsdump>
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
// callsite: calls.go:176:25|0 flagstr "" flagval 0 score 47 mask 256 maskstr "passFuncToNestedIndCallAdj"
// <endcallsites>
// <endfuncpreamble>
func T_pass_noninlinable_func_to_param_feeding_nested_indirect_call(x int) int {
return callsParamNested(x, calleeNoInline)
}
var G int
func callee(x int) int {
return x
}
func calleeNoInline(x int) int {
defer func() { G++ }()
return x
}
func callsexit(x int) {
println(x)
os.Exit(x)
}
func callsParam(x int, f func(int) int) int {
return f(x)
}
func callsParamNested(x int, f func(int) int) int {
if x < 0 {
return f(x)
}
return 0
}

View File

@ -275,7 +275,7 @@ func T_callsexit(x int) {
// funcflags.go T_exitinexpr 281 0 1
// <endpropsdump>
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[]}
// callsite: funcflags.go:286:18|0 flagstr "CallSiteOnPanicPath" flagval 2
// callsite: funcflags.go:286:18|0 flagstr "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
// <endcallsites>
// <endfuncpreamble>
func T_exitinexpr(x int) {
@ -328,7 +328,7 @@ func T_select_mayreturn(chi chan int, chf chan float32, p *int) int {
// Flags FuncPropNeverReturns
// <endpropsdump>
// {"Flags":1,"ParamFlags":[0],"ResultFlags":[]}
// callsite: funcflags.go:335:15|0 flagstr "CallSiteOnPanicPath" flagval 2
// callsite: funcflags.go:335:15|0 flagstr "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
// <endcallsites>
// <endfuncpreamble>
func T_calls_callsexit(x int) {