Initial commit: Go 1.23 release state
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// Code generated by "stringer -bitset -type ActualExprPropBits"; 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[ActualExprConstant-1]
|
||||
_ = x[ActualExprIsConcreteConvIface-2]
|
||||
_ = x[ActualExprIsFunc-4]
|
||||
_ = x[ActualExprIsInlinableFunc-8]
|
||||
}
|
||||
|
||||
var _ActualExprPropBits_value = [...]uint64{
|
||||
0x1, /* ActualExprConstant */
|
||||
0x2, /* ActualExprIsConcreteConvIface */
|
||||
0x4, /* ActualExprIsFunc */
|
||||
0x8, /* ActualExprIsInlinableFunc */
|
||||
}
|
||||
|
||||
const _ActualExprPropBits_name = "ActualExprConstantActualExprIsConcreteConvIfaceActualExprIsFuncActualExprIsInlinableFunc"
|
||||
|
||||
var _ActualExprPropBits_index = [...]uint8{0, 18, 47, 63, 88}
|
||||
|
||||
func (i ActualExprPropBits) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
remain := uint64(i)
|
||||
seen := false
|
||||
|
||||
for k, v := range _ActualExprPropBits_value {
|
||||
x := _ActualExprPropBits_name[_ActualExprPropBits_index[k]:_ActualExprPropBits_index[k+1]]
|
||||
if v == 0 {
|
||||
if i == 0 {
|
||||
b.WriteString(x)
|
||||
return b.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (v & remain) == v {
|
||||
remain &^= v
|
||||
x := _ActualExprPropBits_name[_ActualExprPropBits_index[k]:_ActualExprPropBits_index[k+1]]
|
||||
if seen {
|
||||
b.WriteString("|")
|
||||
}
|
||||
seen = true
|
||||
b.WriteString(x)
|
||||
}
|
||||
}
|
||||
if remain == 0 {
|
||||
return b.String()
|
||||
}
|
||||
return "ActualExprPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")"
|
||||
}
|
||||
370
src/cmd/compile/internal/inline/inlheur/analyze.go
Normal file
370
src/cmd/compile/internal/inline/inlheur/analyze.go
Normal file
@@ -0,0 +1,370 @@
|
||||
// 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/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/types"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"internal/buildcfg"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
debugTraceFuncs = 1 << iota
|
||||
debugTraceFuncFlags
|
||||
debugTraceResults
|
||||
debugTraceParams
|
||||
debugTraceExprClassify
|
||||
debugTraceCalls
|
||||
debugTraceScoring
|
||||
)
|
||||
|
||||
// propAnalyzer interface is used for defining one or more analyzer
|
||||
// helper objects, each tasked with computing some specific subset of
|
||||
// the properties we're interested in. The assumption is that
|
||||
// properties are independent, so each new analyzer that implements
|
||||
// this interface can operate entirely on its own. For a given analyzer
|
||||
// there will be a sequence of calls to nodeVisitPre and nodeVisitPost
|
||||
// as the nodes within a function are visited, then a followup call to
|
||||
// setResults so that the analyzer can transfer its results into the
|
||||
// final properties object.
|
||||
type propAnalyzer interface {
|
||||
nodeVisitPre(n ir.Node)
|
||||
nodeVisitPost(n ir.Node)
|
||||
setResults(funcProps *FuncProps)
|
||||
}
|
||||
|
||||
// fnInlHeur contains inline heuristics state information about a
|
||||
// specific Go function being analyzed/considered by the inliner. Note
|
||||
// that in addition to constructing a fnInlHeur object by analyzing a
|
||||
// specific *ir.Func, there is also code in the test harness
|
||||
// (funcprops_test.go) that builds up fnInlHeur's by reading in and
|
||||
// parsing a dump. This is the reason why we have file/fname/line
|
||||
// fields below instead of just an *ir.Func field.
|
||||
type fnInlHeur struct {
|
||||
props *FuncProps
|
||||
cstab CallSiteTab
|
||||
fname string
|
||||
file string
|
||||
line uint
|
||||
}
|
||||
|
||||
var fpmap = map[*ir.Func]fnInlHeur{}
|
||||
|
||||
// AnalyzeFunc computes function properties for fn and its contained
|
||||
// closures, updating the global 'fpmap' table. It is assumed that
|
||||
// "CanInline" has been run on fn and on the closures that feed
|
||||
// directly into calls; other closures not directly called will also
|
||||
// be checked inlinability for inlinability here in case they are
|
||||
// returned as a result.
|
||||
func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func), budgetForFunc func(*ir.Func) int32, inlineMaxBudget int) {
|
||||
if fpmap == nil {
|
||||
// If fpmap is nil this indicates that the main inliner pass is
|
||||
// complete and we're doing inlining of wrappers (no heuristics
|
||||
// used here).
|
||||
return
|
||||
}
|
||||
if fn.OClosure != nil {
|
||||
// closures will be processed along with their outer enclosing func.
|
||||
return
|
||||
}
|
||||
enableDebugTraceIfEnv()
|
||||
if debugTrace&debugTraceFuncs != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= AnalyzeFunc(%v)\n", fn)
|
||||
}
|
||||
// Build up a list containing 'fn' and any closures it contains. Along
|
||||
// the way, test to see whether each closure is inlinable in case
|
||||
// we might be returning it.
|
||||
funcs := []*ir.Func{fn}
|
||||
ir.VisitFuncAndClosures(fn, func(n ir.Node) {
|
||||
if clo, ok := n.(*ir.ClosureExpr); ok {
|
||||
funcs = append(funcs, clo.Func)
|
||||
}
|
||||
})
|
||||
|
||||
// Analyze the list of functions. We want to visit a given func
|
||||
// only after the closures it contains have been processed, so
|
||||
// iterate through the list in reverse order. Once a function has
|
||||
// been analyzed, revisit the question of whether it should be
|
||||
// inlinable; if it is over the default hairiness 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, nameFinder)
|
||||
revisitInlinability(f, funcProps, budgetForFunc)
|
||||
if f.Inl != nil {
|
||||
f.Inl.Properties = funcProps.SerializeToString()
|
||||
}
|
||||
}
|
||||
disableDebugTrace()
|
||||
}
|
||||
|
||||
// TearDown is invoked at the end of the main inlining pass; doing
|
||||
// function analysis and call site scoring is unlikely to help a lot
|
||||
// after this point, so nil out fpmap and other globals to reclaim
|
||||
// storage.
|
||||
func TearDown() {
|
||||
fpmap = nil
|
||||
scoreCallsCache.tab = nil
|
||||
scoreCallsCache.csl = nil
|
||||
}
|
||||
|
||||
func analyzeFunc(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) *FuncProps {
|
||||
if funcInlHeur, ok := fpmap[fn]; ok {
|
||||
return funcInlHeur.props
|
||||
}
|
||||
funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget, nf)
|
||||
file, line := fnFileLine(fn)
|
||||
entry := fnInlHeur{
|
||||
fname: fn.Sym().Name,
|
||||
file: file,
|
||||
line: line,
|
||||
props: funcProps,
|
||||
cstab: fcstab,
|
||||
}
|
||||
fn.SetNeverReturns(entry.props.Flags&FuncPropNeverReturns != 0)
|
||||
fpmap[fn] = entry
|
||||
if fn.Inl != nil && fn.Inl.Properties == "" {
|
||||
fn.Inl.Properties = entry.props.SerializeToString()
|
||||
}
|
||||
return funcProps
|
||||
}
|
||||
|
||||
// revisitInlinability revisits the question of whether to continue to
|
||||
// treat function 'fn' as an inline candidate based on the set of
|
||||
// properties we've computed for it. If (for example) it has an
|
||||
// initial size score of 150 and no interesting properties to speak
|
||||
// of, then there isn't really any point to moving ahead with it as an
|
||||
// inline candidate.
|
||||
func revisitInlinability(fn *ir.Func, funcProps *FuncProps, budgetForFunc func(*ir.Func) int32) {
|
||||
if fn.Inl == nil {
|
||||
return
|
||||
}
|
||||
maxAdj := int32(LargestNegativeScoreAdjustment(fn, funcProps))
|
||||
budget := budgetForFunc(fn)
|
||||
if fn.Inl.Cost+maxAdj > budget {
|
||||
fn.Inl = nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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, 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, 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, nf)
|
||||
return funcProps, cstab
|
||||
}
|
||||
|
||||
func runAnalyzersOnFunction(fn *ir.Func, analyzers []propAnalyzer) {
|
||||
var doNode func(ir.Node) bool
|
||||
doNode = func(n ir.Node) bool {
|
||||
for _, a := range analyzers {
|
||||
a.nodeVisitPre(n)
|
||||
}
|
||||
ir.DoChildren(n, doNode)
|
||||
for _, a := range analyzers {
|
||||
a.nodeVisitPost(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
doNode(fn)
|
||||
}
|
||||
|
||||
func propsForFunc(fn *ir.Func) *FuncProps {
|
||||
if funcInlHeur, ok := fpmap[fn]; ok {
|
||||
return funcInlHeur.props
|
||||
} else if fn.Inl != nil && fn.Inl.Properties != "" {
|
||||
// FIXME: considering adding some sort of cache or table
|
||||
// for deserialized properties of imported functions.
|
||||
return DeserializeFromString(fn.Inl.Properties)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fnFileLine(fn *ir.Func) (string, uint) {
|
||||
p := base.Ctxt.InnermostPos(fn.Pos())
|
||||
return filepath.Base(p.Filename()), p.Line()
|
||||
}
|
||||
|
||||
func Enabled() bool {
|
||||
return buildcfg.Experiment.NewInliner || UnitTesting()
|
||||
}
|
||||
|
||||
func UnitTesting() bool {
|
||||
return base.Debug.DumpInlFuncProps != "" ||
|
||||
base.Debug.DumpInlCallSiteScores != 0
|
||||
}
|
||||
|
||||
// DumpFuncProps computes and caches function properties for the func
|
||||
// 'fn', writing out a description of the previously computed 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) {
|
||||
if fn != nil {
|
||||
if fn.OClosure != nil {
|
||||
// closures will be processed along with their outer enclosing func.
|
||||
return
|
||||
}
|
||||
captureFuncDumpEntry(fn)
|
||||
ir.VisitFuncAndClosures(fn, func(n ir.Node) {
|
||||
if clo, ok := n.(*ir.ClosureExpr); ok {
|
||||
captureFuncDumpEntry(clo.Func)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
emitDumpToFile(dumpfile)
|
||||
}
|
||||
}
|
||||
|
||||
// emitDumpToFile writes out the buffer function property dump entries
|
||||
// to a file, for unit testing. Dump entries need to be sorted by
|
||||
// definition line, and due to generics we need to account for the
|
||||
// possibility that several ir.Func's will have the same def line.
|
||||
func emitDumpToFile(dumpfile string) {
|
||||
mode := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
|
||||
if dumpfile[0] == '+' {
|
||||
dumpfile = dumpfile[1:]
|
||||
mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
|
||||
}
|
||||
if dumpfile[0] == '%' {
|
||||
dumpfile = dumpfile[1:]
|
||||
d, b := filepath.Dir(dumpfile), filepath.Base(dumpfile)
|
||||
ptag := strings.ReplaceAll(types.LocalPkg.Path, "/", ":")
|
||||
dumpfile = d + "/" + ptag + "." + b
|
||||
}
|
||||
outf, err := os.OpenFile(dumpfile, mode, 0644)
|
||||
if err != nil {
|
||||
base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
|
||||
}
|
||||
defer outf.Close()
|
||||
dumpFilePreamble(outf)
|
||||
|
||||
atline := map[uint]uint{}
|
||||
sl := make([]fnInlHeur, 0, len(dumpBuffer))
|
||||
for _, e := range dumpBuffer {
|
||||
sl = append(sl, e)
|
||||
atline[e.line] = atline[e.line] + 1
|
||||
}
|
||||
sl = sortFnInlHeurSlice(sl)
|
||||
|
||||
prevline := uint(0)
|
||||
for _, entry := range sl {
|
||||
idx := uint(0)
|
||||
if prevline == entry.line {
|
||||
idx++
|
||||
}
|
||||
prevline = entry.line
|
||||
atl := atline[entry.line]
|
||||
if err := dumpFnPreamble(outf, &entry, nil, idx, atl); err != nil {
|
||||
base.Fatalf("function props dump: %v\n", err)
|
||||
}
|
||||
}
|
||||
dumpBuffer = nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// avoid capturing compiler-generated equality funcs.
|
||||
if strings.HasPrefix(fn.Sym().Name, ".eq.") {
|
||||
return
|
||||
}
|
||||
funcInlHeur, ok := fpmap[fn]
|
||||
if !ok {
|
||||
// Missing entry is expected for functions that are too large
|
||||
// to inline. We still want to write out call site scores in
|
||||
// this case however.
|
||||
funcInlHeur = fnInlHeur{cstab: callSiteTab}
|
||||
}
|
||||
if dumpBuffer == nil {
|
||||
dumpBuffer = make(map[*ir.Func]fnInlHeur)
|
||||
}
|
||||
if _, ok := dumpBuffer[fn]; ok {
|
||||
return
|
||||
}
|
||||
if debugTrace&debugTraceFuncs != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= capturing dump for %v:\n", fn)
|
||||
}
|
||||
dumpBuffer[fn] = funcInlHeur
|
||||
}
|
||||
|
||||
// dumpFilePreamble writes out a file-level preamble for a given
|
||||
// Go function as part of a function properties dump.
|
||||
func dumpFilePreamble(w io.Writer) {
|
||||
fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
|
||||
fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
|
||||
fmt.Fprintf(w, "// for more information on the format of this file.\n")
|
||||
fmt.Fprintf(w, "// %s\n", preambleDelimiter)
|
||||
}
|
||||
|
||||
// dumpFnPreamble writes out a function-level preamble for a given
|
||||
// Go function as part of a function properties dump. See the
|
||||
// README.txt file in testdata/props for more on the format of
|
||||
// this preamble.
|
||||
func dumpFnPreamble(w io.Writer, funcInlHeur *fnInlHeur, ecst encodedCallSiteTab, idx, atl uint) error {
|
||||
fmt.Fprintf(w, "// %s %s %d %d %d\n",
|
||||
funcInlHeur.file, funcInlHeur.fname, funcInlHeur.line, idx, atl)
|
||||
// emit props as comments, followed by delimiter
|
||||
fmt.Fprintf(w, "%s// %s\n", funcInlHeur.props.ToString("// "), comDelimiter)
|
||||
data, err := json.Marshal(funcInlHeur.props)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal error %v\n", err)
|
||||
}
|
||||
fmt.Fprintf(w, "// %s\n", string(data))
|
||||
dumpCallSiteComments(w, funcInlHeur.cstab, ecst)
|
||||
fmt.Fprintf(w, "// %s\n", fnDelimiter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sortFnInlHeurSlice sorts a slice of fnInlHeur based on
|
||||
// the starting line of the function definition, then by name.
|
||||
func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
|
||||
sort.SliceStable(sl, func(i, j int) bool {
|
||||
if sl[i].line != sl[j].line {
|
||||
return sl[i].line < sl[j].line
|
||||
}
|
||||
return sl[i].fname < sl[j].fname
|
||||
})
|
||||
return sl
|
||||
}
|
||||
|
||||
// delimiters written to various preambles to make parsing of
|
||||
// dumps easier.
|
||||
const preambleDelimiter = "<endfilepreamble>"
|
||||
const fnDelimiter = "<endfuncpreamble>"
|
||||
const comDelimiter = "<endpropsdump>"
|
||||
const csDelimiter = "<endcallsites>"
|
||||
|
||||
// dumpBuffer stores up function properties dumps when
|
||||
// "-d=dumpinlfuncprops=..." is in effect.
|
||||
var dumpBuffer map[*ir.Func]fnInlHeur
|
||||
@@ -0,0 +1,413 @@
|
||||
// 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/pgoir"
|
||||
"cmd/compile/internal/typecheck"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type callSiteAnalyzer struct {
|
||||
fn *ir.Func
|
||||
*nameFinder
|
||||
}
|
||||
|
||||
type callSiteTableBuilder struct {
|
||||
fn *ir.Func
|
||||
*nameFinder
|
||||
cstab CallSiteTab
|
||||
ptab map[ir.Node]pstate
|
||||
nstack []ir.Node
|
||||
loopNest int
|
||||
isInit bool
|
||||
}
|
||||
|
||||
func makeCallSiteAnalyzer(fn *ir.Func) *callSiteAnalyzer {
|
||||
return &callSiteAnalyzer{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// computeCallSiteTable builds and returns a table of call sites for
|
||||
// the specified region in function fn. A region here corresponds to a
|
||||
// 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, nf *nameFinder) CallSiteTab {
|
||||
cstb := makeCallSiteTableBuilder(fn, cstab, ptab, loopNestingLevel, nf)
|
||||
var doNode func(ir.Node) bool
|
||||
doNode = func(n ir.Node) bool {
|
||||
cstb.nodeVisitPre(n)
|
||||
ir.DoChildren(n, doNode)
|
||||
cstb.nodeVisitPost(n)
|
||||
return false
|
||||
}
|
||||
for _, n := range region {
|
||||
doNode(n)
|
||||
}
|
||||
return cstb.cstab
|
||||
}
|
||||
|
||||
func (cstb *callSiteTableBuilder) flagsForNode(call *ir.CallExpr) CSPropBits {
|
||||
var r CSPropBits
|
||||
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= analyzing call at %s\n",
|
||||
fmtFullPos(call.Pos()))
|
||||
}
|
||||
|
||||
// Set a bit if this call is within a loop.
|
||||
if cstb.loopNest > 0 {
|
||||
r |= CallSiteInLoop
|
||||
}
|
||||
|
||||
// Set a bit if the call is within an init function (either
|
||||
// compiler-generated or user-written).
|
||||
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(cstb.fn) {
|
||||
r = cstb.determinePanicPathBits(call, r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// determinePanicPathBits updates the CallSiteOnPanicPath bit within
|
||||
// "r" if we think this call is on an unconditional path to
|
||||
// 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 (cstb *callSiteTableBuilder) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
|
||||
cstb.nstack = append(cstb.nstack, call)
|
||||
defer func() {
|
||||
cstb.nstack = cstb.nstack[:len(cstb.nstack)-1]
|
||||
}()
|
||||
|
||||
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 {
|
||||
isStmt = false
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
if n.Op() == ir.OPANIC {
|
||||
r |= CallSiteOnPanicPath
|
||||
break
|
||||
}
|
||||
if v, ok := cstb.ptab[n]; ok {
|
||||
if v == psCallsPanic {
|
||||
r |= CallSiteOnPanicPath
|
||||
break
|
||||
}
|
||||
if isStmt {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// propsForArg returns property bits for a given call argument expression arg.
|
||||
func (cstb *callSiteTableBuilder) propsForArg(arg ir.Node) ActualExprPropBits {
|
||||
if cval := cstb.constValue(arg); cval != nil {
|
||||
return ActualExprConstant
|
||||
}
|
||||
if cstb.isConcreteConvIface(arg) {
|
||||
return ActualExprIsConcreteConvIface
|
||||
}
|
||||
fname := cstb.funcName(arg)
|
||||
if fname != nil {
|
||||
if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
|
||||
return ActualExprIsInlinableFunc
|
||||
}
|
||||
return ActualExprIsFunc
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// argPropsForCall returns a slice of argument properties for the
|
||||
// expressions being passed to the callee in the specific call
|
||||
// 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 (cstb *callSiteTableBuilder) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBits {
|
||||
rv := make([]ActualExprPropBits, len(ce.Args))
|
||||
somethingInteresting := false
|
||||
for idx := range ce.Args {
|
||||
argProp := cstb.propsForArg(ce.Args[idx])
|
||||
somethingInteresting = somethingInteresting || (argProp != 0)
|
||||
rv[idx] = argProp
|
||||
}
|
||||
if !somethingInteresting {
|
||||
return nil
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// FIXME: maybe bulk-allocate these?
|
||||
cs := &CallSite{
|
||||
Call: call,
|
||||
Callee: callee,
|
||||
Assign: cstb.containingAssignment(call),
|
||||
ArgProps: argProps,
|
||||
Flags: flags,
|
||||
ID: uint(len(cstb.cstab)),
|
||||
}
|
||||
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)
|
||||
panic("bad")
|
||||
}
|
||||
// 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)
|
||||
|
||||
if cstb.cstab == nil {
|
||||
cstb.cstab = make(CallSiteTab)
|
||||
}
|
||||
cstb.cstab[call] = cs
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= added callsite: caller=%v callee=%v n=%s\n",
|
||||
cstb.fn, callee, fmtFullPos(call.Pos()))
|
||||
}
|
||||
}
|
||||
|
||||
func (cstb *callSiteTableBuilder) nodeVisitPre(n ir.Node) {
|
||||
switch n.Op() {
|
||||
case ir.ORANGE, ir.OFOR:
|
||||
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
|
||||
cstb.loopNest++
|
||||
}
|
||||
case ir.OCALLFUNC:
|
||||
ce := n.(*ir.CallExpr)
|
||||
callee := pgoir.DirectCallee(ce.Fun)
|
||||
if callee != nil && callee.Inl != nil {
|
||||
cstb.addCallSite(callee, ce)
|
||||
}
|
||||
}
|
||||
cstb.nstack = append(cstb.nstack, n)
|
||||
}
|
||||
|
||||
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)) {
|
||||
cstb.loopNest--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loopBody(n ir.Node) ir.Nodes {
|
||||
if forst, ok := n.(*ir.ForStmt); ok {
|
||||
return forst.Body
|
||||
}
|
||||
if rst, ok := n.(*ir.RangeStmt); ok {
|
||||
return rst.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasTopLevelLoopBodyReturnOrBreak examines the body of a "for" or
|
||||
// "range" loop to try to verify that it is a real loop, as opposed to
|
||||
// a construct that is syntactically loopy but doesn't actually iterate
|
||||
// multiple times, like:
|
||||
//
|
||||
// for {
|
||||
// blah()
|
||||
// return 1
|
||||
// }
|
||||
//
|
||||
// [Remark: the pattern above crops up quite a bit in the source code
|
||||
// for the compiler itself, e.g. the auto-generated rewrite code]
|
||||
//
|
||||
// Note that we don't look for GOTO statements here, so it's possible
|
||||
// we'll get the wrong result for a loop with complicated control
|
||||
// jumps via gotos.
|
||||
func hasTopLevelLoopBodyReturnOrBreak(loopBody ir.Nodes) bool {
|
||||
for _, n := range loopBody {
|
||||
if n.Op() == ir.ORETURN || n.Op() == ir.OBREAK {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// containingAssignment returns the top-level assignment statement
|
||||
// for a statement level function call "n". Examples:
|
||||
//
|
||||
// x := foo()
|
||||
// x, y := bar(z, baz())
|
||||
// if blah() { ...
|
||||
//
|
||||
// Here the top-level assignment statement for the foo() call is the
|
||||
// statement assigning to "x"; the top-level assignment for "bar()"
|
||||
// call is the assignment to x,y. For the baz() and blah() calls,
|
||||
// there is no top level assignment statement.
|
||||
//
|
||||
// The unstated goal here is that we want to use the containing
|
||||
// assignment to establish a connection between a given call and the
|
||||
// variables to which its results/returns are being assigned.
|
||||
//
|
||||
// Note that for the "bar" command above, the front end sometimes
|
||||
// decomposes this into two assignments, the first one assigning the
|
||||
// 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 (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.
|
||||
assignsOnlyAutoTemps := func(x ir.Node) bool {
|
||||
alst := x.(*ir.AssignListStmt)
|
||||
oa2init := alst.Init()
|
||||
if len(oa2init) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range oa2init {
|
||||
d := v.(*ir.Decl)
|
||||
if !ir.IsAutoTmp(d.X) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Simple case: x := foo()
|
||||
if parent.Op() == ir.OAS {
|
||||
return parent
|
||||
}
|
||||
|
||||
// Multi-return case: x, y := bar()
|
||||
if parent.Op() == ir.OAS2FUNC {
|
||||
// Hack city: if the result vars are auto-temps, try looking
|
||||
// for an outer assignment in the tree. The code shape we're
|
||||
// looking for here is:
|
||||
//
|
||||
// OAS1({x,y},OCONVNOP(OAS2FUNC({auto1,auto2},OCALLFUNC(bar))))
|
||||
//
|
||||
if assignsOnlyAutoTemps(parent) {
|
||||
par2 := cstb.nstack[len(cstb.nstack)-2]
|
||||
if par2.Op() == ir.OAS2 {
|
||||
return par2
|
||||
}
|
||||
if par2.Op() == ir.OCONVNOP {
|
||||
par3 := cstb.nstack[len(cstb.nstack)-3]
|
||||
if par3.Op() == ir.OAS2 {
|
||||
return par3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCallsiteTable handles updating of callerfn's call site table
|
||||
// after an inlined has been carried out, e.g. the call at 'n' as been
|
||||
// turned into the inlined call expression 'ic' within function
|
||||
// callerfn. The chief thing of interest here is to make sure that any
|
||||
// call nodes within 'ic' are added to the call site table for
|
||||
// 'callerfn' and scored appropriately.
|
||||
func UpdateCallsiteTable(callerfn *ir.Func, n *ir.CallExpr, ic *ir.InlinedCallExpr) {
|
||||
enableDebugTraceIfEnv()
|
||||
defer disableDebugTrace()
|
||||
|
||||
funcInlHeur, ok := fpmap[callerfn]
|
||||
if !ok {
|
||||
// This can happen for compiler-generated wrappers.
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= early exit, no entry for caller fn %v\n", callerfn)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= UpdateCallsiteTable(caller=%v, cs=%s)\n",
|
||||
callerfn, fmtFullPos(n.Pos()))
|
||||
}
|
||||
|
||||
// Mark the call in question as inlined.
|
||||
oldcs, ok := funcInlHeur.cstab[n]
|
||||
if !ok {
|
||||
// This can happen for compiler-generated wrappers.
|
||||
return
|
||||
}
|
||||
oldcs.aux |= csAuxInlined
|
||||
|
||||
if debugTrace&debugTraceCalls != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= marked as inlined: callee=%v %s\n",
|
||||
oldcs.Callee, EncodeCallSiteKey(oldcs))
|
||||
}
|
||||
|
||||
// Walk the inlined call region to collect new callsites.
|
||||
var icp pstate
|
||||
if oldcs.Flags&CallSiteOnPanicPath != 0 {
|
||||
icp = psCallsPanic
|
||||
}
|
||||
var loopNestLevel int
|
||||
if oldcs.Flags&CallSiteInLoop != 0 {
|
||||
loopNestLevel = 1
|
||||
}
|
||||
ptab := map[ir.Node]pstate{ic: icp}
|
||||
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. 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
|
||||
csa.scoreCallsRegion(callerfn, ic.Body, icstab, doCallResults, ic)
|
||||
}
|
||||
356
src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go
Normal file
356
src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go
Normal file
@@ -0,0 +1,356 @@
|
||||
// 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/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/types"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// funcFlagsAnalyzer computes the "Flags" value for the FuncProps
|
||||
// object we're computing. The main item of interest here is "nstate",
|
||||
// which stores the disposition of a given ir Node with respect to the
|
||||
// flags/properties we're trying to compute.
|
||||
type funcFlagsAnalyzer struct {
|
||||
fn *ir.Func
|
||||
nstate map[ir.Node]pstate
|
||||
noInfo bool // set if we see something inscrutable/un-analyzable
|
||||
}
|
||||
|
||||
// pstate keeps track of the disposition of a given node and its
|
||||
// children with respect to panic/exit calls.
|
||||
type pstate int
|
||||
|
||||
const (
|
||||
psNoInfo pstate = iota // nothing interesting about this node
|
||||
psCallsPanic // node causes call to panic or os.Exit
|
||||
psMayReturn // executing node may trigger a "return" stmt
|
||||
psTop // dataflow lattice "top" element
|
||||
)
|
||||
|
||||
func makeFuncFlagsAnalyzer(fn *ir.Func) *funcFlagsAnalyzer {
|
||||
return &funcFlagsAnalyzer{
|
||||
fn: fn,
|
||||
nstate: make(map[ir.Node]pstate),
|
||||
}
|
||||
}
|
||||
|
||||
// setResults transfers func flag results to 'funcProps'.
|
||||
func (ffa *funcFlagsAnalyzer) setResults(funcProps *FuncProps) {
|
||||
var rv FuncPropBits
|
||||
if !ffa.noInfo && ffa.stateForList(ffa.fn.Body) == psCallsPanic {
|
||||
rv = FuncPropNeverReturns
|
||||
}
|
||||
// This is slightly hacky and not at all required, but include a
|
||||
// special case for main.main, which often ends in a call to
|
||||
// os.Exit. People who write code like this (very common I
|
||||
// imagine)
|
||||
//
|
||||
// func main() {
|
||||
// rc = perform()
|
||||
// ...
|
||||
// foo()
|
||||
// os.Exit(rc)
|
||||
// }
|
||||
//
|
||||
// will be constantly surprised when foo() is inlined in many
|
||||
// other spots in the program but not in main().
|
||||
if isMainMain(ffa.fn) {
|
||||
rv &^= FuncPropNeverReturns
|
||||
}
|
||||
funcProps.Flags = rv
|
||||
}
|
||||
|
||||
func (ffa *funcFlagsAnalyzer) getState(n ir.Node) pstate {
|
||||
return ffa.nstate[n]
|
||||
}
|
||||
|
||||
func (ffa *funcFlagsAnalyzer) setState(n ir.Node, st pstate) {
|
||||
if st != psNoInfo {
|
||||
ffa.nstate[n] = st
|
||||
}
|
||||
}
|
||||
|
||||
func (ffa *funcFlagsAnalyzer) updateState(n ir.Node, st pstate) {
|
||||
if st == psNoInfo {
|
||||
delete(ffa.nstate, n)
|
||||
} else {
|
||||
ffa.nstate[n] = st
|
||||
}
|
||||
}
|
||||
|
||||
func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate {
|
||||
return ffa.nstate
|
||||
}
|
||||
|
||||
// blockCombine merges together states as part of a linear sequence of
|
||||
// statements, where 'pred' and 'succ' are analysis results for a pair
|
||||
// of consecutive statements. Examples:
|
||||
//
|
||||
// case 1: case 2:
|
||||
// panic("foo") if q { return x } <-pred
|
||||
// return x panic("boo") <-succ
|
||||
//
|
||||
// In case 1, since the pred state is "always panic" it doesn't matter
|
||||
// what the succ state is, hence the state for the combination of the
|
||||
// two blocks is "always panics". In case 2, because there is a path
|
||||
// to return that avoids the panic in succ, the state for the
|
||||
// combination of the two statements is "may return".
|
||||
func blockCombine(pred, succ pstate) pstate {
|
||||
switch succ {
|
||||
case psTop:
|
||||
return pred
|
||||
case psMayReturn:
|
||||
if pred == psCallsPanic {
|
||||
return psCallsPanic
|
||||
}
|
||||
return psMayReturn
|
||||
case psNoInfo:
|
||||
return pred
|
||||
case psCallsPanic:
|
||||
if pred == psMayReturn {
|
||||
return psMayReturn
|
||||
}
|
||||
return psCallsPanic
|
||||
}
|
||||
panic("should never execute")
|
||||
}
|
||||
|
||||
// branchCombine combines two states at a control flow branch point where
|
||||
// either p1 or p2 executes (as in an "if" statement).
|
||||
func branchCombine(p1, p2 pstate) pstate {
|
||||
if p1 == psCallsPanic && p2 == psCallsPanic {
|
||||
return psCallsPanic
|
||||
}
|
||||
if p1 == psMayReturn || p2 == psMayReturn {
|
||||
return psMayReturn
|
||||
}
|
||||
return psNoInfo
|
||||
}
|
||||
|
||||
// stateForList walks through a list of statements and computes the
|
||||
// state/disposition for the entire list as a whole, as well
|
||||
// as updating disposition of intermediate nodes.
|
||||
func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
|
||||
st := psTop
|
||||
// Walk the list backwards so that we can update the state for
|
||||
// earlier list elements based on what we find out about their
|
||||
// successors. Example:
|
||||
//
|
||||
// if ... {
|
||||
// L10: foo()
|
||||
// L11: <stmt>
|
||||
// L12: panic(...)
|
||||
// }
|
||||
//
|
||||
// After combining the dispositions for line 11 and 12, we want to
|
||||
// update the state for the call at line 10 based on that combined
|
||||
// disposition (if L11 has no path to "return", then the call at
|
||||
// line 10 will be on a panic path).
|
||||
for i := len(list) - 1; i >= 0; i-- {
|
||||
n := list[i]
|
||||
psi := ffa.getState(n)
|
||||
if debugTrace&debugTraceFuncFlags != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n",
|
||||
ir.Line(n), n.Op().String(), psi.String())
|
||||
}
|
||||
st = blockCombine(psi, st)
|
||||
ffa.updateState(n, st)
|
||||
}
|
||||
if st == psTop {
|
||||
st = psNoInfo
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func isMainMain(fn *ir.Func) bool {
|
||||
s := fn.Sym()
|
||||
return (s.Pkg.Name == "main" && s.Name == "main")
|
||||
}
|
||||
|
||||
func isWellKnownFunc(s *types.Sym, pkg, name string) bool {
|
||||
return s.Pkg.Path == pkg && s.Name == name
|
||||
}
|
||||
|
||||
// isExitCall reports TRUE if the node itself is an unconditional
|
||||
// call to os.Exit(), a panic, or a function that does likewise.
|
||||
func isExitCall(n ir.Node) bool {
|
||||
if n.Op() != ir.OCALLFUNC {
|
||||
return false
|
||||
}
|
||||
cx := n.(*ir.CallExpr)
|
||||
name := ir.StaticCalleeName(cx.Fun)
|
||||
if name == nil {
|
||||
return false
|
||||
}
|
||||
s := name.Sym()
|
||||
if isWellKnownFunc(s, "os", "Exit") ||
|
||||
isWellKnownFunc(s, "runtime", "throw") {
|
||||
return true
|
||||
}
|
||||
if funcProps := propsForFunc(name.Func); funcProps != nil {
|
||||
if funcProps.Flags&FuncPropNeverReturns != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return name.Func.NeverReturns()
|
||||
}
|
||||
|
||||
// pessimize is called to record the fact that we saw something in the
|
||||
// function that renders it entirely impossible to analyze.
|
||||
func (ffa *funcFlagsAnalyzer) pessimize() {
|
||||
ffa.noInfo = true
|
||||
}
|
||||
|
||||
// shouldVisit reports TRUE if this is an interesting node from the
|
||||
// perspective of computing function flags. NB: due to the fact that
|
||||
// ir.CallExpr implements the Stmt interface, we wind up visiting
|
||||
// a lot of nodes that we don't really need to, but these can
|
||||
// simply be screened out as part of the visit.
|
||||
func shouldVisit(n ir.Node) bool {
|
||||
_, isStmt := n.(ir.Stmt)
|
||||
return n.Op() != ir.ODCL &&
|
||||
(isStmt || n.Op() == ir.OCALLFUNC || n.Op() == ir.OPANIC)
|
||||
}
|
||||
|
||||
// nodeVisitPost helps implement the propAnalyzer interface; when
|
||||
// called on a given node, it decides the disposition of that node
|
||||
// based on the state(s) of the node's children.
|
||||
func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) {
|
||||
if debugTrace&debugTraceFuncFlags != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=+= nodevis %v %s should=%v\n",
|
||||
ir.Line(n), n.Op().String(), shouldVisit(n))
|
||||
}
|
||||
if !shouldVisit(n) {
|
||||
return
|
||||
}
|
||||
var st pstate
|
||||
switch n.Op() {
|
||||
case ir.OCALLFUNC:
|
||||
if isExitCall(n) {
|
||||
st = psCallsPanic
|
||||
}
|
||||
case ir.OPANIC:
|
||||
st = psCallsPanic
|
||||
case ir.ORETURN:
|
||||
st = psMayReturn
|
||||
case ir.OBREAK, ir.OCONTINUE:
|
||||
// FIXME: this handling of break/continue is sub-optimal; we
|
||||
// have them as "mayReturn" in order to help with this case:
|
||||
//
|
||||
// for {
|
||||
// if q() { break }
|
||||
// panic(...)
|
||||
// }
|
||||
//
|
||||
// where the effect of the 'break' is to cause the subsequent
|
||||
// panic to be skipped. One possible improvement would be to
|
||||
// track whether the currently enclosing loop is a "for {" or
|
||||
// a for/range with condition, then use mayReturn only for the
|
||||
// former. Note also that "break X" or "continue X" is treated
|
||||
// the same as "goto", since we don't have a good way to track
|
||||
// the target of the branch.
|
||||
st = psMayReturn
|
||||
n := n.(*ir.BranchStmt)
|
||||
if n.Label != nil {
|
||||
ffa.pessimize()
|
||||
}
|
||||
case ir.OBLOCK:
|
||||
n := n.(*ir.BlockStmt)
|
||||
st = ffa.stateForList(n.List)
|
||||
case ir.OCASE:
|
||||
if ccst, ok := n.(*ir.CaseClause); ok {
|
||||
st = ffa.stateForList(ccst.Body)
|
||||
} else if ccst, ok := n.(*ir.CommClause); ok {
|
||||
st = ffa.stateForList(ccst.Body)
|
||||
} else {
|
||||
panic("unexpected")
|
||||
}
|
||||
case ir.OIF:
|
||||
n := n.(*ir.IfStmt)
|
||||
st = branchCombine(ffa.stateForList(n.Body), ffa.stateForList(n.Else))
|
||||
case ir.OFOR:
|
||||
// Treat for { XXX } like a block.
|
||||
// Treat for <cond> { XXX } like an if statement with no else.
|
||||
n := n.(*ir.ForStmt)
|
||||
bst := ffa.stateForList(n.Body)
|
||||
if n.Cond == nil {
|
||||
st = bst
|
||||
} else {
|
||||
if bst == psMayReturn {
|
||||
st = psMayReturn
|
||||
}
|
||||
}
|
||||
case ir.ORANGE:
|
||||
// Treat for range { XXX } like an if statement with no else.
|
||||
n := n.(*ir.RangeStmt)
|
||||
if ffa.stateForList(n.Body) == psMayReturn {
|
||||
st = psMayReturn
|
||||
}
|
||||
case ir.OGOTO:
|
||||
// punt if we see even one goto. if we built a control
|
||||
// flow graph we could do more, but this is just a tree walk.
|
||||
ffa.pessimize()
|
||||
case ir.OSELECT:
|
||||
// process selects for "may return" but not "always panics",
|
||||
// the latter case seems very improbable.
|
||||
n := n.(*ir.SelectStmt)
|
||||
if len(n.Cases) != 0 {
|
||||
st = psTop
|
||||
for _, c := range n.Cases {
|
||||
st = branchCombine(ffa.stateForList(c.Body), st)
|
||||
}
|
||||
}
|
||||
case ir.OSWITCH:
|
||||
n := n.(*ir.SwitchStmt)
|
||||
if len(n.Cases) != 0 {
|
||||
st = psTop
|
||||
for _, c := range n.Cases {
|
||||
st = branchCombine(ffa.stateForList(c.Body), st)
|
||||
}
|
||||
}
|
||||
|
||||
st, fall := psTop, psNoInfo
|
||||
for i := len(n.Cases) - 1; i >= 0; i-- {
|
||||
cas := n.Cases[i]
|
||||
cst := ffa.stateForList(cas.Body)
|
||||
endsInFallthrough := false
|
||||
if len(cas.Body) != 0 {
|
||||
endsInFallthrough = cas.Body[0].Op() == ir.OFALL
|
||||
}
|
||||
if endsInFallthrough {
|
||||
cst = blockCombine(cst, fall)
|
||||
}
|
||||
st = branchCombine(st, cst)
|
||||
fall = cst
|
||||
}
|
||||
case ir.OFALL:
|
||||
// Not important.
|
||||
case ir.ODCLFUNC, ir.ORECOVER, ir.OAS, ir.OAS2, ir.OAS2FUNC, ir.OASOP,
|
||||
ir.OPRINTLN, ir.OPRINT, ir.OLABEL, ir.OCALLINTER, ir.ODEFER,
|
||||
ir.OSEND, ir.ORECV, ir.OSELRECV2, ir.OGO, ir.OAPPEND, ir.OAS2DOTTYPE,
|
||||
ir.OAS2MAPR, ir.OGETG, ir.ODELETE, ir.OINLMARK, ir.OAS2RECV,
|
||||
ir.OMIN, ir.OMAX, ir.OMAKE, ir.ORECOVERFP, ir.OGETCALLERSP:
|
||||
// these should all be benign/uninteresting
|
||||
case ir.OTAILCALL, ir.OJUMPTABLE, ir.OTYPESW:
|
||||
// don't expect to see these at all.
|
||||
base.Fatalf("unexpected op %s in func %s",
|
||||
n.Op().String(), ir.FuncName(ffa.fn))
|
||||
default:
|
||||
base.Fatalf("%v: unhandled op %s in func %v",
|
||||
ir.Line(n), n.Op().String(), ir.FuncName(ffa.fn))
|
||||
}
|
||||
if debugTrace&debugTraceFuncFlags != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n",
|
||||
ir.Line(n), n.Op().String(), st.String())
|
||||
}
|
||||
ffa.setState(n, st)
|
||||
}
|
||||
|
||||
func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) {
|
||||
}
|
||||
355
src/cmd/compile/internal/inline/inlheur/analyze_func_params.go
Normal file
355
src/cmd/compile/internal/inline/inlheur/analyze_func_params.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// paramsAnalyzer holds state information for the phase that computes
|
||||
// flags for a Go functions parameters, for use in inline heuristics.
|
||||
// Note that the params slice below includes entries for blanks.
|
||||
type paramsAnalyzer struct {
|
||||
fname string
|
||||
values []ParamPropBits
|
||||
params []*ir.Name
|
||||
top []bool
|
||||
*condLevelTracker
|
||||
*nameFinder
|
||||
}
|
||||
|
||||
// getParams returns an *ir.Name slice containing all params for the
|
||||
// function (plus rcvr as well if applicable).
|
||||
func getParams(fn *ir.Func) []*ir.Name {
|
||||
sig := fn.Type()
|
||||
numParams := sig.NumRecvs() + sig.NumParams()
|
||||
return fn.Dcl[:numParams]
|
||||
}
|
||||
|
||||
// addParamsAnalyzer creates a new paramsAnalyzer helper object for
|
||||
// the function fn, appends it to the analyzers list, and returns 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, nf *nameFinder) []propAnalyzer {
|
||||
pa, props := makeParamsAnalyzer(fn, nf)
|
||||
if pa != nil {
|
||||
analyzers = append(analyzers, pa)
|
||||
} else {
|
||||
fp.ParamFlags = props
|
||||
}
|
||||
return analyzers
|
||||
}
|
||||
|
||||
// makeParamsAnalyzer creates a new helper object to analyze parameters
|
||||
// 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, nf *nameFinder) (*paramsAnalyzer, []ParamPropBits) {
|
||||
params := getParams(fn) // includes receiver if applicable
|
||||
if len(params) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
vals := make([]ParamPropBits, len(params))
|
||||
if fn.Inl == nil {
|
||||
return nil, vals
|
||||
}
|
||||
top := make([]bool, len(params))
|
||||
interestingToAnalyze := false
|
||||
for i, pn := range params {
|
||||
if pn == nil {
|
||||
continue
|
||||
}
|
||||
pt := pn.Type()
|
||||
if !pt.IsScalar() && !pt.HasNil() {
|
||||
// existing properties not applicable here (for things
|
||||
// like structs, arrays, slices, etc).
|
||||
continue
|
||||
}
|
||||
// If param is reassigned, skip it.
|
||||
if ir.Reassigned(pn) {
|
||||
continue
|
||||
}
|
||||
top[i] = true
|
||||
interestingToAnalyze = true
|
||||
}
|
||||
if !interestingToAnalyze {
|
||||
return nil, vals
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceParams != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= param analysis of func %v:\n",
|
||||
fn.Sym().Name)
|
||||
for i := range vals {
|
||||
n := "_"
|
||||
if params[i] != nil {
|
||||
n = params[i].Sym().String()
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "=-= %d: %q %s top=%v\n",
|
||||
i, n, vals[i].String(), top[i])
|
||||
}
|
||||
}
|
||||
pa := ¶msAnalyzer{
|
||||
fname: fn.Sym().Name,
|
||||
values: vals,
|
||||
params: params,
|
||||
top: top,
|
||||
condLevelTracker: new(condLevelTracker),
|
||||
nameFinder: nf,
|
||||
}
|
||||
return pa, nil
|
||||
}
|
||||
|
||||
func (pa *paramsAnalyzer) setResults(funcProps *FuncProps) {
|
||||
funcProps.ParamFlags = pa.values
|
||||
}
|
||||
|
||||
func (pa *paramsAnalyzer) findParamIdx(n *ir.Name) int {
|
||||
if n == nil {
|
||||
panic("bad")
|
||||
}
|
||||
for i := range pa.params {
|
||||
if pa.params[i] == n {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
type testfType func(x ir.Node, param *ir.Name, idx int) (bool, bool)
|
||||
|
||||
// paramsAnalyzer invokes function 'testf' on the specified expression
|
||||
// 'x' for each parameter, and if the result is TRUE, or's 'flag' into
|
||||
// the flags for that param.
|
||||
func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag ParamPropBits, testf testfType) {
|
||||
for idx, p := range pa.params {
|
||||
if !pa.top[idx] && pa.values[idx] == ParamNoInfo {
|
||||
continue
|
||||
}
|
||||
result, may := testf(x, p, idx)
|
||||
if debugTrace&debugTraceParams != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= test expr %v param %s result=%v flag=%s\n", x, p.Sym().Name, result, flag.String())
|
||||
}
|
||||
if result {
|
||||
v := flag
|
||||
if pa.condLevel != 0 || may {
|
||||
v = mayflag
|
||||
}
|
||||
pa.values[idx] |= v
|
||||
pa.top[idx] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// foldCheckParams checks expression 'x' (an 'if' condition or
|
||||
// 'switch' stmt expr) to see if the expr would fold away if a
|
||||
// specific parameter had a constant value.
|
||||
func (pa *paramsAnalyzer) foldCheckParams(x ir.Node) {
|
||||
pa.checkParams(x, ParamFeedsIfOrSwitch, ParamMayFeedIfOrSwitch,
|
||||
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
|
||||
return ShouldFoldIfNameConstant(x, []*ir.Name{p}), false
|
||||
})
|
||||
}
|
||||
|
||||
// callCheckParams examines the target of call expression 'ce' to see
|
||||
// if it is making a call to the value passed in for some parameter.
|
||||
func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
|
||||
switch ce.Op() {
|
||||
case ir.OCALLINTER:
|
||||
if ce.Op() != ir.OCALLINTER {
|
||||
return
|
||||
}
|
||||
sel := ce.Fun.(*ir.SelectorExpr)
|
||||
r := pa.staticValue(sel.X)
|
||||
if r.Op() != ir.ONAME {
|
||||
return
|
||||
}
|
||||
name := r.(*ir.Name)
|
||||
if name.Class != ir.PPARAM {
|
||||
return
|
||||
}
|
||||
pa.checkParams(r, ParamFeedsInterfaceMethodCall,
|
||||
ParamMayFeedInterfaceMethodCall,
|
||||
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
|
||||
name := x.(*ir.Name)
|
||||
return name == p, false
|
||||
})
|
||||
case ir.OCALLFUNC:
|
||||
if ce.Fun.Op() != ir.ONAME {
|
||||
return
|
||||
}
|
||||
called := ir.StaticValue(ce.Fun)
|
||||
if called.Op() != ir.ONAME {
|
||||
return
|
||||
}
|
||||
name := called.(*ir.Name)
|
||||
if name.Class == ir.PPARAM {
|
||||
pa.checkParams(called, ParamFeedsIndirectCall,
|
||||
ParamMayFeedIndirectCall,
|
||||
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
|
||||
name := x.(*ir.Name)
|
||||
return name == p, false
|
||||
})
|
||||
} else {
|
||||
cname := pa.funcName(called)
|
||||
if cname != nil {
|
||||
pa.deriveFlagsFromCallee(ce, cname.Func)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deriveFlagsFromCallee tries to derive flags for the current
|
||||
// function based on a call this function makes to some other
|
||||
// function. Example:
|
||||
//
|
||||
// /* Simple */ /* Derived from callee */
|
||||
// func foo(f func(int)) { func foo(f func(int)) {
|
||||
// f(2) bar(32, f)
|
||||
// } }
|
||||
// func bar(x int, f func()) {
|
||||
// f(x)
|
||||
// }
|
||||
//
|
||||
// Here we can set the "param feeds indirect call" flag for
|
||||
// foo's param 'f' since we know that bar has that flag set for
|
||||
// its second param, and we're passing that param a function.
|
||||
func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func) {
|
||||
calleeProps := propsForFunc(callee)
|
||||
if calleeProps == nil {
|
||||
return
|
||||
}
|
||||
if debugTrace&debugTraceParams != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= callee props for %v:\n%s",
|
||||
callee.Sym().Name, calleeProps.String())
|
||||
}
|
||||
|
||||
must := []ParamPropBits{ParamFeedsInterfaceMethodCall, ParamFeedsIndirectCall, ParamFeedsIfOrSwitch}
|
||||
may := []ParamPropBits{ParamMayFeedInterfaceMethodCall, ParamMayFeedIndirectCall, ParamMayFeedIfOrSwitch}
|
||||
|
||||
for pidx, arg := range ce.Args {
|
||||
// Does the callee param have any interesting properties?
|
||||
// If not we can skip this one.
|
||||
pflag := calleeProps.ParamFlags[pidx]
|
||||
if pflag == 0 {
|
||||
continue
|
||||
}
|
||||
// See if one of the caller's parameters is flowing unmodified
|
||||
// into this actual expression.
|
||||
r := pa.staticValue(arg)
|
||||
if r.Op() != ir.ONAME {
|
||||
return
|
||||
}
|
||||
name := r.(*ir.Name)
|
||||
if name.Class != ir.PPARAM {
|
||||
return
|
||||
}
|
||||
callerParamIdx := pa.findParamIdx(name)
|
||||
// note that callerParamIdx may return -1 in the case where
|
||||
// the param belongs not to the current closure func we're
|
||||
// analyzing but to an outer enclosing func.
|
||||
if callerParamIdx == -1 {
|
||||
return
|
||||
}
|
||||
if pa.params[callerParamIdx] == nil {
|
||||
panic("something went wrong")
|
||||
}
|
||||
if !pa.top[callerParamIdx] &&
|
||||
pa.values[callerParamIdx] == ParamNoInfo {
|
||||
continue
|
||||
}
|
||||
if debugTrace&debugTraceParams != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= pflag for arg %d is %s\n",
|
||||
pidx, pflag.String())
|
||||
}
|
||||
for i := range must {
|
||||
mayv := may[i]
|
||||
mustv := must[i]
|
||||
if pflag&mustv != 0 && pa.condLevel == 0 {
|
||||
pa.values[callerParamIdx] |= mustv
|
||||
} else if pflag&(mustv|mayv) != 0 {
|
||||
pa.values[callerParamIdx] |= mayv
|
||||
}
|
||||
}
|
||||
pa.top[callerParamIdx] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *paramsAnalyzer) nodeVisitPost(n ir.Node) {
|
||||
if len(pa.values) == 0 {
|
||||
return
|
||||
}
|
||||
pa.condLevelTracker.post(n)
|
||||
switch n.Op() {
|
||||
case ir.OCALLFUNC:
|
||||
ce := n.(*ir.CallExpr)
|
||||
pa.callCheckParams(ce)
|
||||
case ir.OCALLINTER:
|
||||
ce := n.(*ir.CallExpr)
|
||||
pa.callCheckParams(ce)
|
||||
case ir.OIF:
|
||||
ifst := n.(*ir.IfStmt)
|
||||
pa.foldCheckParams(ifst.Cond)
|
||||
case ir.OSWITCH:
|
||||
swst := n.(*ir.SwitchStmt)
|
||||
if swst.Tag != nil {
|
||||
pa.foldCheckParams(swst.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *paramsAnalyzer) nodeVisitPre(n ir.Node) {
|
||||
if len(pa.values) == 0 {
|
||||
return
|
||||
}
|
||||
pa.condLevelTracker.pre(n)
|
||||
}
|
||||
|
||||
// condLevelTracker helps keeps track very roughly of "level of conditional
|
||||
// nesting", e.g. how many "if" statements you have to go through to
|
||||
// get to the point where a given stmt executes. Example:
|
||||
//
|
||||
// cond nesting level
|
||||
// func foo() {
|
||||
// G = 1 0
|
||||
// if x < 10 { 0
|
||||
// if y < 10 { 1
|
||||
// G = 0 2
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The intent here is to provide some sort of very abstract relative
|
||||
// hotness metric, e.g. "G = 1" above is expected to be executed more
|
||||
// often than "G = 0" (in the aggregate, across large numbers of
|
||||
// functions).
|
||||
type condLevelTracker struct {
|
||||
condLevel int
|
||||
}
|
||||
|
||||
func (c *condLevelTracker) pre(n ir.Node) {
|
||||
// Increment level of "conditional testing" if we see
|
||||
// an "if" or switch statement, and decrement if in
|
||||
// a loop.
|
||||
switch n.Op() {
|
||||
case ir.OIF, ir.OSWITCH:
|
||||
c.condLevel++
|
||||
case ir.OFOR, ir.ORANGE:
|
||||
c.condLevel--
|
||||
}
|
||||
}
|
||||
|
||||
func (c *condLevelTracker) post(n ir.Node) {
|
||||
switch n.Op() {
|
||||
case ir.OFOR, ir.ORANGE:
|
||||
c.condLevel++
|
||||
case ir.OIF:
|
||||
c.condLevel--
|
||||
case ir.OSWITCH:
|
||||
c.condLevel--
|
||||
}
|
||||
}
|
||||
277
src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go
Normal file
277
src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"os"
|
||||
)
|
||||
|
||||
// resultsAnalyzer stores state information for the process of
|
||||
// computing flags/properties for the return values of a specific Go
|
||||
// function, as part of inline heuristics synthesis.
|
||||
type resultsAnalyzer struct {
|
||||
fname string
|
||||
props []ResultPropBits
|
||||
values []resultVal
|
||||
inlineMaxBudget int
|
||||
*nameFinder
|
||||
}
|
||||
|
||||
// resultVal captures information about a specific result returned from
|
||||
// the function we're analyzing; we are interested in cases where
|
||||
// the func always returns the same constant, or always returns
|
||||
// the same function, etc. This container stores info on a the specific
|
||||
// scenarios we're looking for.
|
||||
type resultVal struct {
|
||||
cval constant.Value
|
||||
fn *ir.Name
|
||||
fnClo bool
|
||||
top bool
|
||||
derived bool // see deriveReturnFlagsFromCallee below
|
||||
}
|
||||
|
||||
// addResultsAnalyzer creates a new resultsAnalyzer helper object for
|
||||
// the function fn, appends it to the analyzers list, and returns the
|
||||
// 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, nf *nameFinder) []propAnalyzer {
|
||||
ra, props := makeResultsAnalyzer(fn, inlineMaxBudget, nf)
|
||||
if ra != nil {
|
||||
analyzers = append(analyzers, ra)
|
||||
} else {
|
||||
fp.ResultFlags = props
|
||||
}
|
||||
return analyzers
|
||||
}
|
||||
|
||||
// makeResultsAnalyzer creates a new helper object to analyze results
|
||||
// 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, nf *nameFinder) (*resultsAnalyzer, []ResultPropBits) {
|
||||
results := fn.Type().Results()
|
||||
if len(results) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
props := make([]ResultPropBits, len(results))
|
||||
if fn.Inl == nil {
|
||||
return nil, props
|
||||
}
|
||||
vals := make([]resultVal, len(results))
|
||||
interestingToAnalyze := false
|
||||
for i := range results {
|
||||
rt := results[i].Type
|
||||
if !rt.IsScalar() && !rt.HasNil() {
|
||||
// existing properties not applicable here (for things
|
||||
// like structs, arrays, slices, etc).
|
||||
continue
|
||||
}
|
||||
// set the "top" flag (as in "top element of data flow lattice")
|
||||
// meaning "we have no info yet, but we might later on".
|
||||
vals[i].top = true
|
||||
interestingToAnalyze = true
|
||||
}
|
||||
if !interestingToAnalyze {
|
||||
return nil, props
|
||||
}
|
||||
ra := &resultsAnalyzer{
|
||||
props: props,
|
||||
values: vals,
|
||||
inlineMaxBudget: inlineMaxBudget,
|
||||
nameFinder: nf,
|
||||
}
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
// setResults transfers the calculated result properties for this
|
||||
// function to 'funcProps'.
|
||||
func (ra *resultsAnalyzer) setResults(funcProps *FuncProps) {
|
||||
// Promote ResultAlwaysSameFunc to ResultAlwaysSameInlinableFunc
|
||||
for i := range ra.values {
|
||||
if ra.props[i] == ResultAlwaysSameFunc && !ra.values[i].derived {
|
||||
f := ra.values[i].fn.Func
|
||||
// HACK: in order to allow for call site score
|
||||
// adjustments, we used a relaxed inline budget in
|
||||
// determining inlinability. For the check below, however,
|
||||
// we want to know is whether the func in question is
|
||||
// likely to be inlined, as opposed to whether it might
|
||||
// possibly be inlined if all the right score adjustments
|
||||
// happened, so do a simple check based on the cost.
|
||||
if f.Inl != nil && f.Inl.Cost <= int32(ra.inlineMaxBudget) {
|
||||
ra.props[i] = ResultAlwaysSameInlinableFunc
|
||||
}
|
||||
}
|
||||
}
|
||||
funcProps.ResultFlags = ra.props
|
||||
}
|
||||
|
||||
func (ra *resultsAnalyzer) pessimize() {
|
||||
for i := range ra.props {
|
||||
ra.props[i] = ResultNoInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (ra *resultsAnalyzer) nodeVisitPre(n ir.Node) {
|
||||
}
|
||||
|
||||
func (ra *resultsAnalyzer) nodeVisitPost(n ir.Node) {
|
||||
if len(ra.values) == 0 {
|
||||
return
|
||||
}
|
||||
if n.Op() != ir.ORETURN {
|
||||
return
|
||||
}
|
||||
if debugTrace&debugTraceResults != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=+= returns nodevis %v %s\n",
|
||||
ir.Line(n), n.Op().String())
|
||||
}
|
||||
|
||||
// No support currently for named results, so if we see an empty
|
||||
// "return" stmt, be conservative.
|
||||
rs := n.(*ir.ReturnStmt)
|
||||
if len(rs.Results) != len(ra.values) {
|
||||
ra.pessimize()
|
||||
return
|
||||
}
|
||||
for i, r := range rs.Results {
|
||||
ra.analyzeResult(i, r)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// applies a dataflow "meet" operation to combine this result with any
|
||||
// previous result (for the given return slot) that we've already
|
||||
// processed.
|
||||
func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
|
||||
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 := ra.deriveReturnFlagsFromCallee(n)
|
||||
newp := ResultNoInfo
|
||||
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 isnil=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isNil, isFunc, isClo)
|
||||
}
|
||||
|
||||
if ra.values[ii].top {
|
||||
ra.values[ii].top = false
|
||||
// this is the first return we've seen; record
|
||||
// whatever properties it has.
|
||||
switch {
|
||||
case isAllocMem:
|
||||
newp = ResultIsAllocatedMem
|
||||
case isConcConvItf:
|
||||
newp = ResultIsConcreteTypeConvertedToInterface
|
||||
case isFunc:
|
||||
newp = ResultAlwaysSameFunc
|
||||
newfunc = rfunc
|
||||
case isConst:
|
||||
newp = ResultAlwaysSameConstant
|
||||
newcval = constVal
|
||||
case isNil:
|
||||
newp = ResultAlwaysSameConstant
|
||||
newcval = nil
|
||||
case isDerivedFromCall:
|
||||
newp = dprops
|
||||
ra.values[ii].derived = true
|
||||
}
|
||||
} else {
|
||||
if !ra.values[ii].derived {
|
||||
// this is not the first return we've seen; apply
|
||||
// what amounts of a "meet" operator to combine
|
||||
// the properties we see here with what we saw on
|
||||
// the previous returns.
|
||||
switch curp {
|
||||
case ResultIsAllocatedMem:
|
||||
if isAllocMem {
|
||||
newp = ResultIsAllocatedMem
|
||||
}
|
||||
case ResultIsConcreteTypeConvertedToInterface:
|
||||
if isConcConvItf {
|
||||
newp = ResultIsConcreteTypeConvertedToInterface
|
||||
}
|
||||
case ResultAlwaysSameConstant:
|
||||
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
|
||||
newcval = constVal
|
||||
}
|
||||
case ResultAlwaysSameFunc:
|
||||
if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
|
||||
newp = ResultAlwaysSameFunc
|
||||
newfunc = rfunc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ra.values[ii].fn = newfunc
|
||||
ra.values[ii].fnClo = isClo
|
||||
ra.values[ii].cval = newcval
|
||||
ra.props[ii] = newp
|
||||
|
||||
if debugTrace&debugTraceResults != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult newp=%s\n",
|
||||
ir.Line(n), newp)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// prop is valid. Examples:
|
||||
//
|
||||
// func foo() int { return bar() }
|
||||
// func bar() int { return 42 }
|
||||
// func blix() int { return 43 }
|
||||
// func two(y int) int {
|
||||
// if y < 0 { return bar() } else { return blix() }
|
||||
// }
|
||||
//
|
||||
// Since "foo" always returns the result of a call to "bar", we can
|
||||
// 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 (ra *resultsAnalyzer) deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
|
||||
if n.Op() != ir.OCALLFUNC {
|
||||
return 0, false
|
||||
}
|
||||
ce := n.(*ir.CallExpr)
|
||||
if ce.Fun.Op() != ir.ONAME {
|
||||
return 0, false
|
||||
}
|
||||
called := ir.StaticValue(ce.Fun)
|
||||
if called.Op() != ir.ONAME {
|
||||
return 0, false
|
||||
}
|
||||
cname := ra.funcName(called)
|
||||
if cname == nil {
|
||||
return 0, false
|
||||
}
|
||||
calleeProps := propsForFunc(cname.Func)
|
||||
if calleeProps == nil {
|
||||
return 0, false
|
||||
}
|
||||
if len(calleeProps.ResultFlags) != 1 {
|
||||
return 0, false
|
||||
}
|
||||
return calleeProps.ResultFlags[0], true
|
||||
}
|
||||
149
src/cmd/compile/internal/inline/inlheur/callsite.go
Normal file
149
src/cmd/compile/internal/inline/inlheur/callsite.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// 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/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/internal/src"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CallSite records useful information about a potentially inlinable
|
||||
// (direct) function call. "Callee" is the target of the call, "Call"
|
||||
// is the ir node corresponding to the call itself, "Assign" is
|
||||
// the top-level assignment statement containing the call (if the call
|
||||
// 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
|
||||
// containing function.
|
||||
type CallSite struct {
|
||||
Callee *ir.Func
|
||||
Call *ir.CallExpr
|
||||
parent *CallSite
|
||||
Assign ir.Node
|
||||
Flags CSPropBits
|
||||
|
||||
ArgProps []ActualExprPropBits
|
||||
Score int
|
||||
ScoreMask scoreAdjustTyp
|
||||
ID uint
|
||||
aux uint8
|
||||
}
|
||||
|
||||
// CallSiteTab is a table of call sites, keyed by call expr.
|
||||
// Ideally it would be nice to key the table by src.XPos, but
|
||||
// this results in collisions for calls on very long lines (the
|
||||
// front end saturates column numbers at 255). We also wind up
|
||||
// with many calls that share the same auto-generated pos.
|
||||
type CallSiteTab map[*ir.CallExpr]*CallSite
|
||||
|
||||
// ActualExprPropBits describes a property of an actual expression (value
|
||||
// passed to some specific func argument at a call site).
|
||||
type ActualExprPropBits uint8
|
||||
|
||||
const (
|
||||
ActualExprConstant ActualExprPropBits = 1 << iota
|
||||
ActualExprIsConcreteConvIface
|
||||
ActualExprIsFunc
|
||||
ActualExprIsInlinableFunc
|
||||
)
|
||||
|
||||
type CSPropBits uint32
|
||||
|
||||
const (
|
||||
CallSiteInLoop CSPropBits = 1 << iota
|
||||
CallSiteOnPanicPath
|
||||
CallSiteInInitFunc
|
||||
)
|
||||
|
||||
type csAuxBits uint8
|
||||
|
||||
const (
|
||||
csAuxInlined = 1 << iota
|
||||
)
|
||||
|
||||
// encodedCallSiteTab is a table keyed by "encoded" callsite
|
||||
// (stringified src.XPos plus call site ID) mapping to a value of call
|
||||
// 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 {
|
||||
if prev, ok := cst[k]; ok {
|
||||
return fmt.Errorf("internal error: collision during call site table merge, fn=%s callsite=%s", prev.Callee.Sym().Name, fmtFullPos(prev.Call.Pos()))
|
||||
}
|
||||
cst[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fmtFullPos(p src.XPos) string {
|
||||
var sb strings.Builder
|
||||
sep := ""
|
||||
base.Ctxt.AllPos(p, func(pos src.Pos) {
|
||||
fmt.Fprintf(&sb, sep)
|
||||
sep = "|"
|
||||
file := filepath.Base(pos.Filename())
|
||||
fmt.Fprintf(&sb, "%s:%d:%d", file, pos.Line(), pos.Col())
|
||||
})
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func EncodeCallSiteKey(cs *CallSite) string {
|
||||
var sb strings.Builder
|
||||
// FIXME: maybe rewrite line offsets relative to function start?
|
||||
sb.WriteString(fmtFullPos(cs.Call.Pos()))
|
||||
fmt.Fprintf(&sb, "|%d", cs.ID)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func buildEncodedCallSiteTab(tab CallSiteTab) encodedCallSiteTab {
|
||||
r := make(encodedCallSiteTab)
|
||||
for _, cs := range tab {
|
||||
k := EncodeCallSiteKey(cs)
|
||||
r[k] = propsAndScore{
|
||||
props: cs.Flags,
|
||||
score: cs.Score,
|
||||
mask: cs.ScoreMask,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// dumpCallSiteComments emits comments into the dump file for the
|
||||
// callsites in the function of interest. If "ecst" is non-nil, we use
|
||||
// that, otherwise generated a fresh encodedCallSiteTab from "tab".
|
||||
func dumpCallSiteComments(w io.Writer, tab CallSiteTab, ecst encodedCallSiteTab) {
|
||||
if ecst == nil {
|
||||
ecst = buildEncodedCallSiteTab(tab)
|
||||
}
|
||||
tags := make([]string, 0, len(ecst))
|
||||
for k := range ecst {
|
||||
tags = append(tags, k)
|
||||
}
|
||||
sort.Strings(tags)
|
||||
for _, s := range tags {
|
||||
v := ecst[s]
|
||||
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)
|
||||
}
|
||||
56
src/cmd/compile/internal/inline/inlheur/cspropbits_string.go
Normal file
56
src/cmd/compile/internal/inline/inlheur/cspropbits_string.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Code generated by "stringer -bitset -type CSPropBits"; 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[CallSiteInLoop-1]
|
||||
_ = x[CallSiteOnPanicPath-2]
|
||||
_ = x[CallSiteInInitFunc-4]
|
||||
}
|
||||
|
||||
var _CSPropBits_value = [...]uint64{
|
||||
0x1, /* CallSiteInLoop */
|
||||
0x2, /* CallSiteOnPanicPath */
|
||||
0x4, /* CallSiteInInitFunc */
|
||||
}
|
||||
|
||||
const _CSPropBits_name = "CallSiteInLoopCallSiteOnPanicPathCallSiteInInitFunc"
|
||||
|
||||
var _CSPropBits_index = [...]uint8{0, 14, 33, 51}
|
||||
|
||||
func (i CSPropBits) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
remain := uint64(i)
|
||||
seen := false
|
||||
|
||||
for k, v := range _CSPropBits_value {
|
||||
x := _CSPropBits_name[_CSPropBits_index[k]:_CSPropBits_index[k+1]]
|
||||
if v == 0 {
|
||||
if i == 0 {
|
||||
b.WriteString(x)
|
||||
return b.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (v & remain) == v {
|
||||
remain &^= v
|
||||
x := _CSPropBits_name[_CSPropBits_index[k]:_CSPropBits_index[k+1]]
|
||||
if seen {
|
||||
b.WriteString("|")
|
||||
}
|
||||
seen = true
|
||||
b.WriteString(x)
|
||||
}
|
||||
}
|
||||
if remain == 0 {
|
||||
return b.String()
|
||||
}
|
||||
return "CSPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")"
|
||||
}
|
||||
65
src/cmd/compile/internal/inline/inlheur/debugflags_test.go
Normal file
65
src/cmd/compile/internal/inline/inlheur/debugflags_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInlScoreAdjFlagParse(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
value string
|
||||
expok bool
|
||||
}{
|
||||
{
|
||||
value: "returnFeedsConcreteToInterfaceCallAdj:9",
|
||||
expok: true,
|
||||
},
|
||||
{
|
||||
value: "panicPathAdj:-1/initFuncAdj:9",
|
||||
expok: true,
|
||||
},
|
||||
{
|
||||
value: "",
|
||||
expok: false,
|
||||
},
|
||||
{
|
||||
value: "nonsenseAdj:10",
|
||||
expok: false,
|
||||
},
|
||||
{
|
||||
value: "inLoopAdj:",
|
||||
expok: false,
|
||||
},
|
||||
{
|
||||
value: "inLoopAdj:10:10",
|
||||
expok: false,
|
||||
},
|
||||
{
|
||||
value: "inLoopAdj:blah",
|
||||
expok: false,
|
||||
},
|
||||
{
|
||||
value: "/",
|
||||
expok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
err := parseScoreAdj(scenario.value)
|
||||
t.Logf("for value=%q err is %v\n", scenario.value, err)
|
||||
if scenario.expok {
|
||||
if err != nil {
|
||||
t.Errorf("expected parseScoreAdj(%s) ok, got err %v",
|
||||
scenario.value, err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected parseScoreAdj(%s) failure, got success",
|
||||
scenario.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
src/cmd/compile/internal/inline/inlheur/dumpscores_test.go
Normal file
109
src/cmd/compile/internal/inline/inlheur/dumpscores_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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 (
|
||||
"internal/testenv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDumpCallSiteScoreDump(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testenv.MustHaveGoBuild(t)
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
promoted int
|
||||
indirectlyPromoted int
|
||||
demoted int
|
||||
unchanged int
|
||||
}{
|
||||
{
|
||||
name: "dumpscores",
|
||||
promoted: 1,
|
||||
indirectlyPromoted: 1,
|
||||
demoted: 1,
|
||||
unchanged: 5,
|
||||
},
|
||||
}
|
||||
|
||||
for _, scen := range scenarios {
|
||||
dumpfile, err := gatherInlCallSitesScoresForFile(t, scen.name, td)
|
||||
if err != nil {
|
||||
t.Fatalf("dumping callsite scores for %q: error %v", scen.name, err)
|
||||
}
|
||||
var lines []string
|
||||
if content, err := os.ReadFile(dumpfile); err != nil {
|
||||
t.Fatalf("reading dump %q: error %v", dumpfile, err)
|
||||
} else {
|
||||
lines = strings.Split(string(content), "\n")
|
||||
}
|
||||
prom, indprom, dem, unch := 0, 0, 0, 0
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.TrimSpace(line) == "":
|
||||
case !strings.Contains(line, "|"):
|
||||
case strings.HasPrefix(line, "#"):
|
||||
case strings.Contains(line, "PROMOTED"):
|
||||
prom++
|
||||
case strings.Contains(line, "INDPROM"):
|
||||
indprom++
|
||||
case strings.Contains(line, "DEMOTED"):
|
||||
dem++
|
||||
default:
|
||||
unch++
|
||||
}
|
||||
}
|
||||
showout := false
|
||||
if prom != scen.promoted {
|
||||
t.Errorf("testcase %q, got %d promoted want %d promoted",
|
||||
scen.name, prom, scen.promoted)
|
||||
showout = true
|
||||
}
|
||||
if indprom != scen.indirectlyPromoted {
|
||||
t.Errorf("testcase %q, got %d indirectly promoted want %d",
|
||||
scen.name, indprom, scen.indirectlyPromoted)
|
||||
showout = true
|
||||
}
|
||||
if dem != scen.demoted {
|
||||
t.Errorf("testcase %q, got %d demoted want %d demoted",
|
||||
scen.name, dem, scen.demoted)
|
||||
showout = true
|
||||
}
|
||||
if unch != scen.unchanged {
|
||||
t.Errorf("testcase %q, got %d unchanged want %d unchanged",
|
||||
scen.name, unch, scen.unchanged)
|
||||
showout = true
|
||||
}
|
||||
if showout {
|
||||
t.Logf(">> dump output: %s", strings.Join(lines, "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gatherInlCallSitesScoresForFile builds the specified testcase 'testcase'
|
||||
// from testdata/props passing the "-d=dumpinlcallsitescores=1"
|
||||
// compiler option, to produce a dump, then returns the path of the
|
||||
// newly created file.
|
||||
func gatherInlCallSitesScoresForFile(t *testing.T, testcase string, td string) (string, error) {
|
||||
t.Helper()
|
||||
gopath := "testdata/" + testcase + ".go"
|
||||
outpath := filepath.Join(td, testcase+".a")
|
||||
dumpfile := filepath.Join(td, testcase+".callsites.txt")
|
||||
run := []string{testenv.GoToolPath(t), "build",
|
||||
"-gcflags=-d=dumpinlcallsitescores=1", "-o", outpath, gopath}
|
||||
out, err := testenv.Command(t, run[0], run[1:]...).CombinedOutput()
|
||||
t.Logf("run: %+v\n", run)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.WriteFile(dumpfile, out, 0666); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dumpfile, err
|
||||
}
|
||||
247
src/cmd/compile/internal/inline/inlheur/eclassify.go
Normal file
247
src/cmd/compile/internal/inline/inlheur/eclassify.go
Normal file
@@ -0,0 +1,247 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ShouldFoldIfNameConstant analyzes expression tree 'e' to see
|
||||
// whether it contains only combinations of simple references to all
|
||||
// of the names in 'names' with selected constants + operators. The
|
||||
// intent is to identify expression that could be folded away to a
|
||||
// constant if the value of 'n' were available. Return value is TRUE
|
||||
// if 'e' does look foldable given the value of 'n', and given that
|
||||
// 'e' actually makes reference to 'n'. Some examples where the type
|
||||
// of "n" is int64, type of "s" is string, and type of "p" is *byte:
|
||||
//
|
||||
// Simple? Expr
|
||||
// yes n<10
|
||||
// yes n*n-100
|
||||
// yes (n < 10 || n > 100) && (n >= 12 || n <= 99 || n != 101)
|
||||
// yes s == "foo"
|
||||
// yes p == nil
|
||||
// no n<foo()
|
||||
// no n<1 || n>m
|
||||
// no float32(n)<1.0
|
||||
// no *p == 1
|
||||
// no 1 + 100
|
||||
// no 1 / n
|
||||
// no 1 + unsafe.Sizeof(n)
|
||||
//
|
||||
// To avoid complexities (e.g. nan, inf) we stay way from folding and
|
||||
// floating point or complex operations (integers, bools, and strings
|
||||
// only). We also try to be conservative about avoiding any operation
|
||||
// that might result in a panic at runtime, e.g. for "n" with type
|
||||
// int64:
|
||||
//
|
||||
// 1<<(n-9) < 100/(n<<9999)
|
||||
//
|
||||
// we would return FALSE due to the negative shift count and/or
|
||||
// potential divide by zero.
|
||||
func ShouldFoldIfNameConstant(n ir.Node, names []*ir.Name) bool {
|
||||
cl := makeExprClassifier(names)
|
||||
var doNode func(ir.Node) bool
|
||||
doNode = func(n ir.Node) bool {
|
||||
ir.DoChildren(n, doNode)
|
||||
cl.Visit(n)
|
||||
return false
|
||||
}
|
||||
doNode(n)
|
||||
if cl.getdisp(n) != exprSimple {
|
||||
return false
|
||||
}
|
||||
for _, v := range cl.names {
|
||||
if !v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// exprClassifier holds intermediate state about nodes within an
|
||||
// expression tree being analyzed by ShouldFoldIfNameConstant. Here
|
||||
// "name" is the name node passed in, and "disposition" stores the
|
||||
// result of classifying a given IR node.
|
||||
type exprClassifier struct {
|
||||
names map[*ir.Name]bool
|
||||
disposition map[ir.Node]disp
|
||||
}
|
||||
|
||||
type disp int
|
||||
|
||||
const (
|
||||
// no info on this expr
|
||||
exprNoInfo disp = iota
|
||||
|
||||
// expr contains only literals
|
||||
exprLiterals
|
||||
|
||||
// expr is legal combination of literals and specified names
|
||||
exprSimple
|
||||
)
|
||||
|
||||
func (d disp) String() string {
|
||||
switch d {
|
||||
case exprNoInfo:
|
||||
return "noinfo"
|
||||
case exprSimple:
|
||||
return "simple"
|
||||
case exprLiterals:
|
||||
return "literals"
|
||||
default:
|
||||
return fmt.Sprintf("unknown<%d>", d)
|
||||
}
|
||||
}
|
||||
|
||||
func makeExprClassifier(names []*ir.Name) *exprClassifier {
|
||||
m := make(map[*ir.Name]bool, len(names))
|
||||
for _, n := range names {
|
||||
m[n] = false
|
||||
}
|
||||
return &exprClassifier{
|
||||
names: m,
|
||||
disposition: make(map[ir.Node]disp),
|
||||
}
|
||||
}
|
||||
|
||||
// Visit sets the classification for 'n' based on the previously
|
||||
// calculated classifications for n's children, as part of a bottom-up
|
||||
// walk over an expression tree.
|
||||
func (ec *exprClassifier) Visit(n ir.Node) {
|
||||
|
||||
ndisp := exprNoInfo
|
||||
|
||||
binparts := func(n ir.Node) (ir.Node, ir.Node) {
|
||||
if lex, ok := n.(*ir.LogicalExpr); ok {
|
||||
return lex.X, lex.Y
|
||||
} else if bex, ok := n.(*ir.BinaryExpr); ok {
|
||||
return bex.X, bex.Y
|
||||
} else {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
t := n.Type()
|
||||
if t == nil {
|
||||
if debugTrace&debugTraceExprClassify != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= *** untyped op=%s\n",
|
||||
n.Op().String())
|
||||
}
|
||||
} else if t.IsInteger() || t.IsString() || t.IsBoolean() || t.HasNil() {
|
||||
switch n.Op() {
|
||||
// FIXME: maybe add support for OADDSTR?
|
||||
case ir.ONIL:
|
||||
ndisp = exprLiterals
|
||||
|
||||
case ir.OLITERAL:
|
||||
if _, ok := n.(*ir.BasicLit); ok {
|
||||
} else {
|
||||
panic("unexpected")
|
||||
}
|
||||
ndisp = exprLiterals
|
||||
|
||||
case ir.ONAME:
|
||||
nn := n.(*ir.Name)
|
||||
if _, ok := ec.names[nn]; ok {
|
||||
ndisp = exprSimple
|
||||
ec.names[nn] = true
|
||||
} else {
|
||||
sv := ir.StaticValue(n)
|
||||
if sv.Op() == ir.ONAME {
|
||||
nn = sv.(*ir.Name)
|
||||
}
|
||||
if _, ok := ec.names[nn]; ok {
|
||||
ndisp = exprSimple
|
||||
ec.names[nn] = true
|
||||
}
|
||||
}
|
||||
|
||||
case ir.ONOT,
|
||||
ir.OPLUS,
|
||||
ir.ONEG:
|
||||
uex := n.(*ir.UnaryExpr)
|
||||
ndisp = ec.getdisp(uex.X)
|
||||
|
||||
case ir.OEQ,
|
||||
ir.ONE,
|
||||
ir.OLT,
|
||||
ir.OGT,
|
||||
ir.OGE,
|
||||
ir.OLE:
|
||||
// compare ops
|
||||
x, y := binparts(n)
|
||||
ndisp = ec.dispmeet(x, y)
|
||||
if debugTrace&debugTraceExprClassify != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= meet(%s,%s) = %s for op=%s\n",
|
||||
ec.getdisp(x), ec.getdisp(y), ec.dispmeet(x, y),
|
||||
n.Op().String())
|
||||
}
|
||||
case ir.OLSH,
|
||||
ir.ORSH,
|
||||
ir.ODIV,
|
||||
ir.OMOD:
|
||||
x, y := binparts(n)
|
||||
if ec.getdisp(y) == exprLiterals {
|
||||
ndisp = ec.dispmeet(x, y)
|
||||
}
|
||||
|
||||
case ir.OADD,
|
||||
ir.OSUB,
|
||||
ir.OOR,
|
||||
ir.OXOR,
|
||||
ir.OMUL,
|
||||
ir.OAND,
|
||||
ir.OANDNOT,
|
||||
ir.OANDAND,
|
||||
ir.OOROR:
|
||||
x, y := binparts(n)
|
||||
if debugTrace&debugTraceExprClassify != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= meet(%s,%s) = %s for op=%s\n",
|
||||
ec.getdisp(x), ec.getdisp(y), ec.dispmeet(x, y),
|
||||
n.Op().String())
|
||||
}
|
||||
ndisp = ec.dispmeet(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceExprClassify != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= op=%s disp=%v\n", n.Op().String(),
|
||||
ndisp.String())
|
||||
}
|
||||
|
||||
ec.disposition[n] = ndisp
|
||||
}
|
||||
|
||||
func (ec *exprClassifier) getdisp(x ir.Node) disp {
|
||||
if d, ok := ec.disposition[x]; ok {
|
||||
return d
|
||||
} else {
|
||||
panic("missing node from disp table")
|
||||
}
|
||||
}
|
||||
|
||||
// dispmeet performs a "meet" operation on the data flow states of
|
||||
// node x and y (where the term "meet" is being drawn from traditional
|
||||
// lattice-theoretical data flow analysis terminology).
|
||||
func (ec *exprClassifier) dispmeet(x, y ir.Node) disp {
|
||||
xd := ec.getdisp(x)
|
||||
if xd == exprNoInfo {
|
||||
return exprNoInfo
|
||||
}
|
||||
yd := ec.getdisp(y)
|
||||
if yd == exprNoInfo {
|
||||
return exprNoInfo
|
||||
}
|
||||
if xd == exprSimple || yd == exprSimple {
|
||||
return exprSimple
|
||||
}
|
||||
if xd != exprLiterals || yd != exprLiterals {
|
||||
panic("unexpected")
|
||||
}
|
||||
return exprLiterals
|
||||
}
|
||||
44
src/cmd/compile/internal/inline/inlheur/funcprop_string.go
Normal file
44
src/cmd/compile/internal/inline/inlheur/funcprop_string.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (fp *FuncProps) String() string {
|
||||
return fp.ToString("")
|
||||
}
|
||||
|
||||
func (fp *FuncProps) ToString(prefix string) string {
|
||||
var sb strings.Builder
|
||||
if fp.Flags != 0 {
|
||||
fmt.Fprintf(&sb, "%sFlags %s\n", prefix, fp.Flags)
|
||||
}
|
||||
flagSliceToSB[ParamPropBits](&sb, fp.ParamFlags,
|
||||
prefix, "ParamFlags")
|
||||
flagSliceToSB[ResultPropBits](&sb, fp.ResultFlags,
|
||||
prefix, "ResultFlags")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func flagSliceToSB[T interface {
|
||||
~uint32
|
||||
String() string
|
||||
}](sb *strings.Builder, sl []T, prefix string, tag string) {
|
||||
var sb2 strings.Builder
|
||||
foundnz := false
|
||||
fmt.Fprintf(&sb2, "%s%s\n", prefix, tag)
|
||||
for i, e := range sl {
|
||||
if e != 0 {
|
||||
foundnz = true
|
||||
}
|
||||
fmt.Fprintf(&sb2, "%s %d %s\n", prefix, i, e.String())
|
||||
}
|
||||
if foundnz {
|
||||
sb.WriteString(sb2.String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by "stringer -bitset -type FuncPropBits"; DO NOT EDIT.
|
||||
|
||||
package inlheur
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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[FuncPropNeverReturns-1]
|
||||
}
|
||||
|
||||
var _FuncPropBits_value = [...]uint64{
|
||||
0x1, /* FuncPropNeverReturns */
|
||||
}
|
||||
|
||||
const _FuncPropBits_name = "FuncPropNeverReturns"
|
||||
|
||||
var _FuncPropBits_index = [...]uint8{0, 20}
|
||||
|
||||
func (i FuncPropBits) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
remain := uint64(i)
|
||||
seen := false
|
||||
|
||||
for k, v := range _FuncPropBits_value {
|
||||
x := _FuncPropBits_name[_FuncPropBits_index[k]:_FuncPropBits_index[k+1]]
|
||||
if v == 0 {
|
||||
if i == 0 {
|
||||
b.WriteString(x)
|
||||
return b.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (v & remain) == v {
|
||||
remain &^= v
|
||||
x := _FuncPropBits_name[_FuncPropBits_index[k]:_FuncPropBits_index[k+1]]
|
||||
if seen {
|
||||
b.WriteString("|")
|
||||
}
|
||||
seen = true
|
||||
b.WriteString(x)
|
||||
}
|
||||
}
|
||||
if remain == 0 {
|
||||
return b.String()
|
||||
}
|
||||
return "FuncPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")"
|
||||
}
|
||||
530
src/cmd/compile/internal/inline/inlheur/funcprops_test.go
Normal file
530
src/cmd/compile/internal/inline/inlheur/funcprops_test.go
Normal file
@@ -0,0 +1,530 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var remasterflag = flag.Bool("update-expected", false, "if true, generate updated golden results in testcases for all props tests")
|
||||
|
||||
func TestFuncProperties(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
// td = "/tmp/qqq"
|
||||
// os.RemoveAll(td)
|
||||
// os.Mkdir(td, 0777)
|
||||
testenv.MustHaveGoBuild(t)
|
||||
|
||||
// NOTE: this testpoint has the unfortunate characteristic that it
|
||||
// relies on the installed compiler, meaning that if you make
|
||||
// changes to the inline heuristics code in your working copy and
|
||||
// then run the test, it will test the installed compiler and not
|
||||
// your local modifications. TODO: decide whether to convert this
|
||||
// to building a fresh compiler on the fly, or using some other
|
||||
// scheme.
|
||||
|
||||
testcases := []string{"funcflags", "returns", "params",
|
||||
"acrosscall", "calls", "returns2"}
|
||||
for _, tc := range testcases {
|
||||
dumpfile, err := gatherPropsDumpForFile(t, tc, td)
|
||||
if err != nil {
|
||||
t.Fatalf("dumping func props for %q: error %v", tc, err)
|
||||
}
|
||||
// Read in the newly generated dump.
|
||||
dentries, dcsites, derr := readDump(t, dumpfile)
|
||||
if derr != nil {
|
||||
t.Fatalf("reading func prop dump: %v", derr)
|
||||
}
|
||||
if *remasterflag {
|
||||
updateExpected(t, tc, dentries, dcsites)
|
||||
continue
|
||||
}
|
||||
// Generate expected dump.
|
||||
epath, egerr := genExpected(td, tc)
|
||||
if egerr != nil {
|
||||
t.Fatalf("generating expected func prop dump: %v", egerr)
|
||||
}
|
||||
// Read in the expected result entries.
|
||||
eentries, ecsites, eerr := readDump(t, epath)
|
||||
if eerr != nil {
|
||||
t.Fatalf("reading expected func prop dump: %v", eerr)
|
||||
}
|
||||
// Compare new vs expected.
|
||||
n := len(dentries)
|
||||
eidx := 0
|
||||
for i := 0; i < n; i++ {
|
||||
dentry := dentries[i]
|
||||
dcst := dcsites[i]
|
||||
if !interestingToCompare(dentry.fname) {
|
||||
continue
|
||||
}
|
||||
if eidx >= len(eentries) {
|
||||
t.Errorf("testcase %s missing expected entry for %s, skipping", tc, dentry.fname)
|
||||
continue
|
||||
}
|
||||
eentry := eentries[eidx]
|
||||
ecst := ecsites[eidx]
|
||||
eidx++
|
||||
if dentry.fname != eentry.fname {
|
||||
t.Errorf("got fn %q wanted %q, skipping checks",
|
||||
dentry.fname, eentry.fname)
|
||||
continue
|
||||
}
|
||||
compareEntries(t, tc, &dentry, dcst, &eentry, ecst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func propBitsToString[T interface{ String() string }](sl []T) string {
|
||||
var sb strings.Builder
|
||||
for i, f := range sl {
|
||||
fmt.Fprintf(&sb, "%d: %s\n", i, f.String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func compareEntries(t *testing.T, tc string, dentry *fnInlHeur, dcsites encodedCallSiteTab, eentry *fnInlHeur, ecsites encodedCallSiteTab) {
|
||||
dfp := dentry.props
|
||||
efp := eentry.props
|
||||
dfn := dentry.fname
|
||||
|
||||
// Compare function flags.
|
||||
if dfp.Flags != efp.Flags {
|
||||
t.Errorf("testcase %q: Flags mismatch for %q: got %s, wanted %s",
|
||||
tc, dfn, dfp.Flags.String(), efp.Flags.String())
|
||||
}
|
||||
// Compare returns
|
||||
rgot := propBitsToString[ResultPropBits](dfp.ResultFlags)
|
||||
rwant := propBitsToString[ResultPropBits](efp.ResultFlags)
|
||||
if rgot != rwant {
|
||||
t.Errorf("testcase %q: Results mismatch for %q: got:\n%swant:\n%s",
|
||||
tc, dfn, rgot, rwant)
|
||||
}
|
||||
// Compare receiver + params.
|
||||
pgot := propBitsToString[ParamPropBits](dfp.ParamFlags)
|
||||
pwant := propBitsToString[ParamPropBits](efp.ParamFlags)
|
||||
if pgot != pwant {
|
||||
t.Errorf("testcase %q: Params mismatch for %q: got:\n%swant:\n%s",
|
||||
tc, dfn, pgot, pwant)
|
||||
}
|
||||
// Compare call sites.
|
||||
for k, ve := range ecsites {
|
||||
if vd, ok := dcsites[k]; !ok {
|
||||
t.Errorf("testcase %q missing expected callsite %q in func %q", tc, k, dfn)
|
||||
continue
|
||||
} else {
|
||||
if vd != ve {
|
||||
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("testcase %q unexpected extra callsite %q in func %q", tc, k, dfn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type dumpReader struct {
|
||||
s *bufio.Scanner
|
||||
t *testing.T
|
||||
p string
|
||||
ln int
|
||||
}
|
||||
|
||||
// readDump reads in the contents of a dump file produced
|
||||
// by the "-d=dumpinlfuncprops=..." command line flag by the Go
|
||||
// compiler. It breaks the dump down into separate sections
|
||||
// by function, then deserializes each func section into a
|
||||
// fnInlHeur object and returns a slice of those objects.
|
||||
func readDump(t *testing.T, path string) ([]fnInlHeur, []encodedCallSiteTab, error) {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dr := &dumpReader{
|
||||
s: bufio.NewScanner(strings.NewReader(string(content))),
|
||||
t: t,
|
||||
p: path,
|
||||
ln: 1,
|
||||
}
|
||||
// consume header comment until preamble delimiter.
|
||||
found := false
|
||||
for dr.scan() {
|
||||
if dr.curLine() == preambleDelimiter {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, nil, fmt.Errorf("malformed testcase file %s, missing preamble delimiter", path)
|
||||
}
|
||||
res := []fnInlHeur{}
|
||||
csres := []encodedCallSiteTab{}
|
||||
for {
|
||||
dentry, dcst, err := dr.readEntry()
|
||||
if err != nil {
|
||||
t.Fatalf("reading func prop dump: %v", err)
|
||||
}
|
||||
if dentry.fname == "" {
|
||||
break
|
||||
}
|
||||
res = append(res, dentry)
|
||||
csres = append(csres, dcst)
|
||||
}
|
||||
return res, csres, nil
|
||||
}
|
||||
|
||||
func (dr *dumpReader) scan() bool {
|
||||
v := dr.s.Scan()
|
||||
if v {
|
||||
dr.ln++
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (dr *dumpReader) curLine() string {
|
||||
res := strings.TrimSpace(dr.s.Text())
|
||||
if !strings.HasPrefix(res, "// ") {
|
||||
dr.t.Fatalf("malformed line %s:%d, no comment: %s", dr.p, dr.ln, res)
|
||||
}
|
||||
return res[3:]
|
||||
}
|
||||
|
||||
// readObjBlob reads in a series of commented lines until
|
||||
// it hits a delimiter, then returns the contents of the comments.
|
||||
func (dr *dumpReader) readObjBlob(delim string) (string, error) {
|
||||
var sb strings.Builder
|
||||
foundDelim := false
|
||||
for dr.scan() {
|
||||
line := dr.curLine()
|
||||
if delim == line {
|
||||
foundDelim = true
|
||||
break
|
||||
}
|
||||
sb.WriteString(line + "\n")
|
||||
}
|
||||
if err := dr.s.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !foundDelim {
|
||||
return "", fmt.Errorf("malformed input %s, missing delimiter %q",
|
||||
dr.p, delim)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// readEntry reads a single function's worth of material from
|
||||
// a file produced by the "-d=dumpinlfuncprops=..." command line
|
||||
// flag. It deserializes the json for the func properties and
|
||||
// returns the resulting properties and function name. EOF is
|
||||
// signaled by a nil FuncProps return (with no error
|
||||
func (dr *dumpReader) readEntry() (fnInlHeur, encodedCallSiteTab, error) {
|
||||
var funcInlHeur fnInlHeur
|
||||
var callsites encodedCallSiteTab
|
||||
if !dr.scan() {
|
||||
return funcInlHeur, callsites, nil
|
||||
}
|
||||
// first line contains info about function: file/name/line
|
||||
info := dr.curLine()
|
||||
chunks := strings.Fields(info)
|
||||
funcInlHeur.file = chunks[0]
|
||||
funcInlHeur.fname = chunks[1]
|
||||
if _, err := fmt.Sscanf(chunks[2], "%d", &funcInlHeur.line); err != nil {
|
||||
return funcInlHeur, callsites, fmt.Errorf("scanning line %q: %v", info, err)
|
||||
}
|
||||
// consume comments until and including delimiter
|
||||
for {
|
||||
if !dr.scan() {
|
||||
break
|
||||
}
|
||||
if dr.curLine() == comDelimiter {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Consume JSON for encoded props.
|
||||
dr.scan()
|
||||
line := dr.curLine()
|
||||
fp := &FuncProps{}
|
||||
if err := json.Unmarshal([]byte(line), fp); err != nil {
|
||||
return funcInlHeur, callsites, err
|
||||
}
|
||||
funcInlHeur.props = fp
|
||||
|
||||
// Consume callsites.
|
||||
callsites = make(encodedCallSiteTab)
|
||||
for dr.scan() {
|
||||
line := dr.curLine()
|
||||
if line == csDelimiter {
|
||||
break
|
||||
}
|
||||
// expected format: "// callsite: <expanded pos> flagstr <desc> flagval <flags> score <score> mask <scoremask> maskstr <scoremaskstring>"
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 12 {
|
||||
return funcInlHeur, 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" || fields[6] != "score" || fields[8] != "mask" || fields[10] != "maskstr" {
|
||||
return funcInlHeur, nil, fmt.Errorf("malformed callsite %s line %d: %s",
|
||||
dr.p, dr.ln, line)
|
||||
}
|
||||
tag := fields[1]
|
||||
flagstr := fields[5]
|
||||
flags, err := strconv.Atoi(flagstr)
|
||||
if err != nil {
|
||||
return funcInlHeur, nil, fmt.Errorf("bad flags val %s line %d: %q err=%v",
|
||||
dr.p, dr.ln, line, err)
|
||||
}
|
||||
scorestr := fields[7]
|
||||
score, err2 := strconv.Atoi(scorestr)
|
||||
if err2 != nil {
|
||||
return funcInlHeur, 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 funcInlHeur, 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.
|
||||
dr.scan()
|
||||
line = dr.curLine()
|
||||
if line != fnDelimiter {
|
||||
return funcInlHeur, nil, fmt.Errorf("malformed testcase file %q, missing delimiter %q", dr.p, fnDelimiter)
|
||||
}
|
||||
|
||||
return funcInlHeur, callsites, nil
|
||||
}
|
||||
|
||||
// gatherPropsDumpForFile builds the specified testcase 'testcase' from
|
||||
// testdata/props passing the "-d=dumpinlfuncprops=..." compiler option,
|
||||
// to produce a properties dump, then returns the path of the newly
|
||||
// created file. NB: we can't use "go tool compile" here, since
|
||||
// some of the test cases import stdlib packages (such as "os").
|
||||
// This means using "go build", which is problematic since the
|
||||
// Go command can potentially cache the results of the compile step,
|
||||
// causing the test to fail when being run interactively. E.g.
|
||||
//
|
||||
// $ rm -f dump.txt
|
||||
// $ go build -o foo.a -gcflags=-d=dumpinlfuncprops=dump.txt foo.go
|
||||
// $ rm -f dump.txt foo.a
|
||||
// $ go build -o foo.a -gcflags=-d=dumpinlfuncprops=dump.txt foo.go
|
||||
// $ ls foo.a dump.txt > /dev/null
|
||||
// ls : cannot access 'dump.txt': No such file or directory
|
||||
// $
|
||||
//
|
||||
// For this reason, pick a unique filename for the dump, so as to
|
||||
// defeat the caching.
|
||||
func gatherPropsDumpForFile(t *testing.T, testcase string, td string) (string, error) {
|
||||
t.Helper()
|
||||
gopath := "testdata/props/" + testcase + ".go"
|
||||
outpath := filepath.Join(td, testcase+".a")
|
||||
salt := fmt.Sprintf(".p%dt%d", os.Getpid(), time.Now().UnixNano())
|
||||
dumpfile := filepath.Join(td, testcase+salt+".dump.txt")
|
||||
run := []string{testenv.GoToolPath(t), "build",
|
||||
"-gcflags=-d=dumpinlfuncprops=" + dumpfile, "-o", outpath, gopath}
|
||||
out, err := testenv.Command(t, run[0], run[1:]...).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Logf("compile command: %+v", run)
|
||||
}
|
||||
if strings.TrimSpace(string(out)) != "" {
|
||||
t.Logf("%s", out)
|
||||
}
|
||||
return dumpfile, err
|
||||
}
|
||||
|
||||
// genExpected reads in a given Go testcase file, strips out all the
|
||||
// unindented (column 0) commands, writes them out to a new file, and
|
||||
// returns the path of that new file. By picking out just the comments
|
||||
// from the Go file we wind up with something that resembles the
|
||||
// output from a "-d=dumpinlfuncprops=..." compilation.
|
||||
func genExpected(td string, testcase string) (string, error) {
|
||||
epath := filepath.Join(td, testcase+".expected")
|
||||
outf, err := os.OpenFile(epath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gopath := "testdata/props/" + testcase + ".go"
|
||||
content, err := os.ReadFile(gopath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines[3:] {
|
||||
if !strings.HasPrefix(line, "// ") {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(outf, "%s\n", line)
|
||||
}
|
||||
if err := outf.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return epath, nil
|
||||
}
|
||||
|
||||
type upexState struct {
|
||||
dentries []fnInlHeur
|
||||
newgolines []string
|
||||
atline map[uint]uint
|
||||
}
|
||||
|
||||
func mkUpexState(dentries []fnInlHeur) *upexState {
|
||||
atline := make(map[uint]uint)
|
||||
for _, e := range dentries {
|
||||
atline[e.line] = atline[e.line] + 1
|
||||
}
|
||||
return &upexState{
|
||||
dentries: dentries,
|
||||
atline: atline,
|
||||
}
|
||||
}
|
||||
|
||||
// updateExpected takes a given Go testcase file X.go and writes out a
|
||||
// new/updated version of the file to X.go.new, where the column-0
|
||||
// "expected" comments have been updated using fresh data from
|
||||
// "dentries".
|
||||
//
|
||||
// Writing of expected results is complicated by closures and by
|
||||
// generics, where you can have multiple functions that all share the
|
||||
// same starting line. Currently we combine up all the dups and
|
||||
// closures into the single pre-func comment.
|
||||
func updateExpected(t *testing.T, testcase string, dentries []fnInlHeur, dcsites []encodedCallSiteTab) {
|
||||
nd := len(dentries)
|
||||
|
||||
ues := mkUpexState(dentries)
|
||||
|
||||
gopath := "testdata/props/" + testcase + ".go"
|
||||
newgopath := "testdata/props/" + testcase + ".go.new"
|
||||
|
||||
// Read the existing Go file.
|
||||
content, err := os.ReadFile(gopath)
|
||||
if err != nil {
|
||||
t.Fatalf("opening %s: %v", gopath, err)
|
||||
}
|
||||
golines := strings.Split(string(content), "\n")
|
||||
|
||||
// Preserve copyright.
|
||||
ues.newgolines = append(ues.newgolines, golines[:4]...)
|
||||
if !strings.HasPrefix(golines[0], "// Copyright") {
|
||||
t.Fatalf("missing copyright from existing testcase")
|
||||
}
|
||||
golines = golines[4:]
|
||||
|
||||
clore := regexp.MustCompile(`.+\.func\d+[\.\d]*$`)
|
||||
|
||||
emitFunc := func(e *fnInlHeur, dcsites encodedCallSiteTab,
|
||||
instance, atl uint) {
|
||||
var sb strings.Builder
|
||||
dumpFnPreamble(&sb, e, dcsites, instance, atl)
|
||||
ues.newgolines = append(ues.newgolines,
|
||||
strings.Split(strings.TrimSpace(sb.String()), "\n")...)
|
||||
}
|
||||
|
||||
// Write file preamble with "DO NOT EDIT" message and such.
|
||||
var sb strings.Builder
|
||||
dumpFilePreamble(&sb)
|
||||
ues.newgolines = append(ues.newgolines,
|
||||
strings.Split(strings.TrimSpace(sb.String()), "\n")...)
|
||||
|
||||
// Helper to add a clump of functions to the output file.
|
||||
processClump := func(idx int, emit bool) int {
|
||||
// Process func itself, plus anything else defined
|
||||
// on the same line
|
||||
atl := ues.atline[dentries[idx].line]
|
||||
for k := uint(0); k < atl; k++ {
|
||||
if emit {
|
||||
emitFunc(&dentries[idx], dcsites[idx], k, atl)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
// now process any closures it contains
|
||||
ncl := 0
|
||||
for idx < nd {
|
||||
nfn := dentries[idx].fname
|
||||
if !clore.MatchString(nfn) {
|
||||
break
|
||||
}
|
||||
ncl++
|
||||
if emit {
|
||||
emitFunc(&dentries[idx], dcsites[idx], 0, 1)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
didx := 0
|
||||
for _, line := range golines {
|
||||
if strings.HasPrefix(line, "func ") {
|
||||
|
||||
// We have a function definition.
|
||||
// Pick out the corresponding entry or entries in the dump
|
||||
// and emit if interesting (or skip if not).
|
||||
dentry := dentries[didx]
|
||||
emit := interestingToCompare(dentry.fname)
|
||||
didx = processClump(didx, emit)
|
||||
}
|
||||
|
||||
// Consume all existing comments.
|
||||
if strings.HasPrefix(line, "//") {
|
||||
continue
|
||||
}
|
||||
ues.newgolines = append(ues.newgolines, line)
|
||||
}
|
||||
|
||||
if didx != nd {
|
||||
t.Logf("didx=%d wanted %d", didx, nd)
|
||||
}
|
||||
|
||||
// Open new Go file and write contents.
|
||||
of, err := os.OpenFile(newgopath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("opening %s: %v", newgopath, err)
|
||||
}
|
||||
fmt.Fprintf(of, "%s", strings.Join(ues.newgolines, "\n"))
|
||||
if err := of.Close(); err != nil {
|
||||
t.Fatalf("closing %s: %v", newgopath, err)
|
||||
}
|
||||
|
||||
t.Logf("update-expected: emitted updated file %s", newgopath)
|
||||
t.Logf("please compare the two files, then overwrite %s with %s\n",
|
||||
gopath, newgopath)
|
||||
}
|
||||
|
||||
// interestingToCompare returns TRUE if we want to compare results
|
||||
// for function 'fname'.
|
||||
func interestingToCompare(fname string) bool {
|
||||
if strings.HasPrefix(fname, "init.") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(fname, "T_") {
|
||||
return true
|
||||
}
|
||||
f := strings.Split(fname, ".")
|
||||
if len(f) == 2 && strings.HasPrefix(f[1], "T_") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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
|
||||
|
||||
// This file defines a set of Go function "properties" intended to
|
||||
// guide inlining heuristics; these properties may apply to the
|
||||
// function as a whole, or to one or more function return values or
|
||||
// parameters.
|
||||
//
|
||||
// IMPORTANT: function properties are produced on a "best effort"
|
||||
// basis, meaning that the code that computes them doesn't verify that
|
||||
// the properties are guaranteed to be true in 100% of cases. For this
|
||||
// reason, properties should only be used to drive always-safe
|
||||
// optimization decisions (e.g. "should I inline this call", or
|
||||
// "should I unroll this loop") as opposed to potentially unsafe IR
|
||||
// alterations that could change program semantics (e.g. "can I delete
|
||||
// this variable" or "can I move this statement to a new location").
|
||||
//
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// FuncProps describes a set of function or method properties that may
|
||||
// be useful for inlining heuristics. Here 'Flags' are properties that
|
||||
// we think apply to the entire function; 'RecvrParamFlags' are
|
||||
// properties of specific function params (or the receiver), and
|
||||
// 'ResultFlags' are things properties we think will apply to values
|
||||
// of specific results. Note that 'ParamFlags' includes and entry for
|
||||
// the receiver if applicable, and does include etries for blank
|
||||
// params; for a function such as "func foo(_ int, b byte, _ float32)"
|
||||
// the length of ParamFlags will be 3.
|
||||
type FuncProps struct {
|
||||
Flags FuncPropBits
|
||||
ParamFlags []ParamPropBits // slot 0 receiver if applicable
|
||||
ResultFlags []ResultPropBits
|
||||
}
|
||||
|
||||
type FuncPropBits uint32
|
||||
|
||||
const (
|
||||
// Function always panics or invokes os.Exit() or a func that does
|
||||
// likewise.
|
||||
FuncPropNeverReturns FuncPropBits = 1 << iota
|
||||
)
|
||||
|
||||
type ParamPropBits uint32
|
||||
|
||||
const (
|
||||
// No info about this param
|
||||
ParamNoInfo ParamPropBits = 0
|
||||
|
||||
// Parameter value feeds unmodified into a top-level interface
|
||||
// call (this assumes the parameter is of interface type).
|
||||
ParamFeedsInterfaceMethodCall ParamPropBits = 1 << iota
|
||||
|
||||
// Parameter value feeds unmodified into an interface call that
|
||||
// may be conditional/nested and not always executed (this assumes
|
||||
// the parameter is of interface type).
|
||||
ParamMayFeedInterfaceMethodCall ParamPropBits = 1 << iota
|
||||
|
||||
// Parameter value feeds unmodified into a top level indirect
|
||||
// function call (assumes parameter is of function type).
|
||||
ParamFeedsIndirectCall
|
||||
|
||||
// Parameter value feeds unmodified into an indirect function call
|
||||
// that is conditional/nested (not guaranteed to execute). Assumes
|
||||
// parameter is of function type.
|
||||
ParamMayFeedIndirectCall
|
||||
|
||||
// Parameter value feeds unmodified into a top level "switch"
|
||||
// statement or "if" statement simple expressions (see more on
|
||||
// "simple" expression classification below).
|
||||
ParamFeedsIfOrSwitch
|
||||
|
||||
// Parameter value feeds unmodified into a "switch" or "if"
|
||||
// statement simple expressions (see more on "simple" expression
|
||||
// classification below), where the if/switch is
|
||||
// conditional/nested.
|
||||
ParamMayFeedIfOrSwitch
|
||||
)
|
||||
|
||||
type ResultPropBits uint32
|
||||
|
||||
const (
|
||||
// No info about this result
|
||||
ResultNoInfo ResultPropBits = 0
|
||||
// This result always contains allocated memory.
|
||||
ResultIsAllocatedMem ResultPropBits = 1 << iota
|
||||
// This result is always a single concrete type that is
|
||||
// implicitly converted to interface.
|
||||
ResultIsConcreteTypeConvertedToInterface
|
||||
// Result is always the same non-composite compile time constant.
|
||||
ResultAlwaysSameConstant
|
||||
// Result is always the same function or closure.
|
||||
ResultAlwaysSameFunc
|
||||
// Result is always the same (potentially) inlinable function or closure.
|
||||
ResultAlwaysSameInlinableFunc
|
||||
)
|
||||
129
src/cmd/compile/internal/inline/inlheur/names.go
Normal file
129
src/cmd/compile/internal/inline/inlheur/names.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by "stringer -bitset -type ParamPropBits"; DO NOT EDIT.
|
||||
|
||||
package inlheur
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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[ParamNoInfo-0]
|
||||
_ = x[ParamFeedsInterfaceMethodCall-2]
|
||||
_ = x[ParamMayFeedInterfaceMethodCall-4]
|
||||
_ = x[ParamFeedsIndirectCall-8]
|
||||
_ = x[ParamMayFeedIndirectCall-16]
|
||||
_ = x[ParamFeedsIfOrSwitch-32]
|
||||
_ = x[ParamMayFeedIfOrSwitch-64]
|
||||
}
|
||||
|
||||
var _ParamPropBits_value = [...]uint64{
|
||||
0x0, /* ParamNoInfo */
|
||||
0x2, /* ParamFeedsInterfaceMethodCall */
|
||||
0x4, /* ParamMayFeedInterfaceMethodCall */
|
||||
0x8, /* ParamFeedsIndirectCall */
|
||||
0x10, /* ParamMayFeedIndirectCall */
|
||||
0x20, /* ParamFeedsIfOrSwitch */
|
||||
0x40, /* ParamMayFeedIfOrSwitch */
|
||||
}
|
||||
|
||||
const _ParamPropBits_name = "ParamNoInfoParamFeedsInterfaceMethodCallParamMayFeedInterfaceMethodCallParamFeedsIndirectCallParamMayFeedIndirectCallParamFeedsIfOrSwitchParamMayFeedIfOrSwitch"
|
||||
|
||||
var _ParamPropBits_index = [...]uint8{0, 11, 40, 71, 93, 117, 137, 159}
|
||||
|
||||
func (i ParamPropBits) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
remain := uint64(i)
|
||||
seen := false
|
||||
|
||||
for k, v := range _ParamPropBits_value {
|
||||
x := _ParamPropBits_name[_ParamPropBits_index[k]:_ParamPropBits_index[k+1]]
|
||||
if v == 0 {
|
||||
if i == 0 {
|
||||
b.WriteString(x)
|
||||
return b.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (v & remain) == v {
|
||||
remain &^= v
|
||||
x := _ParamPropBits_name[_ParamPropBits_index[k]:_ParamPropBits_index[k+1]]
|
||||
if seen {
|
||||
b.WriteString("|")
|
||||
}
|
||||
seen = true
|
||||
b.WriteString(x)
|
||||
}
|
||||
}
|
||||
if remain == 0 {
|
||||
return b.String()
|
||||
}
|
||||
return "ParamPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")"
|
||||
}
|
||||
30
src/cmd/compile/internal/inline/inlheur/pstate_string.go
Normal file
30
src/cmd/compile/internal/inline/inlheur/pstate_string.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by "stringer -type pstate"; DO NOT EDIT.
|
||||
|
||||
package inlheur
|
||||
|
||||
import "strconv"
|
||||
|
||||
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[psNoInfo-0]
|
||||
_ = x[psCallsPanic-1]
|
||||
_ = x[psMayReturn-2]
|
||||
_ = x[psTop-3]
|
||||
}
|
||||
|
||||
const _pstate_name = "psNoInfopsCallsPanicpsMayReturnpsTop"
|
||||
|
||||
var _pstate_index = [...]uint8{0, 8, 20, 31, 36}
|
||||
|
||||
func (i pstate) String() string {
|
||||
if i < 0 || i >= pstate(len(_pstate_index)-1) {
|
||||
return "pstate(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _pstate_name[_pstate_index[i]:_pstate_index[i+1]]
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
// Code generated by "stringer -bitset -type ResultPropBits"; DO NOT EDIT.
|
||||
|
||||
package inlheur
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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[ResultNoInfo-0]
|
||||
_ = x[ResultIsAllocatedMem-2]
|
||||
_ = x[ResultIsConcreteTypeConvertedToInterface-4]
|
||||
_ = x[ResultAlwaysSameConstant-8]
|
||||
_ = x[ResultAlwaysSameFunc-16]
|
||||
_ = x[ResultAlwaysSameInlinableFunc-32]
|
||||
}
|
||||
|
||||
var _ResultPropBits_value = [...]uint64{
|
||||
0x0, /* ResultNoInfo */
|
||||
0x2, /* ResultIsAllocatedMem */
|
||||
0x4, /* ResultIsConcreteTypeConvertedToInterface */
|
||||
0x8, /* ResultAlwaysSameConstant */
|
||||
0x10, /* ResultAlwaysSameFunc */
|
||||
0x20, /* ResultAlwaysSameInlinableFunc */
|
||||
}
|
||||
|
||||
const _ResultPropBits_name = "ResultNoInfoResultIsAllocatedMemResultIsConcreteTypeConvertedToInterfaceResultAlwaysSameConstantResultAlwaysSameFuncResultAlwaysSameInlinableFunc"
|
||||
|
||||
var _ResultPropBits_index = [...]uint8{0, 12, 32, 72, 96, 116, 145}
|
||||
|
||||
func (i ResultPropBits) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
remain := uint64(i)
|
||||
seen := false
|
||||
|
||||
for k, v := range _ResultPropBits_value {
|
||||
x := _ResultPropBits_name[_ResultPropBits_index[k]:_ResultPropBits_index[k+1]]
|
||||
if v == 0 {
|
||||
if i == 0 {
|
||||
b.WriteString(x)
|
||||
return b.String()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (v & remain) == v {
|
||||
remain &^= v
|
||||
x := _ResultPropBits_name[_ResultPropBits_index[k]:_ResultPropBits_index[k+1]]
|
||||
if seen {
|
||||
b.WriteString("|")
|
||||
}
|
||||
seen = true
|
||||
b.WriteString(x)
|
||||
}
|
||||
}
|
||||
if remain == 0 {
|
||||
return b.String()
|
||||
}
|
||||
return "ResultPropBits(0x" + strconv.FormatInt(int64(i), 16) + ")"
|
||||
}
|
||||
413
src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go
Normal file
413
src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go
Normal file
@@ -0,0 +1,413 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// This file contains code to re-score callsites based on how the
|
||||
// results of the call were used. Example:
|
||||
//
|
||||
// func foo() {
|
||||
// x, fptr := bar()
|
||||
// switch x {
|
||||
// case 10: fptr = baz()
|
||||
// default: blix()
|
||||
// }
|
||||
// fptr(100)
|
||||
// }
|
||||
//
|
||||
// The initial scoring pass will assign a score to "bar()" based on
|
||||
// various criteria, however once the first pass of scoring is done,
|
||||
// we look at the flags on the result from bar, and check to see
|
||||
// how those results are used. If bar() always returns the same constant
|
||||
// for its first result, and if the variable receiving that result
|
||||
// isn't redefined, and if that variable feeds into an if/switch
|
||||
// condition, then we will try to adjust the score for "bar" (on the
|
||||
// theory that if we inlined, we can constant fold / deadcode).
|
||||
|
||||
type resultPropAndCS struct {
|
||||
defcs *CallSite
|
||||
props ResultPropBits
|
||||
}
|
||||
|
||||
type resultUseAnalyzer struct {
|
||||
resultNameTab map[*ir.Name]resultPropAndCS
|
||||
fn *ir.Func
|
||||
cstab CallSiteTab
|
||||
*condLevelTracker
|
||||
}
|
||||
|
||||
// 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 (csa *callSiteAnalyzer) rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) {
|
||||
enableDebugTraceIfEnv()
|
||||
rua := &resultUseAnalyzer{
|
||||
resultNameTab: resultNameTab,
|
||||
fn: fn,
|
||||
cstab: cstab,
|
||||
condLevelTracker: new(condLevelTracker),
|
||||
}
|
||||
var doNode func(ir.Node) bool
|
||||
doNode = func(n ir.Node) bool {
|
||||
rua.nodeVisitPre(n)
|
||||
ir.DoChildren(n, doNode)
|
||||
rua.nodeVisitPost(n)
|
||||
return false
|
||||
}
|
||||
doNode(fn)
|
||||
disableDebugTrace()
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// Invoke a helper to pick out the specific ir.Name's the results
|
||||
// from this call are assigned into, e.g. "x, y := fooBar()". If
|
||||
// the call is not part of an assignment statement, or if the
|
||||
// variables in question are not newly defined, then we'll receive
|
||||
// an empty list here.
|
||||
//
|
||||
names, autoTemps, props := namesDefined(cs)
|
||||
if len(names) == 0 {
|
||||
return resultNameTab
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= %d names defined\n", len(names))
|
||||
}
|
||||
|
||||
// For each returned value, if the value has interesting
|
||||
// properties (ex: always returns the same constant), and the name
|
||||
// in question is never redefined, then make an entry in the
|
||||
// result table for it.
|
||||
const interesting = (ResultIsConcreteTypeConvertedToInterface |
|
||||
ResultAlwaysSameConstant | ResultAlwaysSameInlinableFunc | ResultAlwaysSameFunc)
|
||||
for idx, n := range names {
|
||||
rprop := props.ResultFlags[idx]
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= props for ret %d %q: %s\n",
|
||||
idx, n.Sym().Name, rprop.String())
|
||||
}
|
||||
|
||||
if rprop&interesting == 0 {
|
||||
continue
|
||||
}
|
||||
if csa.nameFinder.reassigned(n) {
|
||||
continue
|
||||
}
|
||||
if resultNameTab == nil {
|
||||
resultNameTab = make(map[*ir.Name]resultPropAndCS)
|
||||
} else if _, ok := resultNameTab[n]; ok {
|
||||
panic("should never happen")
|
||||
}
|
||||
entry := resultPropAndCS{
|
||||
defcs: cs,
|
||||
props: rprop,
|
||||
}
|
||||
resultNameTab[n] = entry
|
||||
if autoTemps[idx] != nil {
|
||||
resultNameTab[autoTemps[idx]] = entry
|
||||
}
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= add resultNameTab table entry n=%v autotemp=%v props=%s\n", n, autoTemps[idx], rprop.String())
|
||||
}
|
||||
}
|
||||
return resultNameTab
|
||||
}
|
||||
|
||||
// namesDefined returns a list of ir.Name's corresponding to locals
|
||||
// that receive the results from the call at site 'cs', plus the
|
||||
// properties object for the called function. If a given result
|
||||
// isn't cleanly assigned to a newly defined local, the
|
||||
// slot for that result in the returned list will be nil. Example:
|
||||
//
|
||||
// call returned name list
|
||||
//
|
||||
// x := foo() [ x ]
|
||||
// z, y := bar() [ nil, nil ]
|
||||
// _, q := baz() [ nil, q ]
|
||||
//
|
||||
// In the case of a multi-return call, such as "x, y := foo()",
|
||||
// the pattern we see from the front end will be a call op
|
||||
// assigning to auto-temps, and then an assignment of the auto-temps
|
||||
// to the user-level variables. In such cases we return
|
||||
// first the user-level variable (in the first func result)
|
||||
// and then the auto-temp name in the second result.
|
||||
func namesDefined(cs *CallSite) ([]*ir.Name, []*ir.Name, *FuncProps) {
|
||||
// If this call doesn't feed into an assignment (and of course not
|
||||
// all calls do), then we don't have anything to work with here.
|
||||
if cs.Assign == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
funcInlHeur, ok := fpmap[cs.Callee]
|
||||
if !ok {
|
||||
// TODO: add an assert/panic here.
|
||||
return nil, nil, nil
|
||||
}
|
||||
if len(funcInlHeur.props.ResultFlags) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Single return case.
|
||||
if len(funcInlHeur.props.ResultFlags) == 1 {
|
||||
asgn, ok := cs.Assign.(*ir.AssignStmt)
|
||||
if !ok {
|
||||
return nil, nil, nil
|
||||
}
|
||||
// locate name being assigned
|
||||
aname, ok := asgn.X.(*ir.Name)
|
||||
if !ok {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return []*ir.Name{aname}, []*ir.Name{nil}, funcInlHeur.props
|
||||
}
|
||||
|
||||
// Multi-return case
|
||||
asgn, ok := cs.Assign.(*ir.AssignListStmt)
|
||||
if !ok || !asgn.Def {
|
||||
return nil, nil, nil
|
||||
}
|
||||
userVars := make([]*ir.Name, len(funcInlHeur.props.ResultFlags))
|
||||
autoTemps := make([]*ir.Name, len(funcInlHeur.props.ResultFlags))
|
||||
for idx, x := range asgn.Lhs {
|
||||
if n, ok := x.(*ir.Name); ok {
|
||||
userVars[idx] = n
|
||||
r := asgn.Rhs[idx]
|
||||
if r.Op() == ir.OCONVNOP {
|
||||
r = r.(*ir.ConvExpr).X
|
||||
}
|
||||
if ir.IsAutoTmp(r) {
|
||||
autoTemps[idx] = r.(*ir.Name)
|
||||
}
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= multi-ret namedef uv=%v at=%v\n",
|
||||
x, autoTemps[idx])
|
||||
}
|
||||
} else {
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
return userVars, autoTemps, funcInlHeur.props
|
||||
}
|
||||
|
||||
func (rua *resultUseAnalyzer) nodeVisitPost(n ir.Node) {
|
||||
rua.condLevelTracker.post(n)
|
||||
}
|
||||
|
||||
func (rua *resultUseAnalyzer) nodeVisitPre(n ir.Node) {
|
||||
rua.condLevelTracker.pre(n)
|
||||
switch n.Op() {
|
||||
case ir.OCALLINTER:
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= rescore examine iface call %v:\n", n)
|
||||
}
|
||||
rua.callTargetCheckResults(n)
|
||||
case ir.OCALLFUNC:
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= rescore examine call %v:\n", n)
|
||||
}
|
||||
rua.callTargetCheckResults(n)
|
||||
case ir.OIF:
|
||||
ifst := n.(*ir.IfStmt)
|
||||
rua.foldCheckResults(ifst.Cond)
|
||||
case ir.OSWITCH:
|
||||
swst := n.(*ir.SwitchStmt)
|
||||
if swst.Tag != nil {
|
||||
rua.foldCheckResults(swst.Tag)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// callTargetCheckResults examines a given call to see whether the
|
||||
// callee expression is potentially an inlinable function returned
|
||||
// from a potentially inlinable call. Examples:
|
||||
//
|
||||
// Scenario 1: named intermediate
|
||||
//
|
||||
// fn1 := foo() conc := bar()
|
||||
// fn1("blah") conc.MyMethod()
|
||||
//
|
||||
// Scenario 2: returned func or concrete object feeds directly to call
|
||||
//
|
||||
// foo()("blah") bar().MyMethod()
|
||||
//
|
||||
// In the second case although at the source level the result of the
|
||||
// direct call feeds right into the method call or indirect call,
|
||||
// we're relying on the front end having inserted an auto-temp to
|
||||
// capture the value.
|
||||
func (rua *resultUseAnalyzer) callTargetCheckResults(call ir.Node) {
|
||||
ce := call.(*ir.CallExpr)
|
||||
rname := rua.getCallResultName(ce)
|
||||
if rname == nil {
|
||||
return
|
||||
}
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= staticvalue returns %v:\n",
|
||||
rname)
|
||||
}
|
||||
if rname.Class != ir.PAUTO {
|
||||
return
|
||||
}
|
||||
switch call.Op() {
|
||||
case ir.OCALLINTER:
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= in %s checking %v for cci prop:\n",
|
||||
rua.fn.Sym().Name, rname)
|
||||
}
|
||||
if cs := rua.returnHasProp(rname, ResultIsConcreteTypeConvertedToInterface); cs != nil {
|
||||
|
||||
adj := returnFeedsConcreteToInterfaceCallAdj
|
||||
cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask)
|
||||
}
|
||||
case ir.OCALLFUNC:
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= in %s checking %v for samefunc props:\n",
|
||||
rua.fn.Sym().Name, rname)
|
||||
v, ok := rua.resultNameTab[rname]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "=-= no entry for %v in rt\n", rname)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "=-= props for %v: %q\n", rname, v.props.String())
|
||||
}
|
||||
}
|
||||
if cs := rua.returnHasProp(rname, ResultAlwaysSameInlinableFunc); cs != nil {
|
||||
adj := returnFeedsInlinableFuncToIndCallAdj
|
||||
cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask)
|
||||
} else if cs := rua.returnHasProp(rname, ResultAlwaysSameFunc); cs != nil {
|
||||
adj := returnFeedsFuncToIndCallAdj
|
||||
cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// foldCheckResults examines the specified if/switch condition 'cond'
|
||||
// to see if it refers to locals defined by a (potentially inlinable)
|
||||
// function call at call site C, and if so, whether 'cond' contains
|
||||
// only combinations of simple references to all of the names in
|
||||
// 'names' with selected constants + operators. If these criteria are
|
||||
// met, then we adjust the score for call site C to reflect the
|
||||
// fact that inlining will enable deadcode and/or constant propagation.
|
||||
// Note: for this heuristic to kick in, the names in question have to
|
||||
// be all from the same callsite. Examples:
|
||||
//
|
||||
// q, r := baz() x, y := foo()
|
||||
// switch q+r { a, b, c := bar()
|
||||
// ... if x && y && a && b && c {
|
||||
// } ...
|
||||
// }
|
||||
//
|
||||
// For the call to "baz" above we apply a score adjustment, but not
|
||||
// for the calls to "foo" or "bar".
|
||||
func (rua *resultUseAnalyzer) foldCheckResults(cond ir.Node) {
|
||||
namesUsed := collectNamesUsed(cond)
|
||||
if len(namesUsed) == 0 {
|
||||
return
|
||||
}
|
||||
var cs *CallSite
|
||||
for _, n := range namesUsed {
|
||||
rpcs, found := rua.resultNameTab[n]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
if cs != nil && rpcs.defcs != cs {
|
||||
return
|
||||
}
|
||||
cs = rpcs.defcs
|
||||
if rpcs.props&ResultAlwaysSameConstant == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
nls := func(nl []*ir.Name) string {
|
||||
r := ""
|
||||
for _, n := range nl {
|
||||
r += " " + n.Sym().Name
|
||||
}
|
||||
return r
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "=-= calling ShouldFoldIfNameConstant on names={%s} cond=%v\n", nls(namesUsed), cond)
|
||||
}
|
||||
|
||||
if !ShouldFoldIfNameConstant(cond, namesUsed) {
|
||||
return
|
||||
}
|
||||
adj := returnFeedsConstToIfAdj
|
||||
cs.Score, cs.ScoreMask = adjustScore(adj, cs.Score, cs.ScoreMask)
|
||||
}
|
||||
|
||||
func collectNamesUsed(expr ir.Node) []*ir.Name {
|
||||
res := []*ir.Name{}
|
||||
ir.Visit(expr, func(n ir.Node) {
|
||||
if n.Op() != ir.ONAME {
|
||||
return
|
||||
}
|
||||
nn := n.(*ir.Name)
|
||||
if nn.Class != ir.PAUTO {
|
||||
return
|
||||
}
|
||||
res = append(res, nn)
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
func (rua *resultUseAnalyzer) returnHasProp(name *ir.Name, prop ResultPropBits) *CallSite {
|
||||
v, ok := rua.resultNameTab[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if v.props&prop == 0 {
|
||||
return nil
|
||||
}
|
||||
return v.defcs
|
||||
}
|
||||
|
||||
func (rua *resultUseAnalyzer) getCallResultName(ce *ir.CallExpr) *ir.Name {
|
||||
var callTarg ir.Node
|
||||
if sel, ok := ce.Fun.(*ir.SelectorExpr); ok {
|
||||
// method call
|
||||
callTarg = sel.X
|
||||
} else if ctarg, ok := ce.Fun.(*ir.Name); ok {
|
||||
// regular call
|
||||
callTarg = ctarg
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
r := ir.StaticValue(callTarg)
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= staticname on %v returns %v:\n",
|
||||
callTarg, r)
|
||||
}
|
||||
if r.Op() == ir.OCALLFUNC {
|
||||
// This corresponds to the "x := foo()" case; here
|
||||
// ir.StaticValue has brought us all the way back to
|
||||
// the call expression itself. We need to back off to
|
||||
// the name defined by the call; do this by looking up
|
||||
// the callsite.
|
||||
ce := r.(*ir.CallExpr)
|
||||
cs, ok := rua.cstab[ce]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
names, _, _ := namesDefined(cs)
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
return names[0]
|
||||
} else if r.Op() == ir.ONAME {
|
||||
return r.(*ir.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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[returnFeedsConstToIfAdj-2048]
|
||||
_ = x[returnFeedsFuncToIndCallAdj-4096]
|
||||
_ = x[returnFeedsInlinableFuncToIndCallAdj-8192]
|
||||
_ = x[returnFeedsConcreteToInterfaceCallAdj-16384]
|
||||
}
|
||||
|
||||
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 */
|
||||
0x800, /* returnFeedsConstToIfAdj */
|
||||
0x1000, /* returnFeedsFuncToIndCallAdj */
|
||||
0x2000, /* returnFeedsInlinableFuncToIndCallAdj */
|
||||
0x4000, /* returnFeedsConcreteToInterfaceCallAdj */
|
||||
}
|
||||
|
||||
const _scoreAdjustTyp_name = "panicPathAdjinitFuncAdjinLoopAdjpassConstToIfAdjpassConstToNestedIfAdjpassConcreteToItfCallAdjpassConcreteToNestedItfCallAdjpassFuncToIndCallAdjpassFuncToNestedIndCallAdjpassInlinableFuncToIndCallAdjpassInlinableFuncToNestedIndCallAdjreturnFeedsConstToIfAdjreturnFeedsFuncToIndCallAdjreturnFeedsInlinableFuncToIndCallAdjreturnFeedsConcreteToInterfaceCallAdj"
|
||||
|
||||
var _scoreAdjustTyp_index = [...]uint16{0, 12, 23, 32, 48, 70, 94, 124, 144, 170, 199, 234, 257, 284, 320, 357}
|
||||
|
||||
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) + ")"
|
||||
}
|
||||
751
src/cmd/compile/internal/inline/inlheur/scoring.go
Normal file
751
src/cmd/compile/internal/inline/inlheur/scoring.go
Normal file
@@ -0,0 +1,751 @@
|
||||
// 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/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/pgoir"
|
||||
"cmd/compile/internal/types"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// These constants enumerate the set of possible ways/scenarios
|
||||
// in which we'll adjust the score of a given callsite.
|
||||
type scoreAdjustTyp uint
|
||||
|
||||
// These constants capture the various ways in which the inliner's
|
||||
// scoring phase can adjust a callsite score based on heuristics. They
|
||||
// fall broadly into three categories:
|
||||
//
|
||||
// 1) adjustments based solely on the callsite context (ex: call
|
||||
// appears on panic path)
|
||||
//
|
||||
// 2) adjustments that take into account specific interesting values
|
||||
// passed at a call site (ex: passing a constant that could result in
|
||||
// cprop/deadcode in the caller)
|
||||
//
|
||||
// 3) adjustments that take into account values returned from the call
|
||||
// at a callsite (ex: call always returns the same inlinable function,
|
||||
// and return value flows unmodified into an indirect call)
|
||||
//
|
||||
// For categories 2 and 3 above, each adjustment can have either a
|
||||
// "must" version and a "may" version (but not both). Here the idea is
|
||||
// that in the "must" version the value flow is unconditional: if the
|
||||
// callsite executes, then the condition we're interested in (ex:
|
||||
// param feeding call) is guaranteed to happen. For the "may" version,
|
||||
// there may be control flow that could cause the benefit to be
|
||||
// bypassed.
|
||||
const (
|
||||
// Category 1 adjustments (see above)
|
||||
panicPathAdj scoreAdjustTyp = (1 << iota)
|
||||
initFuncAdj
|
||||
inLoopAdj
|
||||
|
||||
// Category 2 adjustments (see above).
|
||||
passConstToIfAdj
|
||||
passConstToNestedIfAdj
|
||||
passConcreteToItfCallAdj
|
||||
passConcreteToNestedItfCallAdj
|
||||
passFuncToIndCallAdj
|
||||
passFuncToNestedIndCallAdj
|
||||
passInlinableFuncToIndCallAdj
|
||||
passInlinableFuncToNestedIndCallAdj
|
||||
|
||||
// Category 3 adjustments.
|
||||
returnFeedsConstToIfAdj
|
||||
returnFeedsFuncToIndCallAdj
|
||||
returnFeedsInlinableFuncToIndCallAdj
|
||||
returnFeedsConcreteToInterfaceCallAdj
|
||||
|
||||
sentinelScoreAdj // sentinel; not a real adjustment
|
||||
)
|
||||
|
||||
// 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,
|
||||
returnFeedsConstToIfAdj: -15,
|
||||
returnFeedsFuncToIndCallAdj: -25,
|
||||
returnFeedsInlinableFuncToIndCallAdj: -40,
|
||||
returnFeedsConcreteToInterfaceCallAdj: -25,
|
||||
}
|
||||
|
||||
// SetupScoreAdjustments interprets the value of the -d=inlscoreadj
|
||||
// debugging option, if set. The value of this flag is expected to be
|
||||
// a series of "/"-separated clauses of the form adj1:value1. Example:
|
||||
// -d=inlscoreadj=inLoopAdj=0/passConstToIfAdj=-99
|
||||
func SetupScoreAdjustments() {
|
||||
if base.Debug.InlScoreAdj == "" {
|
||||
return
|
||||
}
|
||||
if err := parseScoreAdj(base.Debug.InlScoreAdj); err != nil {
|
||||
base.Fatalf("malformed -d=inlscoreadj argument %q: %v",
|
||||
base.Debug.InlScoreAdj, err)
|
||||
}
|
||||
}
|
||||
|
||||
func adjStringToVal(s string) (scoreAdjustTyp, bool) {
|
||||
for adj := scoreAdjustTyp(1); adj < sentinelScoreAdj; adj <<= 1 {
|
||||
if adj.String() == s {
|
||||
return adj, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func parseScoreAdj(val string) error {
|
||||
clauses := strings.Split(val, "/")
|
||||
if len(clauses) == 0 {
|
||||
return fmt.Errorf("no clauses")
|
||||
}
|
||||
for _, clause := range clauses {
|
||||
elems := strings.Split(clause, ":")
|
||||
if len(elems) < 2 {
|
||||
return fmt.Errorf("clause %q: expected colon", clause)
|
||||
}
|
||||
if len(elems) != 2 {
|
||||
return fmt.Errorf("clause %q has %d elements, wanted 2", clause,
|
||||
len(elems))
|
||||
}
|
||||
adj, ok := adjStringToVal(elems[0])
|
||||
if !ok {
|
||||
return fmt.Errorf("clause %q: unknown adjustment", clause)
|
||||
}
|
||||
val, err := strconv.Atoi(elems[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("clause %q: malformed value: %v", clause, err)
|
||||
}
|
||||
adjValues[adj] = val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func adjValue(x scoreAdjustTyp) int {
|
||||
if val, ok := adjValues[x]; ok {
|
||||
return val
|
||||
} else {
|
||||
panic("internal error unregistered adjustment type")
|
||||
}
|
||||
}
|
||||
|
||||
var mayMustAdj = [...]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 mayMustAdj {
|
||||
if x == v.may {
|
||||
return v.must
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
|
||||
for _, v := range mayMustAdj {
|
||||
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 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
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Stop here if no callee props.
|
||||
if calleeProps == nil {
|
||||
cs.Score, cs.ScoreMask = score, tmask
|
||||
return
|
||||
}
|
||||
|
||||
// Walk through the actual expressions being passed at the call.
|
||||
calleeRecvrParms := callee.Type().RecvParams()
|
||||
for idx := range call.Args {
|
||||
// ignore blanks
|
||||
if calleeRecvrParms[idx].Sym == nil ||
|
||||
calleeRecvrParms[idx].Sym.IsBlank() {
|
||||
continue
|
||||
}
|
||||
arg := call.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(call.Args), arg, pflag.String())
|
||||
}
|
||||
|
||||
if len(cs.ArgProps) == 0 {
|
||||
continue
|
||||
}
|
||||
argProps := cs.ArgProps[idx]
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= arg %d props %s value %v\n",
|
||||
idx, argProps.String(), arg)
|
||||
}
|
||||
|
||||
if argProps&ActualExprConstant != 0 {
|
||||
if pflag&ParamMayFeedIfOrSwitch != 0 {
|
||||
score, tmask = adjustScore(passConstToNestedIfAdj, score, tmask)
|
||||
}
|
||||
if pflag&ParamFeedsIfOrSwitch != 0 {
|
||||
score, tmask = adjustScore(passConstToIfAdj, score, tmask)
|
||||
}
|
||||
}
|
||||
|
||||
if argProps&ActualExprIsConcreteConvIface != 0 {
|
||||
// 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 argProps&(ActualExprIsFunc|ActualExprIsInlinableFunc) != 0 {
|
||||
mayadj := passFuncToNestedIndCallAdj
|
||||
mustadj := passFuncToIndCallAdj
|
||||
if argProps&ActualExprIsInlinableFunc != 0 {
|
||||
mayadj = passInlinableFuncToNestedIndCallAdj
|
||||
mustadj = passInlinableFuncToIndCallAdj
|
||||
}
|
||||
if pflag&ParamMayFeedIndirectCall != 0 {
|
||||
score, tmask = adjustScore(mayadj, score, tmask)
|
||||
}
|
||||
if pflag&ParamFeedsIndirectCall != 0 {
|
||||
score, tmask = adjustScore(mustadj, score, tmask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cs.Score, cs.ScoreMask = 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
|
||||
}
|
||||
|
||||
var resultFlagToPositiveAdj map[ResultPropBits]scoreAdjustTyp
|
||||
var paramFlagToPositiveAdj map[ParamPropBits]scoreAdjustTyp
|
||||
|
||||
func setupFlagToAdjMaps() {
|
||||
resultFlagToPositiveAdj = map[ResultPropBits]scoreAdjustTyp{
|
||||
ResultIsAllocatedMem: returnFeedsConcreteToInterfaceCallAdj,
|
||||
ResultAlwaysSameFunc: returnFeedsFuncToIndCallAdj,
|
||||
ResultAlwaysSameConstant: returnFeedsConstToIfAdj,
|
||||
}
|
||||
paramFlagToPositiveAdj = map[ParamPropBits]scoreAdjustTyp{
|
||||
ParamMayFeedInterfaceMethodCall: passConcreteToNestedItfCallAdj,
|
||||
ParamFeedsInterfaceMethodCall: passConcreteToItfCallAdj,
|
||||
ParamMayFeedIndirectCall: passInlinableFuncToNestedIndCallAdj,
|
||||
ParamFeedsIndirectCall: passInlinableFuncToIndCallAdj,
|
||||
}
|
||||
}
|
||||
|
||||
// LargestNegativeScoreAdjustment tries to estimate the largest possible
|
||||
// negative score adjustment that could be applied to a call of the
|
||||
// function with the specified props. Example:
|
||||
//
|
||||
// func foo() { func bar(x int, p *int) int {
|
||||
// ... if x < 0 { *p = x }
|
||||
// } return 99
|
||||
// }
|
||||
//
|
||||
// Function 'foo' above on the left has no interesting properties,
|
||||
// thus as a result the most we'll adjust any call to is the value for
|
||||
// "call in loop". If the calculated cost of the function is 150, and
|
||||
// the in-loop adjustment is 5 (for example), then there is not much
|
||||
// point treating it as inlinable. On the other hand "bar" has a param
|
||||
// property (parameter "x" feeds unmodified to an "if" statement") and
|
||||
// a return property (always returns same constant) meaning that a
|
||||
// given call _could_ be rescored down as much as -35 points-- thus if
|
||||
// the size of "bar" is 100 (for example) then there is at least a
|
||||
// chance that scoring will enable inlining.
|
||||
func LargestNegativeScoreAdjustment(fn *ir.Func, props *FuncProps) int {
|
||||
if resultFlagToPositiveAdj == nil {
|
||||
setupFlagToAdjMaps()
|
||||
}
|
||||
var tmask scoreAdjustTyp
|
||||
score := adjValues[inLoopAdj] // any call can be in a loop
|
||||
for _, pf := range props.ParamFlags {
|
||||
if adj, ok := paramFlagToPositiveAdj[pf]; ok {
|
||||
score, tmask = adjustScore(adj, score, tmask)
|
||||
}
|
||||
}
|
||||
for _, rf := range props.ResultFlags {
|
||||
if adj, ok := resultFlagToPositiveAdj[rf]; ok {
|
||||
score, tmask = adjustScore(adj, score, tmask)
|
||||
}
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= largestScore(%v) is %d\n",
|
||||
fn, score)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
// LargestPositiveScoreAdjustment tries to estimate the largest possible
|
||||
// positive score adjustment that could be applied to a given callsite.
|
||||
// At the moment we don't have very many positive score adjustments, so
|
||||
// this is just hard-coded, not table-driven.
|
||||
func LargestPositiveScoreAdjustment(fn *ir.Func) int {
|
||||
return adjValues[panicPathAdj] + adjValues[initFuncAdj]
|
||||
}
|
||||
|
||||
// callSiteTab contains entries for each call in the function
|
||||
// currently being processed by InlineCalls; this variable will either
|
||||
// be set to 'cstabCache' below (for non-inlinable routines) or to the
|
||||
// local 'cstab' entry in the fnInlHeur object for inlinable routines.
|
||||
//
|
||||
// NOTE: this assumes that inlining operations are happening in a serial,
|
||||
// single-threaded fashion,f which is true today but probably won't hold
|
||||
// in the future (for example, we might want to score the callsites
|
||||
// in multiple functions in parallel); if the inliner evolves in this
|
||||
// direction we'll need to come up with a different approach here.
|
||||
var callSiteTab CallSiteTab
|
||||
|
||||
// scoreCallsCache caches a call site table and call site list between
|
||||
// invocations of ScoreCalls so that we can reuse previously allocated
|
||||
// storage.
|
||||
var scoreCallsCache scoreCallsCacheType
|
||||
|
||||
type scoreCallsCacheType struct {
|
||||
tab CallSiteTab
|
||||
csl []*CallSite
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if len(fn.Body) == 0 {
|
||||
return
|
||||
}
|
||||
enableDebugTraceIfEnv()
|
||||
|
||||
nameFinder := newNameFinder(fn)
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
|
||||
}
|
||||
|
||||
// If this is an inlinable function, use the precomputed
|
||||
// call site table for it. If the function wasn't an inline
|
||||
// candidate, collect a callsite table for it now.
|
||||
var cstab CallSiteTab
|
||||
if funcInlHeur, ok := fpmap[fn]; ok {
|
||||
cstab = funcInlHeur.cstab
|
||||
} else {
|
||||
if len(scoreCallsCache.tab) != 0 {
|
||||
panic("missing call to ScoreCallsCleanup")
|
||||
}
|
||||
if scoreCallsCache.tab == nil {
|
||||
scoreCallsCache.tab = make(CallSiteTab)
|
||||
}
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
|
||||
ir.FuncName(fn))
|
||||
}
|
||||
cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0,
|
||||
nameFinder)
|
||||
}
|
||||
|
||||
csa := makeCallSiteAnalyzer(fn)
|
||||
const doCallResults = true
|
||||
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 (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) len(cstab)=%d\n",
|
||||
ir.FuncName(fn), region[0].Op().String(), len(cstab))
|
||||
}
|
||||
|
||||
// Sort callsites to avoid any surprises with non deterministic
|
||||
// map iteration order (this is probably not needed, but here just
|
||||
// in case).
|
||||
csl := scoreCallsCache.csl[:0]
|
||||
for _, cs := range cstab {
|
||||
csl = append(csl, cs)
|
||||
}
|
||||
scoreCallsCache.csl = csl[:0]
|
||||
sort.Slice(csl, func(i, j int) bool {
|
||||
return csl[i].ID < csl[j].ID
|
||||
})
|
||||
|
||||
// Score each call site.
|
||||
var resultNameTab map[*ir.Name]resultPropAndCS
|
||||
for _, cs := range csl {
|
||||
var cprops *FuncProps
|
||||
fihcprops := false
|
||||
desercprops := false
|
||||
if funcInlHeur, ok := fpmap[cs.Callee]; ok {
|
||||
cprops = funcInlHeur.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.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 = csa.examineCallResults(cs, resultNameTab)
|
||||
}
|
||||
|
||||
if debugTrace&debugTraceScoring != 0 {
|
||||
fmt.Fprintf(os.Stderr, "=-= scoring call at %s: flags=%d score=%d funcInlHeur=%v deser=%v\n", fmtFullPos(cs.Call.Pos()), cs.Flags, cs.Score, fihcprops, desercprops)
|
||||
}
|
||||
}
|
||||
|
||||
if resultNameTab != nil {
|
||||
csa.rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
|
||||
}
|
||||
|
||||
disableDebugTrace()
|
||||
|
||||
if ic != nil && callSiteTab != nil {
|
||||
// Integrate the calls from this cstab into the table for the caller.
|
||||
if err := callSiteTab.merge(cstab); err != nil {
|
||||
base.FatalfAt(ic.Pos(), "%v", err)
|
||||
}
|
||||
} else {
|
||||
callSiteTab = cstab
|
||||
}
|
||||
}
|
||||
|
||||
// ScoreCallsCleanup resets the state of the callsite cache
|
||||
// once ScoreCalls is done with a function.
|
||||
func ScoreCallsCleanup() {
|
||||
if base.Debug.DumpInlCallSiteScores != 0 {
|
||||
if allCallSites == nil {
|
||||
allCallSites = make(CallSiteTab)
|
||||
}
|
||||
for call, cs := range callSiteTab {
|
||||
allCallSites[call] = cs
|
||||
}
|
||||
}
|
||||
for k := range scoreCallsCache.tab {
|
||||
delete(scoreCallsCache.tab, k)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCallSiteScore returns the previously calculated score for call
|
||||
// within fn.
|
||||
func GetCallSiteScore(fn *ir.Func, call *ir.CallExpr) (int, bool) {
|
||||
if funcInlHeur, ok := fpmap[fn]; ok {
|
||||
if cs, ok := funcInlHeur.cstab[call]; ok {
|
||||
return cs.Score, true
|
||||
}
|
||||
}
|
||||
if cs, ok := callSiteTab[call]; ok {
|
||||
return cs.Score, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// BudgetExpansion returns the amount to relax/expand the base
|
||||
// inlining budget when the new inliner is turned on; the inliner
|
||||
// will add the returned value to the hairiness budget.
|
||||
//
|
||||
// Background: with the new inliner, the score for a given callsite
|
||||
// can be adjusted down by some amount due to heuristics, however we
|
||||
// won't know whether this is going to happen until much later after
|
||||
// the CanInline call. This function returns the amount to relax the
|
||||
// budget initially (to allow for a large score adjustment); later on
|
||||
// in RevisitInlinability we'll look at each individual function to
|
||||
// demote it if needed.
|
||||
func BudgetExpansion(maxBudget int32) int32 {
|
||||
if base.Debug.InlBudgetSlack != 0 {
|
||||
return int32(base.Debug.InlBudgetSlack)
|
||||
}
|
||||
// In the default case, return maxBudget, which will effectively
|
||||
// double the budget from 80 to 160; this should be good enough
|
||||
// for most cases.
|
||||
return maxBudget
|
||||
}
|
||||
|
||||
var allCallSites CallSiteTab
|
||||
|
||||
// DumpInlCallSiteScores is invoked by the inliner if the debug flag
|
||||
// "-d=dumpinlcallsitescores" is set; it dumps out a human-readable
|
||||
// summary of all (potentially) inlinable callsites in the package,
|
||||
// along with info on call site scoring and the adjustments made to a
|
||||
// given score. Here profile is the PGO profile in use (may be
|
||||
// nil), budgetCallback is a callback that can be invoked to find out
|
||||
// the original pre-adjustment hairiness limit for the function, and
|
||||
// inlineHotMaxBudget is the constant of the same name used in the
|
||||
// inliner. Sample output lines:
|
||||
//
|
||||
// Score Adjustment Status Callee CallerPos ScoreFlags
|
||||
// 115 40 DEMOTED cmd/compile/internal/abi.(*ABIParamAssignment).Offset expand_calls.go:1679:14|6 panicPathAdj
|
||||
// 76 -5n PROMOTED runtime.persistentalloc mcheckmark.go:48:45|3 inLoopAdj
|
||||
// 201 0 --- PGO unicode.DecodeRuneInString utf8.go:312:30|1
|
||||
// 7 -5 --- PGO internal/abi.Name.DataChecked type.go:625:22|0 inLoopAdj
|
||||
//
|
||||
// In the dump above, "Score" is the final score calculated for the
|
||||
// callsite, "Adjustment" is the amount added to or subtracted from
|
||||
// the original hairiness estimate to form the score. "Status" shows
|
||||
// whether anything changed with the site -- did the adjustment bump
|
||||
// it down just below the threshold ("PROMOTED") or instead bump it
|
||||
// above the threshold ("DEMOTED"); this will be blank ("---") if no
|
||||
// threshold was crossed as a result of the heuristics. Note that
|
||||
// "Status" also shows whether PGO was involved. "Callee" is the name
|
||||
// of the function called, "CallerPos" is the position of the
|
||||
// callsite, and "ScoreFlags" is a digest of the specific properties
|
||||
// we used to make adjustments to callsite score via heuristics.
|
||||
func DumpInlCallSiteScores(profile *pgoir.Profile, budgetCallback func(fn *ir.Func, profile *pgoir.Profile) (int32, bool)) {
|
||||
|
||||
var indirectlyDueToPromotion func(cs *CallSite) bool
|
||||
indirectlyDueToPromotion = func(cs *CallSite) bool {
|
||||
bud, _ := budgetCallback(cs.Callee, profile)
|
||||
hairyval := cs.Callee.Inl.Cost
|
||||
score := int32(cs.Score)
|
||||
if hairyval > bud && score <= bud {
|
||||
return true
|
||||
}
|
||||
if cs.parent != nil {
|
||||
return indirectlyDueToPromotion(cs.parent)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
genstatus := func(cs *CallSite) string {
|
||||
hairyval := cs.Callee.Inl.Cost
|
||||
bud, isPGO := budgetCallback(cs.Callee, profile)
|
||||
score := int32(cs.Score)
|
||||
st := "---"
|
||||
expinl := false
|
||||
switch {
|
||||
case hairyval <= bud && score <= bud:
|
||||
// "Normal" inlined case: hairy val sufficiently low that
|
||||
// it would have been inlined anyway without heuristics.
|
||||
expinl = true
|
||||
case hairyval > bud && score > bud:
|
||||
// "Normal" not inlined case: hairy val sufficiently high
|
||||
// and scoring didn't lower it.
|
||||
case hairyval > bud && score <= bud:
|
||||
// Promoted: we would not have inlined it before, but
|
||||
// after score adjustment we decided to inline.
|
||||
st = "PROMOTED"
|
||||
expinl = true
|
||||
case hairyval <= bud && score > bud:
|
||||
// Demoted: we would have inlined it before, but after
|
||||
// score adjustment we decided not to inline.
|
||||
st = "DEMOTED"
|
||||
}
|
||||
inlined := cs.aux&csAuxInlined != 0
|
||||
indprom := false
|
||||
if cs.parent != nil {
|
||||
indprom = indirectlyDueToPromotion(cs.parent)
|
||||
}
|
||||
if inlined && indprom {
|
||||
st += "|INDPROM"
|
||||
}
|
||||
if inlined && !expinl {
|
||||
st += "|[NI?]"
|
||||
} else if !inlined && expinl {
|
||||
st += "|[IN?]"
|
||||
}
|
||||
if isPGO {
|
||||
st += "|PGO"
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
if base.Debug.DumpInlCallSiteScores != 0 {
|
||||
var sl []*CallSite
|
||||
for _, cs := range allCallSites {
|
||||
sl = append(sl, cs)
|
||||
}
|
||||
sort.Slice(sl, func(i, j int) bool {
|
||||
if sl[i].Score != sl[j].Score {
|
||||
return sl[i].Score < sl[j].Score
|
||||
}
|
||||
fni := ir.PkgFuncName(sl[i].Callee)
|
||||
fnj := ir.PkgFuncName(sl[j].Callee)
|
||||
if fni != fnj {
|
||||
return fni < fnj
|
||||
}
|
||||
ecsi := EncodeCallSiteKey(sl[i])
|
||||
ecsj := EncodeCallSiteKey(sl[j])
|
||||
return ecsi < ecsj
|
||||
})
|
||||
|
||||
mkname := func(fn *ir.Func) string {
|
||||
var n string
|
||||
if fn == nil || fn.Nname == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
if fn.Sym().Pkg == types.LocalPkg {
|
||||
n = "·" + fn.Sym().Name
|
||||
} else {
|
||||
n = ir.PkgFuncName(fn)
|
||||
}
|
||||
// don't try to print super-long names
|
||||
if len(n) <= 64 {
|
||||
return n
|
||||
}
|
||||
return n[:32] + "..." + n[len(n)-32:len(n)]
|
||||
}
|
||||
|
||||
if len(sl) != 0 {
|
||||
fmt.Fprintf(os.Stdout, "# scores for package %s\n", types.LocalPkg.Path)
|
||||
fmt.Fprintf(os.Stdout, "# Score Adjustment Status Callee CallerPos Flags ScoreFlags\n")
|
||||
}
|
||||
for _, cs := range sl {
|
||||
hairyval := cs.Callee.Inl.Cost
|
||||
adj := int32(cs.Score) - hairyval
|
||||
nm := mkname(cs.Callee)
|
||||
ecc := EncodeCallSiteKey(cs)
|
||||
fmt.Fprintf(os.Stdout, "%d %d\t%s\t%s\t%s\t%s\n",
|
||||
cs.Score, adj, genstatus(cs),
|
||||
nm, ecc,
|
||||
cs.ScoreMask.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/cmd/compile/internal/inline/inlheur/serialize.go
Normal file
80
src/cmd/compile/internal/inline/inlheur/serialize.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 "strings"
|
||||
|
||||
func (funcProps *FuncProps) SerializeToString() string {
|
||||
if funcProps == nil {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
writeUleb128(&sb, uint64(funcProps.Flags))
|
||||
writeUleb128(&sb, uint64(len(funcProps.ParamFlags)))
|
||||
for _, pf := range funcProps.ParamFlags {
|
||||
writeUleb128(&sb, uint64(pf))
|
||||
}
|
||||
writeUleb128(&sb, uint64(len(funcProps.ResultFlags)))
|
||||
for _, rf := range funcProps.ResultFlags {
|
||||
writeUleb128(&sb, uint64(rf))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func DeserializeFromString(s string) *FuncProps {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
var funcProps FuncProps
|
||||
var v uint64
|
||||
sl := []byte(s)
|
||||
v, sl = readULEB128(sl)
|
||||
funcProps.Flags = FuncPropBits(v)
|
||||
v, sl = readULEB128(sl)
|
||||
funcProps.ParamFlags = make([]ParamPropBits, v)
|
||||
for i := range funcProps.ParamFlags {
|
||||
v, sl = readULEB128(sl)
|
||||
funcProps.ParamFlags[i] = ParamPropBits(v)
|
||||
}
|
||||
v, sl = readULEB128(sl)
|
||||
funcProps.ResultFlags = make([]ResultPropBits, v)
|
||||
for i := range funcProps.ResultFlags {
|
||||
v, sl = readULEB128(sl)
|
||||
funcProps.ResultFlags[i] = ResultPropBits(v)
|
||||
}
|
||||
return &funcProps
|
||||
}
|
||||
|
||||
func readULEB128(sl []byte) (value uint64, rsl []byte) {
|
||||
var shift uint
|
||||
|
||||
for {
|
||||
b := sl[0]
|
||||
sl = sl[1:]
|
||||
value |= (uint64(b&0x7F) << shift)
|
||||
if b&0x80 == 0 {
|
||||
break
|
||||
}
|
||||
shift += 7
|
||||
}
|
||||
return value, sl
|
||||
}
|
||||
|
||||
func writeUleb128(sb *strings.Builder, v uint64) {
|
||||
if v < 128 {
|
||||
sb.WriteByte(uint8(v))
|
||||
return
|
||||
}
|
||||
more := true
|
||||
for more {
|
||||
c := uint8(v & 0x7f)
|
||||
v >>= 7
|
||||
more = v != 0
|
||||
if more {
|
||||
c |= 0x80
|
||||
}
|
||||
sb.WriteByte(c)
|
||||
}
|
||||
}
|
||||
45
src/cmd/compile/internal/inline/inlheur/testdata/dumpscores.go
vendored
Normal file
45
src/cmd/compile/internal/inline/inlheur/testdata/dumpscores.go
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 dumpscores
|
||||
|
||||
var G int
|
||||
|
||||
func inlinable(x int, f func(int) int) int {
|
||||
if x != 0 {
|
||||
return 1
|
||||
}
|
||||
G += noninl(x)
|
||||
return f(x)
|
||||
}
|
||||
|
||||
func inlinable2(x int) int {
|
||||
return noninl(-x)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func noninl(x int) int {
|
||||
return x + 1
|
||||
}
|
||||
|
||||
func tooLargeToInline(x int) int {
|
||||
if x > 101 {
|
||||
// Drive up the cost of inlining this func over the
|
||||
// regular threshold.
|
||||
return big(big(big(big(big(G + x)))))
|
||||
}
|
||||
if x < 100 {
|
||||
// make sure this callsite is scored properly
|
||||
G += inlinable(101, inlinable2)
|
||||
if G == 101 {
|
||||
return 0
|
||||
}
|
||||
panic(inlinable2(3))
|
||||
}
|
||||
return G
|
||||
}
|
||||
|
||||
func big(q int) int {
|
||||
return noninl(q) + noninl(-q)
|
||||
}
|
||||
77
src/cmd/compile/internal/inline/inlheur/testdata/props/README.txt
vendored
Normal file
77
src/cmd/compile/internal/inline/inlheur/testdata/props/README.txt
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
Notes on the format of the testcase files in
|
||||
cmd/compile/internal/inline/inlheur/testdata/props:
|
||||
|
||||
- each (compilable) file contains input Go code and expected results
|
||||
in the form of column-0 comments.
|
||||
|
||||
- functions or methods that begin with "T_" are targeted for testing,
|
||||
as well as "init" functions; all other functions are ignored.
|
||||
|
||||
- function header comments begin with a line containing
|
||||
the file name, function name, definition line, then index
|
||||
and a count of the number of funcs that share that same
|
||||
definition line (needed to support generics). Example:
|
||||
|
||||
// foo.go T_mumble 35 1 4
|
||||
|
||||
Here "T_mumble" is defined at line 35, and it is func 0
|
||||
out of the 4 funcs that share that same line.
|
||||
|
||||
- function property expected results appear as comments in immediately
|
||||
prior to the function. For example, here we have first the function
|
||||
name ("T_feeds_if_simple"), then human-readable dump of the function
|
||||
properties, as well as the JSON for the properties object, each
|
||||
section separated by a "<>" delimiter.
|
||||
|
||||
// params.go T_feeds_if_simple 35 0 1
|
||||
// RecvrParamFlags:
|
||||
// 0: ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"RecvrParamFlags":[8],"ReturnFlags":[]}
|
||||
// callsite: params.go:34:10|0 "CallSiteOnPanicPath" 2
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_simple(x int) {
|
||||
if x < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
- when the test runs, it will compile the Go source file with an
|
||||
option to dump out function properties, then compare the new dump
|
||||
for each function with the JSON appearing in the header comment for
|
||||
the function (in the example above, the JSON appears between
|
||||
"<endpropsdump>" and "<endfuncpreamble>". The material prior to the
|
||||
dump is simply there for human consumption, so that a developer can
|
||||
easily see that "RecvrParamFlags":[8] means that the first parameter
|
||||
has flag ParamFeedsIfOrSwitch.
|
||||
|
||||
- when making changes to the compiler (which can alter the expected
|
||||
results) or edits/additions to the go code in the testcase files,
|
||||
you can remaster the results by running
|
||||
|
||||
go test -v -count=1 .
|
||||
|
||||
In the trace output of this run, you'll see messages of the form
|
||||
|
||||
=== RUN TestFuncProperties
|
||||
funcprops_test.go:NNN: update-expected: emitted updated file
|
||||
testdata/props/XYZ.go.new
|
||||
funcprops_test.go:MMM: please compare the two files, then overwrite
|
||||
testdata/props/XYZ.go with testdata/props/XYZ.go.new
|
||||
|
||||
at which point you can compare the old and new files by hand, then
|
||||
overwrite the *.go file with the *.go.new file if you are happy with
|
||||
the diffs.
|
||||
|
||||
- note that the remastering process will strip out any existing
|
||||
column-0 (unindented) comments; if you write comments that you
|
||||
want to see preserved, use "/* */" or indent them.
|
||||
|
||||
|
||||
|
||||
214
src/cmd/compile/internal/inline/inlheur/testdata/props/acrosscall.go
vendored
Normal file
214
src/cmd/compile/internal/inline/inlheur/testdata/props/acrosscall.go
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
package params
|
||||
|
||||
// acrosscall.go T_feeds_indirect_call_via_call_toplevel 19 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIndirectCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[8],"ResultFlags":null}
|
||||
// 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)) {
|
||||
callsparam(f)
|
||||
}
|
||||
|
||||
// acrosscall.go T_feeds_indirect_call_via_call_conditional 31 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIndirectCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[16],"ResultFlags":null}
|
||||
// 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)) {
|
||||
if G != 101 {
|
||||
callsparam(f)
|
||||
}
|
||||
}
|
||||
|
||||
// acrosscall.go T_feeds_conditional_indirect_call_via_call_toplevel 45 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIndirectCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[16],"ResultFlags":null}
|
||||
// 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)) {
|
||||
callsparamconditional(f)
|
||||
}
|
||||
|
||||
// acrosscall.go T_feeds_if_via_call 57 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// callsite: acrosscall.go:58:9|0 flagstr "" flagval 0 score 8 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_via_call(x int) {
|
||||
feedsif(x)
|
||||
}
|
||||
|
||||
// acrosscall.go T_feeds_if_via_call_conditional 69 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[64],"ResultFlags":null}
|
||||
// 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) {
|
||||
if G != 101 {
|
||||
feedsif(x)
|
||||
}
|
||||
}
|
||||
|
||||
// acrosscall.go T_feeds_conditional_if_via_call 83 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[64],"ResultFlags":null}
|
||||
// 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) {
|
||||
feedsifconditional(x)
|
||||
}
|
||||
|
||||
// acrosscall.go T_multifeeds1 97 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIndirectCall|ParamMayFeedIndirectCall
|
||||
// 1 ParamNoInfo
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[24,0],"ResultFlags":null}
|
||||
// callsite: acrosscall.go:98:12|0 flagstr "" flagval 0 score 60 mask 0 maskstr ""
|
||||
// callsite: acrosscall.go:99:23|1 flagstr "" flagval 0 score 64 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multifeeds1(f1, f2 func(int)) {
|
||||
callsparam(f1)
|
||||
callsparamconditional(f1)
|
||||
}
|
||||
|
||||
// acrosscall.go T_acrosscall_returnsconstant 110 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameConstant
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[8]}
|
||||
// callsite: acrosscall.go:111:24|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_acrosscall_returnsconstant() int {
|
||||
return returnsconstant()
|
||||
}
|
||||
|
||||
// acrosscall.go T_acrosscall_returnsmem 122 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsAllocatedMem
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[2]}
|
||||
// callsite: acrosscall.go:123:19|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_acrosscall_returnsmem() *int {
|
||||
return returnsmem()
|
||||
}
|
||||
|
||||
// acrosscall.go T_acrosscall_returnscci 134 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsConcreteTypeConvertedToInterface
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[4]}
|
||||
// callsite: acrosscall.go:135:19|0 flagstr "" flagval 0 score 7 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_acrosscall_returnscci() I {
|
||||
return returnscci()
|
||||
}
|
||||
|
||||
// acrosscall.go T_acrosscall_multiret 144 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: acrosscall.go:146:25|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_acrosscall_multiret(q int) int {
|
||||
if q != G {
|
||||
return returnsconstant()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// acrosscall.go T_acrosscall_multiret2 158 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: acrosscall.go:160:25|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// callsite: acrosscall.go:162:25|1 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_acrosscall_multiret2(q int) int {
|
||||
if q == G {
|
||||
return returnsconstant()
|
||||
} else {
|
||||
return returnsconstant()
|
||||
}
|
||||
}
|
||||
|
||||
func callsparam(f func(int)) {
|
||||
f(2)
|
||||
}
|
||||
|
||||
func callsparamconditional(f func(int)) {
|
||||
if G != 101 {
|
||||
f(2)
|
||||
}
|
||||
}
|
||||
|
||||
func feedsif(x int) int {
|
||||
if x != 101 {
|
||||
return 42
|
||||
}
|
||||
return 43
|
||||
}
|
||||
|
||||
func feedsifconditional(x int) int {
|
||||
if G != 101 {
|
||||
if x != 101 {
|
||||
return 42
|
||||
}
|
||||
}
|
||||
return 43
|
||||
}
|
||||
|
||||
func returnsconstant() int {
|
||||
return 42
|
||||
}
|
||||
|
||||
func returnsmem() *int {
|
||||
return new(int)
|
||||
}
|
||||
|
||||
func returnscci() I {
|
||||
var q Q
|
||||
return q
|
||||
}
|
||||
|
||||
type I interface {
|
||||
Foo()
|
||||
}
|
||||
|
||||
type Q int
|
||||
|
||||
func (q Q) Foo() {
|
||||
}
|
||||
|
||||
var G int
|
||||
240
src/cmd/compile/internal/inline/inlheur/testdata/props/calls.go
vendored
Normal file
240
src/cmd/compile/internal/inline/inlheur/testdata/props/calls.go
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
package calls
|
||||
|
||||
import "os"
|
||||
|
||||
// calls.go T_call_in_panic_arg 19 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// 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) {
|
||||
if x < G {
|
||||
panic(callee(x))
|
||||
}
|
||||
}
|
||||
|
||||
// calls.go T_calls_in_loops 32 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// 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) {
|
||||
for i := 0; i < x; i++ {
|
||||
callee(i)
|
||||
}
|
||||
for _, s := range q {
|
||||
callee(len(s))
|
||||
}
|
||||
}
|
||||
|
||||
// calls.go T_calls_in_pseudo_loop 48 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// 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) {
|
||||
for i := 0; i < x; i++ {
|
||||
callee(i)
|
||||
return
|
||||
}
|
||||
for _, s := range q {
|
||||
callee(len(s))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// calls.go T_calls_on_panic_paths 67 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// callsite: calls.go:69:9|0 flagstr "CallSiteOnPanicPath" flagval 2 score 42 mask 1 maskstr "panicPathAdj"
|
||||
// callsite: calls.go:73:9|1 flagstr "CallSiteOnPanicPath" flagval 2 score 42 mask 1 maskstr "panicPathAdj"
|
||||
// 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) {
|
||||
if x+G == 101 {
|
||||
callee(x)
|
||||
panic("ouch")
|
||||
}
|
||||
if x < G-101 {
|
||||
callee(x)
|
||||
if len(q) == 0 {
|
||||
G++
|
||||
}
|
||||
callsexit(x)
|
||||
}
|
||||
}
|
||||
|
||||
// calls.go T_calls_not_on_panic_paths 93 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch|ParamMayFeedIfOrSwitch
|
||||
// 1 ParamNoInfo
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[96,0],"ResultFlags":null}
|
||||
// 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 "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_calls_not_on_panic_paths(x int, q []string) {
|
||||
if x != G {
|
||||
panic("ouch")
|
||||
/* Notes: */
|
||||
/* - we only look for post-dominating panic/exit, so */
|
||||
/* this site will on fact not have a panicpath flag */
|
||||
/* - vet will complain about this site as unreachable */
|
||||
callee(x)
|
||||
}
|
||||
if x != G {
|
||||
callee(x)
|
||||
if x < 100 {
|
||||
panic("ouch")
|
||||
}
|
||||
}
|
||||
if x+G == 101 {
|
||||
if x < 100 {
|
||||
panic("ouch")
|
||||
}
|
||||
callee(x)
|
||||
}
|
||||
if x < -101 {
|
||||
callee(x)
|
||||
if len(q) == 0 {
|
||||
return
|
||||
}
|
||||
callsexit(x)
|
||||
}
|
||||
}
|
||||
|
||||
// calls.go init.0 129 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
|
||||
// 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 140 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: calls.go:141:19|0 flagstr "" flagval 0 score 16 mask 512 maskstr "passInlinableFuncToIndCallAdj"
|
||||
// callsite: calls.go:141:19|calls.go:232:10|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <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 150 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: calls.go:153: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 165 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
|
||||
// callsite: calls.go:166:25|0 flagstr "" flagval 0 score 27 mask 1024 maskstr "passInlinableFuncToNestedIndCallAdj"
|
||||
// callsite: calls.go:166:25|calls.go:237:11|0 flagstr "" flagval 0 score 2 mask 0 maskstr ""
|
||||
// <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 177 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
|
||||
// callsite: calls.go:178: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)
|
||||
}
|
||||
|
||||
// calls.go T_call_scoring_in_noninlinable_func 195 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[0]}
|
||||
// callsite: calls.go:209:14|0 flagstr "CallSiteOnPanicPath" flagval 2 score 42 mask 1 maskstr "panicPathAdj"
|
||||
// callsite: calls.go:210:15|1 flagstr "CallSiteOnPanicPath" flagval 2 score 42 mask 1 maskstr "panicPathAdj"
|
||||
// callsite: calls.go:212:19|2 flagstr "" flagval 0 score 16 mask 512 maskstr "passInlinableFuncToIndCallAdj"
|
||||
// callsite: calls.go:212:19|calls.go:232:10|0 flagstr "" flagval 0 score 4 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// calls.go T_call_scoring_in_noninlinable_func.func1 212 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_call_scoring_in_noninlinable_func(x int, sl []int) int {
|
||||
if x == 101 {
|
||||
// Drive up the cost of inlining this funcfunc over the
|
||||
// regular threshold.
|
||||
for i := 0; i < 10; i++ {
|
||||
for j := 0; j < i; j++ {
|
||||
sl = append(sl, append(sl, append(sl, append(sl, x)...)...)...)
|
||||
sl = append(sl, sl[0], sl[1], sl[2])
|
||||
x += calleeNoInline(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
if x < 100 {
|
||||
// make sure this callsite is scored properly
|
||||
G += callee(101)
|
||||
panic(callee(x))
|
||||
}
|
||||
return callsParam(x, func(y int) int { return y + x })
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
341
src/cmd/compile/internal/inline/inlheur/testdata/props/funcflags.go
vendored
Normal file
341
src/cmd/compile/internal/inline/inlheur/testdata/props/funcflags.go
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
|
||||
package funcflags
|
||||
|
||||
import "os"
|
||||
|
||||
// funcflags.go T_simple 20 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":null,"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_simple() {
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// funcflags.go T_nested 32 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_nested(x int) {
|
||||
if x < 10 {
|
||||
panic("bad")
|
||||
} else {
|
||||
panic("good")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_block1 46 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_block1(x int) {
|
||||
panic("bad")
|
||||
if x < 10 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_block2 60 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_block2(x int) {
|
||||
if x < 10 {
|
||||
return
|
||||
}
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// funcflags.go T_switches1 75 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_switches1(x int) {
|
||||
switch x {
|
||||
case 1:
|
||||
panic("one")
|
||||
case 2:
|
||||
panic("two")
|
||||
}
|
||||
panic("whatev")
|
||||
}
|
||||
|
||||
// funcflags.go T_switches1a 92 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_switches1a(x int) {
|
||||
switch x {
|
||||
case 2:
|
||||
panic("two")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_switches2 106 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_switches2(x int) {
|
||||
switch x {
|
||||
case 1:
|
||||
panic("one")
|
||||
case 2:
|
||||
panic("two")
|
||||
default:
|
||||
return
|
||||
}
|
||||
panic("whatev")
|
||||
}
|
||||
|
||||
// funcflags.go T_switches3 123 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_switches3(x interface{}) {
|
||||
switch x.(type) {
|
||||
case bool:
|
||||
panic("one")
|
||||
case float32:
|
||||
panic("two")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_switches4 138 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_switches4(x int) {
|
||||
switch x {
|
||||
case 1:
|
||||
x++
|
||||
fallthrough
|
||||
case 2:
|
||||
panic("two")
|
||||
fallthrough
|
||||
default:
|
||||
panic("bad")
|
||||
}
|
||||
panic("whatev")
|
||||
}
|
||||
|
||||
// funcflags.go T_recov 157 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_recov(x int) {
|
||||
if x := recover(); x != nil {
|
||||
panic(x)
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_forloops1 169 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_forloops1(x int) {
|
||||
for {
|
||||
panic("wokketa")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_forloops2 180 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_forloops2(x int) {
|
||||
for {
|
||||
println("blah")
|
||||
if true {
|
||||
break
|
||||
}
|
||||
panic("warg")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_forloops3 195 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_forloops3(x int) {
|
||||
for i := 0; i < 101; i++ {
|
||||
println("blah")
|
||||
if true {
|
||||
continue
|
||||
}
|
||||
panic("plark")
|
||||
}
|
||||
for i := range [10]int{} {
|
||||
println(i)
|
||||
panic("plark")
|
||||
}
|
||||
panic("whatev")
|
||||
}
|
||||
|
||||
// funcflags.go T_hasgotos 215 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_hasgotos(x int, y int) {
|
||||
{
|
||||
xx := x
|
||||
panic("bad")
|
||||
lab1:
|
||||
goto lab2
|
||||
lab2:
|
||||
if false {
|
||||
goto lab1
|
||||
} else {
|
||||
goto lab4
|
||||
}
|
||||
lab4:
|
||||
if xx < y {
|
||||
lab3:
|
||||
if false {
|
||||
goto lab3
|
||||
}
|
||||
}
|
||||
println(9)
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_break_with_label 246 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIfOrSwitch
|
||||
// 1 ParamNoInfo
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[64,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_break_with_label(x int, y int) {
|
||||
// presence of break with label should pessimize this func
|
||||
// (similar to goto).
|
||||
panic("bad")
|
||||
lab1:
|
||||
for {
|
||||
println("blah")
|
||||
if x < 0 {
|
||||
break lab1
|
||||
}
|
||||
panic("hubba")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_callsexit 268 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_callsexit(x int) {
|
||||
if x < 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// funcflags.go T_exitinexpr 281 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// callsite: funcflags.go:286:18|0 flagstr "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_exitinexpr(x int) {
|
||||
// This function does indeed unconditionally call exit, since the
|
||||
// first thing it does is invoke exprcallsexit, however from the
|
||||
// perspective of this function, the call is not at the statement
|
||||
// level, so we'll wind up missing it.
|
||||
if exprcallsexit(x) < 0 {
|
||||
println("foo")
|
||||
}
|
||||
}
|
||||
|
||||
// funcflags.go T_select_noreturn 297 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[0,0,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_select_noreturn(chi chan int, chf chan float32, p *int) {
|
||||
rv := 0
|
||||
select {
|
||||
case i := <-chi:
|
||||
rv = i
|
||||
case f := <-chf:
|
||||
rv = int(f)
|
||||
}
|
||||
*p = rv
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// funcflags.go T_select_mayreturn 314 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0,0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_select_mayreturn(chi chan int, chf chan float32, p *int) int {
|
||||
rv := 0
|
||||
select {
|
||||
case i := <-chi:
|
||||
rv = i
|
||||
return i
|
||||
case f := <-chf:
|
||||
rv = int(f)
|
||||
}
|
||||
*p = rv
|
||||
panic("bad")
|
||||
}
|
||||
|
||||
// funcflags.go T_calls_callsexit 334 0 1
|
||||
// Flags FuncPropNeverReturns
|
||||
// <endpropsdump>
|
||||
// {"Flags":1,"ParamFlags":[0],"ResultFlags":null}
|
||||
// callsite: funcflags.go:335:15|0 flagstr "CallSiteOnPanicPath" flagval 2 score 102 mask 1 maskstr "panicPathAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_calls_callsexit(x int) {
|
||||
exprcallsexit(x)
|
||||
}
|
||||
|
||||
func exprcallsexit(x int) int {
|
||||
os.Exit(x)
|
||||
return x
|
||||
}
|
||||
367
src/cmd/compile/internal/inline/inlheur/testdata/props/params.go
vendored
Normal file
367
src/cmd/compile/internal/inline/inlheur/testdata/props/params.go
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
package params
|
||||
|
||||
import "os"
|
||||
|
||||
// params.go T_feeds_if_simple 20 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_simple(x int) {
|
||||
if x < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_nested 35 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIfOrSwitch
|
||||
// 1 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[64,32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_nested(x, y int) {
|
||||
if y != 0 {
|
||||
if x < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_pointer 51 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_pointer(xp *int) {
|
||||
if xp != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(xp)
|
||||
}
|
||||
|
||||
// params.go T.T_feeds_if_simple_method 66 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// 1 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32,32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func (r T) T_feeds_if_simple_method(x int) {
|
||||
if x < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
if r != 99 {
|
||||
os.Exit(2)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_blanks 86 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamNoInfo
|
||||
// 1 ParamFeedsIfOrSwitch
|
||||
// 2 ParamNoInfo
|
||||
// 3 ParamNoInfo
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,32,0,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_blanks(_ string, x int, _ bool, _ bool) {
|
||||
// blanks ignored; from a props perspective "x" is param 0
|
||||
if x < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_with_copy 101 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_with_copy(x int) {
|
||||
// simple copy here -- we get this case
|
||||
xx := x
|
||||
if xx < 100 {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_with_copy_expr 115 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_with_copy_expr(x int) {
|
||||
// this case (copy of expression) currently not handled.
|
||||
xx := x < 100
|
||||
if xx {
|
||||
os.Exit(1)
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_switch 131 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_switch(x int) {
|
||||
switch x {
|
||||
case 101:
|
||||
println(101)
|
||||
case 202:
|
||||
panic("bad")
|
||||
}
|
||||
println(x)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_toocomplex 146 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_toocomplex(x int, y int) {
|
||||
// not handled at the moment; we only look for cases where
|
||||
// an "if" or "switch" can be simplified based on a single
|
||||
// constant param, not a combination of constant params.
|
||||
if x < y {
|
||||
panic("bad")
|
||||
}
|
||||
println(x + y)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_redefined 161 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_redefined(x int) {
|
||||
if x < G {
|
||||
x++
|
||||
}
|
||||
if x == 101 {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_redefined2 175 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_redefined2(x int) {
|
||||
// this currently classifies "x" as "no info", since the analysis we
|
||||
// use to check for reassignments/redefinitions is not flow-sensitive,
|
||||
// but we could probably catch this case with better analysis or
|
||||
// high-level SSA.
|
||||
if x == 101 {
|
||||
panic("bad")
|
||||
}
|
||||
if x < G {
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_multi_if 196 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// 1 ParamNoInfo
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32,0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_multi_if(x int, y int) {
|
||||
// Here we have one "if" that is too complex (x < y) but one that is
|
||||
// simple enough. Currently we enable the heuristic for this. It's
|
||||
// possible to imagine this being a bad thing if the function in
|
||||
// question is sufficiently large, but if it's too large we probably
|
||||
// can't inline it anyhow.
|
||||
if x < y {
|
||||
panic("bad")
|
||||
}
|
||||
if x < 10 {
|
||||
panic("whatev")
|
||||
}
|
||||
println(x + y)
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_redefined_indirectwrite 216 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_redefined_indirectwrite(x int) {
|
||||
ax := &x
|
||||
if G != 2 {
|
||||
*ax = G
|
||||
}
|
||||
if x == 101 {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_redefined_indirectwrite_copy 231 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_redefined_indirectwrite_copy(x int) {
|
||||
// we don't catch this case, "x" is marked as no info,
|
||||
// since we're conservative about redefinitions.
|
||||
ax := &x
|
||||
cx := x
|
||||
if G != 2 {
|
||||
*ax = G
|
||||
}
|
||||
if cx == 101 {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_expr1 251 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_expr1(x int) {
|
||||
if x == 101 || x == 102 || x&0xf == 0 {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_expr2 262 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_expr2(x int) {
|
||||
if (x*x)-(x+x)%x == 101 || x&0xf == 0 {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_expr3 273 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_expr3(x int) {
|
||||
if x-(x&0x1)^378 > (1 - G) {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_shift_may_panic 284 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_shift_may_panic(x int) *int {
|
||||
// here if "x" is a constant like 2, we could simplify the "if",
|
||||
// but if we were to pass in a negative value for "x" we can't
|
||||
// fold the condition due to the need to panic on negative shift.
|
||||
if 1<<x > 1024 {
|
||||
return nil
|
||||
}
|
||||
return &G
|
||||
}
|
||||
|
||||
// params.go T_feeds_if_maybe_divide_by_zero 299 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_if_maybe_divide_by_zero(x int) {
|
||||
if 99/x == 3 {
|
||||
return
|
||||
}
|
||||
println("blarg")
|
||||
}
|
||||
|
||||
// params.go T_feeds_indcall 313 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIndirectCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[16],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_indcall(x func()) {
|
||||
if G != 20 {
|
||||
x()
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_indcall_and_if 326 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamMayFeedIndirectCall|ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[48],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_indcall_and_if(x func()) {
|
||||
if x != nil {
|
||||
x()
|
||||
}
|
||||
}
|
||||
|
||||
// params.go T_feeds_indcall_with_copy 339 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIndirectCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[8],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_indcall_with_copy(x func()) {
|
||||
xx := x
|
||||
if G < 10 {
|
||||
G--
|
||||
}
|
||||
xx()
|
||||
}
|
||||
|
||||
// params.go T_feeds_interface_method_call 354 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsInterfaceMethodCall
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[2],"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_feeds_interface_method_call(i I) {
|
||||
i.Blarg()
|
||||
}
|
||||
|
||||
var G int
|
||||
|
||||
type T int
|
||||
|
||||
type I interface {
|
||||
Blarg()
|
||||
}
|
||||
|
||||
func (r T) Blarg() {
|
||||
}
|
||||
370
src/cmd/compile/internal/inline/inlheur/testdata/props/returns.go
vendored
Normal file
370
src/cmd/compile/internal/inline/inlheur/testdata/props/returns.go
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
|
||||
package returns1
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// returns.go T_simple_allocmem 21 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsAllocatedMem
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[2]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_simple_allocmem() *Bar {
|
||||
return &Bar{}
|
||||
}
|
||||
|
||||
// returns.go T_allocmem_two_returns 34 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// ResultFlags
|
||||
// 0 ResultIsAllocatedMem
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[2]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_allocmem_two_returns(x int) *Bar {
|
||||
// multiple returns
|
||||
if x < 0 {
|
||||
return new(Bar)
|
||||
} else {
|
||||
return &Bar{x: 2}
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_allocmem_three_returns 52 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// ResultFlags
|
||||
// 0 ResultIsAllocatedMem
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[2]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_allocmem_three_returns(x int) []*Bar {
|
||||
// more multiple returns
|
||||
switch x {
|
||||
case 10, 11, 12:
|
||||
return make([]*Bar, 10)
|
||||
case 13:
|
||||
fallthrough
|
||||
case 15:
|
||||
return []*Bar{&Bar{x: 15}}
|
||||
}
|
||||
return make([]*Bar, 0, 10)
|
||||
}
|
||||
|
||||
// returns.go T_return_nil 72 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameConstant
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[8]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_nil() *Bar {
|
||||
// simple case: no alloc
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns.go T_multi_return_nil 84 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameConstant
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[8]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_nil(x, y bool) *Bar {
|
||||
if x && y {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns.go T_multi_return_nil_anomoly 98 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsConcreteTypeConvertedToInterface
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[4]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_nil_anomoly(x, y bool) Itf {
|
||||
if x && y {
|
||||
var qnil *Q
|
||||
return qnil
|
||||
}
|
||||
var barnil *Bar
|
||||
return barnil
|
||||
}
|
||||
|
||||
// returns.go T_multi_return_some_nil 112 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_some_nil(x, y bool) *Bar {
|
||||
if x && y {
|
||||
return nil
|
||||
} else {
|
||||
return &GB
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_mixed_returns 127 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_mixed_returns(x int) *Bar {
|
||||
// mix of alloc and non-alloc
|
||||
if x < 0 {
|
||||
return new(Bar)
|
||||
} else {
|
||||
return &GB
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_mixed_returns_slice 143 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_mixed_returns_slice(x int) []*Bar {
|
||||
// mix of alloc and non-alloc
|
||||
switch x {
|
||||
case 10, 11, 12:
|
||||
return make([]*Bar, 10)
|
||||
case 13:
|
||||
fallthrough
|
||||
case 15:
|
||||
return []*Bar{&Bar{x: 15}}
|
||||
}
|
||||
ba := [...]*Bar{&GB, &GB}
|
||||
return ba[:]
|
||||
}
|
||||
|
||||
// returns.go T_maps_and_channels 167 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultNoInfo
|
||||
// 1 ResultNoInfo
|
||||
// 2 ResultNoInfo
|
||||
// 3 ResultAlwaysSameConstant
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[0,0,0,8]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_maps_and_channels(x int, b bool) (bool, map[int]int, chan bool, unsafe.Pointer) {
|
||||
// maps and channels
|
||||
return b, make(map[int]int), make(chan bool), nil
|
||||
}
|
||||
|
||||
// returns.go T_assignment_to_named_returns 179 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[0,0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_assignment_to_named_returns(x int) (r1 *uint64, r2 *uint64) {
|
||||
// assignments to named returns and then "return" not supported
|
||||
r1 = new(uint64)
|
||||
if x < 1 {
|
||||
*r1 = 2
|
||||
}
|
||||
r2 = new(uint64)
|
||||
return
|
||||
}
|
||||
|
||||
// returns.go T_named_returns_but_return_explicit_values 199 0 1
|
||||
// ParamFlags
|
||||
// 0 ParamFeedsIfOrSwitch
|
||||
// ResultFlags
|
||||
// 0 ResultIsAllocatedMem
|
||||
// 1 ResultIsAllocatedMem
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[32],"ResultFlags":[2,2]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_named_returns_but_return_explicit_values(x int) (r1 *uint64, r2 *uint64) {
|
||||
// named returns ok if all returns are non-empty
|
||||
rx1 := new(uint64)
|
||||
if x < 1 {
|
||||
*rx1 = 2
|
||||
}
|
||||
rx2 := new(uint64)
|
||||
return rx1, rx2
|
||||
}
|
||||
|
||||
// returns.go T_return_concrete_type_to_itf 216 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsConcreteTypeConvertedToInterface
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[4]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_concrete_type_to_itf(x, y int) Itf {
|
||||
return &Bar{}
|
||||
}
|
||||
|
||||
// returns.go T_return_concrete_type_to_itfwith_copy 227 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultIsConcreteTypeConvertedToInterface
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[4]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_concrete_type_to_itfwith_copy(x, y int) Itf {
|
||||
b := &Bar{}
|
||||
println("whee")
|
||||
return b
|
||||
}
|
||||
|
||||
// returns.go T_return_concrete_type_to_itf_mixed 238 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_concrete_type_to_itf_mixed(x, y int) Itf {
|
||||
if x < y {
|
||||
b := &Bar{}
|
||||
return b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns.go T_return_same_func 253 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameInlinableFunc
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[32]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_same_func() func(int) int {
|
||||
if G < 10 {
|
||||
return foo
|
||||
} else {
|
||||
return foo
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_return_different_funcs 266 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_different_funcs() func(int) int {
|
||||
if G != 10 {
|
||||
return foo
|
||||
} else {
|
||||
return bar
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_return_same_closure 286 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameInlinableFunc
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[32]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// returns.go T_return_same_closure.func1 287 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_same_closure() func(int) int {
|
||||
p := func(q int) int { return q }
|
||||
if G < 10 {
|
||||
return p
|
||||
} else {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_return_different_closures 312 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// returns.go T_return_different_closures.func1 313 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// returns.go T_return_different_closures.func2 317 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameConstant
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[8]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_different_closures() func(int) int {
|
||||
p := func(q int) int { return q }
|
||||
if G < 10 {
|
||||
return p
|
||||
} else {
|
||||
return func(q int) int { return 101 }
|
||||
}
|
||||
}
|
||||
|
||||
// returns.go T_return_noninlinable 339 0 1
|
||||
// ResultFlags
|
||||
// 0 ResultAlwaysSameFunc
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[16]}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// returns.go T_return_noninlinable.func1 340 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: returns.go:343:4|0 flagstr "" flagval 0 score 4 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
// returns.go T_return_noninlinable.func1.1 341 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_noninlinable(x int) func(int) int {
|
||||
noti := func(q int) int {
|
||||
defer func() {
|
||||
println(q + x)
|
||||
}()
|
||||
return q
|
||||
}
|
||||
return noti
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
x int
|
||||
y string
|
||||
}
|
||||
|
||||
func (b *Bar) Plark() {
|
||||
}
|
||||
|
||||
type Q int
|
||||
|
||||
func (q *Q) Plark() {
|
||||
}
|
||||
|
||||
func foo(x int) int { return x }
|
||||
func bar(x int) int { return -x }
|
||||
|
||||
var G int
|
||||
var GB Bar
|
||||
|
||||
type Itf interface {
|
||||
Plark()
|
||||
}
|
||||
231
src/cmd/compile/internal/inline/inlheur/testdata/props/returns2.go
vendored
Normal file
231
src/cmd/compile/internal/inline/inlheur/testdata/props/returns2.go
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
// 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.
|
||||
|
||||
// DO NOT EDIT (use 'go test -v -update-expected' instead.)
|
||||
// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt
|
||||
// for more information on the format of this file.
|
||||
// <endfilepreamble>
|
||||
|
||||
package returns2
|
||||
|
||||
// returns2.go T_return_feeds_iface_call 18 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
|
||||
// callsite: returns2.go:19:13|0 flagstr "" flagval 0 score 1 mask 16384 maskstr "returnFeedsConcreteToInterfaceCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_feeds_iface_call() {
|
||||
b := newBar(10)
|
||||
b.Plark()
|
||||
}
|
||||
|
||||
// returns2.go T_multi_return_feeds_iface_call 29 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":null,"ResultFlags":null}
|
||||
// callsite: returns2.go:30:20|0 flagstr "" flagval 0 score 3 mask 16384 maskstr "returnFeedsConcreteToInterfaceCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_feeds_iface_call() {
|
||||
_, b, _ := newBar2(10)
|
||||
b.Plark()
|
||||
}
|
||||
|
||||
// returns2.go T_returned_inlinable_func_feeds_indirect_call 41 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// callsite: returns2.go:42:18|0 flagstr "" flagval 0 score -51 mask 8200 maskstr "passConstToIfAdj|returnFeedsInlinableFuncToIndCallAdj"
|
||||
// callsite: returns2.go:44:20|1 flagstr "" flagval 0 score -23 mask 8192 maskstr "returnFeedsInlinableFuncToIndCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_returned_inlinable_func_feeds_indirect_call(q int) {
|
||||
f := returnsFunc(10)
|
||||
f(q)
|
||||
f2 := returnsFunc2()
|
||||
f2(q)
|
||||
}
|
||||
|
||||
// returns2.go T_returned_noninlineable_func_feeds_indirect_call 54 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// callsite: returns2.go:55:30|0 flagstr "" flagval 0 score -23 mask 4096 maskstr "returnFeedsFuncToIndCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_returned_noninlineable_func_feeds_indirect_call(q int) {
|
||||
f := returnsNonInlinableFunc()
|
||||
f(q)
|
||||
}
|
||||
|
||||
// returns2.go T_multi_return_feeds_indirect_call 65 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":null}
|
||||
// callsite: returns2.go:66:29|0 flagstr "" flagval 0 score -21 mask 8192 maskstr "returnFeedsInlinableFuncToIndCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_feeds_indirect_call(q int) {
|
||||
_, f, _ := multiReturnsFunc()
|
||||
f(q)
|
||||
}
|
||||
|
||||
// returns2.go T_return_feeds_ifswitch 76 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: returns2.go:77:14|0 flagstr "" flagval 0 score 10 mask 2048 maskstr "returnFeedsConstToIfAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_return_feeds_ifswitch(q int) int {
|
||||
x := meaning(q)
|
||||
if x < 42 {
|
||||
switch x {
|
||||
case 42:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// returns2.go T_multi_return_feeds_ifswitch 93 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: returns2.go:94:21|0 flagstr "" flagval 0 score 9 mask 2048 maskstr "returnFeedsConstToIfAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_multi_return_feeds_ifswitch(q int) int {
|
||||
x, y, z := meanings(q)
|
||||
if x < y {
|
||||
switch x {
|
||||
case 42:
|
||||
return z
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// returns2.go T_two_calls_feed_ifswitch 111 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0],"ResultFlags":[0]}
|
||||
// callsite: returns2.go:115:14|0 flagstr "" flagval 0 score 25 mask 0 maskstr ""
|
||||
// callsite: returns2.go:116:14|1 flagstr "" flagval 0 score 25 mask 0 maskstr ""
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_two_calls_feed_ifswitch(q int) int {
|
||||
// This case we don't handle; for the heuristic to kick in,
|
||||
// all names in a given if/switch cond have to come from the
|
||||
// same callsite
|
||||
x := meaning(q)
|
||||
y := meaning(-q)
|
||||
if x < y {
|
||||
switch x + y {
|
||||
case 42:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// returns2.go T_chained_indirect_call 132 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// callsite: returns2.go:135:18|0 flagstr "" flagval 0 score -31 mask 8192 maskstr "returnFeedsInlinableFuncToIndCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_chained_indirect_call(x, y int) {
|
||||
// Here 'returnsFunc' returns an inlinable func that feeds
|
||||
// directly into a call (no named intermediate).
|
||||
G += returnsFunc(x - y)(x + y)
|
||||
}
|
||||
|
||||
// returns2.go T_chained_conc_iface_call 144 0 1
|
||||
// <endpropsdump>
|
||||
// {"Flags":0,"ParamFlags":[0,0],"ResultFlags":null}
|
||||
// callsite: returns2.go:148:8|0 flagstr "" flagval 0 score 1 mask 16384 maskstr "returnFeedsConcreteToInterfaceCallAdj"
|
||||
// <endcallsites>
|
||||
// <endfuncpreamble>
|
||||
func T_chained_conc_iface_call(x, y int) {
|
||||
// Similar to the case above, return from call returning concrete type
|
||||
// feeds directly into interface call. Note that only the first
|
||||
// iface call is interesting here.
|
||||
newBar(10).Plark().Plark()
|
||||
}
|
||||
|
||||
func returnsFunc(x int) func(int) int {
|
||||
if x < 0 {
|
||||
G++
|
||||
}
|
||||
return adder
|
||||
}
|
||||
|
||||
func returnsFunc2() func(int) int {
|
||||
return func(x int) int {
|
||||
return adder(x)
|
||||
}
|
||||
}
|
||||
|
||||
func returnsNonInlinableFunc() func(int) int {
|
||||
return adderNoInline
|
||||
}
|
||||
|
||||
func multiReturnsFunc() (int, func(int) int, int) {
|
||||
return 42, func(x int) int { G++; return 1 }, -42
|
||||
}
|
||||
|
||||
func adder(x int) int {
|
||||
G += 1
|
||||
return G
|
||||
}
|
||||
|
||||
func adderNoInline(x int) int {
|
||||
defer func() { G += x }()
|
||||
G += 1
|
||||
return G
|
||||
}
|
||||
|
||||
func meaning(q int) int {
|
||||
r := 0
|
||||
for i := 0; i < 42; i++ {
|
||||
r += q
|
||||
}
|
||||
G += r
|
||||
return 42
|
||||
}
|
||||
|
||||
func meanings(q int) (int, int, int) {
|
||||
r := 0
|
||||
for i := 0; i < 42; i++ {
|
||||
r += q
|
||||
}
|
||||
return 42, 43, r
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
x int
|
||||
y string
|
||||
}
|
||||
|
||||
func (b *Bar) Plark() Itf {
|
||||
return b
|
||||
}
|
||||
|
||||
type Itf interface {
|
||||
Plark() Itf
|
||||
}
|
||||
|
||||
func newBar(x int) Itf {
|
||||
s := 0
|
||||
for i := 0; i < x; i++ {
|
||||
s += i
|
||||
}
|
||||
return &Bar{
|
||||
x: s,
|
||||
}
|
||||
}
|
||||
|
||||
func newBar2(x int) (int, Itf, bool) {
|
||||
s := 0
|
||||
for i := 0; i < x; i++ {
|
||||
s += i
|
||||
}
|
||||
return 0, &Bar{x: s}, false
|
||||
}
|
||||
|
||||
var G int
|
||||
222
src/cmd/compile/internal/inline/inlheur/texpr_classify_test.go
Normal file
222
src/cmd/compile/internal/inline/inlheur/texpr_classify_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// 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/base"
|
||||
"cmd/compile/internal/ir"
|
||||
"cmd/compile/internal/typecheck"
|
||||
"cmd/compile/internal/types"
|
||||
"cmd/internal/obj"
|
||||
"cmd/internal/src"
|
||||
"cmd/internal/sys"
|
||||
"go/constant"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pos src.XPos
|
||||
var local *types.Pkg
|
||||
var f *ir.Func
|
||||
|
||||
func init() {
|
||||
types.PtrSize = 8
|
||||
types.RegSize = 8
|
||||
types.MaxWidth = 1 << 50
|
||||
base.Ctxt = &obj.Link{Arch: &obj.LinkArch{Arch: &sys.Arch{Alignment: 1, CanMergeLoads: true}}}
|
||||
|
||||
typecheck.InitUniverse()
|
||||
local = types.NewPkg("", "")
|
||||
fsym := &types.Sym{
|
||||
Pkg: types.NewPkg("my/import/path", "path"),
|
||||
Name: "function",
|
||||
}
|
||||
f = ir.NewFunc(src.NoXPos, src.NoXPos, fsym, nil)
|
||||
}
|
||||
|
||||
type state struct {
|
||||
ntab map[string]*ir.Name
|
||||
}
|
||||
|
||||
func mkstate() *state {
|
||||
return &state{
|
||||
ntab: make(map[string]*ir.Name),
|
||||
}
|
||||
}
|
||||
|
||||
func bin(x ir.Node, op ir.Op, y ir.Node) ir.Node {
|
||||
return ir.NewBinaryExpr(pos, op, x, y)
|
||||
}
|
||||
|
||||
func conv(x ir.Node, t *types.Type) ir.Node {
|
||||
return ir.NewConvExpr(pos, ir.OCONV, t, x)
|
||||
}
|
||||
|
||||
func logical(x ir.Node, op ir.Op, y ir.Node) ir.Node {
|
||||
return ir.NewLogicalExpr(pos, op, x, y)
|
||||
}
|
||||
|
||||
func un(op ir.Op, x ir.Node) ir.Node {
|
||||
return ir.NewUnaryExpr(pos, op, x)
|
||||
}
|
||||
|
||||
func liti(i int64) ir.Node {
|
||||
return ir.NewBasicLit(pos, types.Types[types.TINT64], constant.MakeInt64(i))
|
||||
}
|
||||
|
||||
func lits(s string) ir.Node {
|
||||
return ir.NewBasicLit(pos, types.Types[types.TSTRING], constant.MakeString(s))
|
||||
}
|
||||
|
||||
func (s *state) nm(name string, t *types.Type) *ir.Name {
|
||||
if n, ok := s.ntab[name]; ok {
|
||||
if n.Type() != t {
|
||||
panic("bad")
|
||||
}
|
||||
return n
|
||||
}
|
||||
sym := local.Lookup(name)
|
||||
nn := ir.NewNameAt(pos, sym, t)
|
||||
s.ntab[name] = nn
|
||||
return nn
|
||||
}
|
||||
|
||||
func (s *state) nmi64(name string) *ir.Name {
|
||||
return s.nm(name, types.Types[types.TINT64])
|
||||
}
|
||||
|
||||
func (s *state) nms(name string) *ir.Name {
|
||||
return s.nm(name, types.Types[types.TSTRING])
|
||||
}
|
||||
|
||||
func TestClassifyIntegerCompare(t *testing.T) {
|
||||
|
||||
// (n < 10 || n > 100) && (n >= 12 || n <= 99 || n != 101)
|
||||
s := mkstate()
|
||||
nn := s.nmi64("n")
|
||||
nlt10 := bin(nn, ir.OLT, liti(10)) // n < 10
|
||||
ngt100 := bin(nn, ir.OGT, liti(100)) // n > 100
|
||||
nge12 := bin(nn, ir.OGE, liti(12)) // n >= 12
|
||||
nle99 := bin(nn, ir.OLE, liti(99)) // n < 10
|
||||
nne101 := bin(nn, ir.ONE, liti(101)) // n != 101
|
||||
noror1 := logical(nlt10, ir.OOROR, ngt100) // n < 10 || n > 100
|
||||
noror2 := logical(nge12, ir.OOROR, nle99) // n >= 12 || n <= 99
|
||||
noror3 := logical(noror2, ir.OOROR, nne101)
|
||||
nandand := typecheck.Expr(logical(noror1, ir.OANDAND, noror3))
|
||||
|
||||
wantv := true
|
||||
v := ShouldFoldIfNameConstant(nandand, []*ir.Name{nn})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", nandand, wantv, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyStringCompare(t *testing.T) {
|
||||
|
||||
// s != "foo" && s < "ooblek" && s > "plarkish"
|
||||
s := mkstate()
|
||||
nn := s.nms("s")
|
||||
snefoo := bin(nn, ir.ONE, lits("foo")) // s != "foo"
|
||||
sltoob := bin(nn, ir.OLT, lits("ooblek")) // s < "ooblek"
|
||||
sgtpk := bin(nn, ir.OGT, lits("plarkish")) // s > "plarkish"
|
||||
nandand := logical(snefoo, ir.OANDAND, sltoob)
|
||||
top := typecheck.Expr(logical(nandand, ir.OANDAND, sgtpk))
|
||||
|
||||
wantv := true
|
||||
v := ShouldFoldIfNameConstant(top, []*ir.Name{nn})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", top, wantv, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyIntegerArith(t *testing.T) {
|
||||
// n+1 ^ n-3 * n/2 + n<<9 + n>>2 - n&^7
|
||||
|
||||
s := mkstate()
|
||||
nn := s.nmi64("n")
|
||||
np1 := bin(nn, ir.OADD, liti(1)) // n+1
|
||||
nm3 := bin(nn, ir.OSUB, liti(3)) // n-3
|
||||
nd2 := bin(nn, ir.ODIV, liti(2)) // n/2
|
||||
nls9 := bin(nn, ir.OLSH, liti(9)) // n<<9
|
||||
nrs2 := bin(nn, ir.ORSH, liti(2)) // n>>2
|
||||
nan7 := bin(nn, ir.OANDNOT, liti(7)) // n&^7
|
||||
c1xor := bin(np1, ir.OXOR, nm3)
|
||||
c2mul := bin(c1xor, ir.OMUL, nd2)
|
||||
c3add := bin(c2mul, ir.OADD, nls9)
|
||||
c4add := bin(c3add, ir.OADD, nrs2)
|
||||
c5sub := bin(c4add, ir.OSUB, nan7)
|
||||
top := typecheck.Expr(c5sub)
|
||||
|
||||
wantv := true
|
||||
v := ShouldFoldIfNameConstant(top, []*ir.Name{nn})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", top, wantv, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyAssortedShifts(t *testing.T) {
|
||||
|
||||
s := mkstate()
|
||||
nn := s.nmi64("n")
|
||||
badcases := []ir.Node{
|
||||
bin(liti(3), ir.OLSH, nn), // 3<<n
|
||||
bin(liti(7), ir.ORSH, nn), // 7>>n
|
||||
}
|
||||
for _, bc := range badcases {
|
||||
wantv := false
|
||||
v := ShouldFoldIfNameConstant(typecheck.Expr(bc), []*ir.Name{nn})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", bc, wantv, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyFloat(t *testing.T) {
|
||||
// float32(n) + float32(10)
|
||||
s := mkstate()
|
||||
nn := s.nm("n", types.Types[types.TUINT32])
|
||||
f1 := conv(nn, types.Types[types.TFLOAT32])
|
||||
f2 := conv(liti(10), types.Types[types.TFLOAT32])
|
||||
add := bin(f1, ir.OADD, f2)
|
||||
|
||||
wantv := false
|
||||
v := ShouldFoldIfNameConstant(typecheck.Expr(add), []*ir.Name{nn})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", add, wantv, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleNamesAllUsed(t *testing.T) {
|
||||
// n != 101 && m < 2
|
||||
s := mkstate()
|
||||
nn := s.nmi64("n")
|
||||
nm := s.nmi64("m")
|
||||
nne101 := bin(nn, ir.ONE, liti(101)) // n != 101
|
||||
mlt2 := bin(nm, ir.OLT, liti(2)) // m < 2
|
||||
nandand := typecheck.Expr(logical(nne101, ir.OANDAND, mlt2))
|
||||
|
||||
// all names used
|
||||
wantv := true
|
||||
v := ShouldFoldIfNameConstant(nandand, []*ir.Name{nn, nm})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", nandand, wantv, v)
|
||||
}
|
||||
|
||||
// not all names used
|
||||
wantv = false
|
||||
v = ShouldFoldIfNameConstant(nne101, []*ir.Name{nn, nm})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", nne101, wantv, v)
|
||||
}
|
||||
|
||||
// other names used.
|
||||
np := s.nmi64("p")
|
||||
pne0 := bin(np, ir.ONE, liti(101)) // p != 0
|
||||
noror := logical(nandand, ir.OOROR, pne0)
|
||||
wantv = false
|
||||
v = ShouldFoldIfNameConstant(noror, []*ir.Name{nn, nm})
|
||||
if v != wantv {
|
||||
t.Errorf("wanted shouldfold(%v) %v, got %v", noror, wantv, v)
|
||||
}
|
||||
}
|
||||
18
src/cmd/compile/internal/inline/inlheur/trace_off.go
Normal file
18
src/cmd/compile/internal/inline/inlheur/trace_off.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
//go:build !debugtrace
|
||||
|
||||
package inlheur
|
||||
|
||||
const debugTrace = 0
|
||||
|
||||
func enableDebugTrace(x int) {
|
||||
}
|
||||
|
||||
func enableDebugTraceIfEnv() {
|
||||
}
|
||||
|
||||
func disableDebugTrace() {
|
||||
}
|
||||
40
src/cmd/compile/internal/inline/inlheur/trace_on.go
Normal file
40
src/cmd/compile/internal/inline/inlheur/trace_on.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
//go:build debugtrace
|
||||
|
||||
package inlheur
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var debugTrace = 0
|
||||
|
||||
func enableDebugTrace(x int) {
|
||||
debugTrace = x
|
||||
}
|
||||
|
||||
func enableDebugTraceIfEnv() {
|
||||
v := os.Getenv("DEBUG_TRACE_INLHEUR")
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
if v[0] == '*' {
|
||||
if !UnitTesting() {
|
||||
return
|
||||
}
|
||||
v = v[1:]
|
||||
}
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
debugTrace = i
|
||||
}
|
||||
|
||||
func disableDebugTrace() {
|
||||
debugTrace = 0
|
||||
}
|
||||
65
src/cmd/compile/internal/inline/inlheur/tserial_test.go
Normal file
65
src/cmd/compile/internal/inline/inlheur/tserial_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 "testing"
|
||||
|
||||
func fpeq(fp1, fp2 FuncProps) bool {
|
||||
if fp1.Flags != fp2.Flags {
|
||||
return false
|
||||
}
|
||||
if len(fp1.ParamFlags) != len(fp2.ParamFlags) {
|
||||
return false
|
||||
}
|
||||
for i := range fp1.ParamFlags {
|
||||
if fp1.ParamFlags[i] != fp2.ParamFlags[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(fp1.ResultFlags) != len(fp2.ResultFlags) {
|
||||
return false
|
||||
}
|
||||
for i := range fp1.ResultFlags {
|
||||
if fp1.ResultFlags[i] != fp2.ResultFlags[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestSerDeser(t *testing.T) {
|
||||
testcases := []FuncProps{
|
||||
FuncProps{},
|
||||
FuncProps{
|
||||
Flags: 0xfffff,
|
||||
},
|
||||
FuncProps{
|
||||
Flags: 1,
|
||||
ResultFlags: []ResultPropBits{ResultAlwaysSameConstant},
|
||||
},
|
||||
FuncProps{
|
||||
Flags: 1,
|
||||
ParamFlags: []ParamPropBits{0x99, 0xaa, 0xfffff},
|
||||
ResultFlags: []ResultPropBits{0xfeedface},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
s := tc.SerializeToString()
|
||||
fp := DeserializeFromString(s)
|
||||
got := fp.String()
|
||||
want := tc.String()
|
||||
if !fpeq(*fp, tc) {
|
||||
t.Errorf("eq check failed for test %d: got:\n%s\nwant:\n%s\n", k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
var nilt *FuncProps
|
||||
ns := nilt.SerializeToString()
|
||||
nfp := DeserializeFromString(ns)
|
||||
if len(ns) != 0 || nfp != nil {
|
||||
t.Errorf("nil serialize/deserialize failed")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user