1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
8 "cmd/compile/internal/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/types"
13 "internal/goexperiment"
22 debugTraceFuncs = 1 << iota
26 debugTraceExprClassify
31 // propAnalyzer interface is used for defining one or more analyzer
32 // helper objects, each tasked with computing some specific subset of
33 // the properties we're interested in. The assumption is that
34 // properties are independent, so each new analyzer that implements
35 // this interface can operate entirely on its own. For a given analyzer
36 // there will be a sequence of calls to nodeVisitPre and nodeVisitPost
37 // as the nodes within a function are visited, then a followup call to
38 // setResults so that the analyzer can transfer its results into the
39 // final properties object.
40 type propAnalyzer interface {
41 nodeVisitPre(n ir.Node)
42 nodeVisitPost(n ir.Node)
43 setResults(fp *FuncProps)
46 // fnInlHeur contains inline heuristics state information about a
47 // specific Go function being analyzed/considered by the inliner. Note
48 // that in addition to constructing a fnInlHeur object by analyzing a
49 // specific *ir.Func, there is also code in the test harness
50 // (funcprops_test.go) that builds up fnInlHeur's by reading in and
51 // parsing a dump. This is the reason why we have file/fname/line
52 // fields below instead of just an *ir.Func field.
53 type fnInlHeur struct {
61 var fpmap = map[*ir.Func]fnInlHeur{}
63 func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func)) *FuncProps {
64 if fih, ok := fpmap[fn]; ok {
67 fp, fcstab := computeFuncProps(fn, canInline)
68 file, line := fnFileLine(fn)
76 // Merge this functions call sites into the package level table.
77 if err := cstab.merge(fcstab); err != nil {
78 base.FatalfAt(fn.Pos(), "%v", err)
80 fn.SetNeverReturns(entry.props.Flags&FuncPropNeverReturns != 0)
82 if fn.Inl != nil && fn.Inl.Properties == "" {
83 fn.Inl.Properties = entry.props.SerializeToString()
88 // computeFuncProps examines the Go function 'fn' and computes for it
89 // a function "properties" object, to be used to drive inlining
90 // heuristics. See comments on the FuncProps type for more info.
91 func computeFuncProps(fn *ir.Func, canInline func(*ir.Func)) (*FuncProps, CallSiteTab) {
92 enableDebugTraceIfEnv()
93 if debugTrace&debugTraceFuncs != 0 {
94 fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
97 ra := makeResultsAnalyzer(fn, canInline)
98 pa := makeParamsAnalyzer(fn)
99 ffa := makeFuncFlagsAnalyzer(fn)
100 analyzers := []propAnalyzer{ffa, ra, pa}
102 runAnalyzersOnFunction(fn, analyzers)
103 for _, a := range analyzers {
106 // Now build up a partial table of callsites for this func.
107 cstab := computeCallSiteTable(fn, ffa.panicPathTable())
112 func runAnalyzersOnFunction(fn *ir.Func, analyzers []propAnalyzer) {
113 var doNode func(ir.Node) bool
114 doNode = func(n ir.Node) bool {
115 for _, a := range analyzers {
118 ir.DoChildren(n, doNode)
119 for _, a := range analyzers {
127 func propsForFunc(fn *ir.Func) *FuncProps {
128 if fih, ok := fpmap[fn]; ok {
130 } else if fn.Inl != nil && fn.Inl.Properties != "" {
131 // FIXME: considering adding some sort of cache or table
132 // for deserialized properties of imported functions.
133 return DeserializeFromString(fn.Inl.Properties)
138 func fnFileLine(fn *ir.Func) (string, uint) {
139 p := base.Ctxt.InnermostPos(fn.Pos())
140 return filepath.Base(p.Filename()), p.Line()
143 func UnitTesting() bool {
144 return base.Debug.DumpInlFuncProps != ""
147 // DumpFuncProps computes and caches function properties for the func
148 // 'fn' and any closures it contains, or if fn is nil, it writes out the
149 // cached set of properties to the file given in 'dumpfile'. Used for
150 // the "-d=dumpinlfuncprops=..." command line flag, intended for use
151 // primarily in unit testing.
152 func DumpFuncProps(fn *ir.Func, dumpfile string, canInline func(*ir.Func)) {
154 enableDebugTraceIfEnv()
155 dmp := func(fn *ir.Func) {
156 if !goexperiment.NewInliner {
159 captureFuncDumpEntry(fn, canInline)
161 captureFuncDumpEntry(fn, canInline)
163 ir.Visit(fn, func(n ir.Node) {
164 if clo, ok := n.(*ir.ClosureExpr); ok {
170 emitDumpToFile(dumpfile)
174 // emitDumpToFile writes out the buffer function property dump entries
175 // to a file, for unit testing. Dump entries need to be sorted by
176 // definition line, and due to generics we need to account for the
177 // possibility that several ir.Func's will have the same def line.
178 func emitDumpToFile(dumpfile string) {
179 mode := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
180 if dumpfile[0] == '+' {
181 dumpfile = dumpfile[1:]
182 mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
184 if dumpfile[0] == '%' {
185 dumpfile = dumpfile[1:]
186 d, b := filepath.Dir(dumpfile), filepath.Base(dumpfile)
187 ptag := strings.ReplaceAll(types.LocalPkg.Path, "/", ":")
188 dumpfile = d + "/" + ptag + "." + b
190 outf, err := os.OpenFile(dumpfile, mode, 0644)
192 base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
195 dumpFilePreamble(outf)
197 atline := map[uint]uint{}
198 sl := make([]fnInlHeur, 0, len(dumpBuffer))
199 for _, e := range dumpBuffer {
201 atline[e.line] = atline[e.line] + 1
203 sl = sortFnInlHeurSlice(sl)
206 for _, entry := range sl {
208 if prevline == entry.line {
211 prevline = entry.line
212 atl := atline[entry.line]
213 if err := dumpFnPreamble(outf, &entry, nil, idx, atl); err != nil {
214 base.Fatalf("function props dump: %v\n", err)
220 // captureFuncDumpEntry grabs the function properties object for 'fn'
221 // and enqueues it for later dumping. Used for the
222 // "-d=dumpinlfuncprops=..." command line flag, intended for use
223 // primarily in unit testing.
224 func captureFuncDumpEntry(fn *ir.Func, canInline func(*ir.Func)) {
225 // avoid capturing compiler-generated equality funcs.
226 if strings.HasPrefix(fn.Sym().Name, ".eq.") {
230 // Props object should already be present, unless this is a
231 // directly recursive routine.
233 AnalyzeFunc(fn, canInline)
235 if fn.Inl != nil && fn.Inl.Properties == "" {
236 fn.Inl.Properties = fih.props.SerializeToString()
239 if dumpBuffer == nil {
240 dumpBuffer = make(map[*ir.Func]fnInlHeur)
242 if _, ok := dumpBuffer[fn]; ok {
243 // we can wind up seeing closures multiple times here,
244 // so don't add them more than once.
247 if debugTrace&debugTraceFuncs != 0 {
248 fmt.Fprintf(os.Stderr, "=-= capturing dump for %v:\n", fn)
253 // dumpFilePreamble writes out a file-level preamble for a given
254 // Go function as part of a function properties dump.
255 func dumpFilePreamble(w io.Writer) {
256 fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
257 fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
258 fmt.Fprintf(w, "// for more information on the format of this file.\n")
259 fmt.Fprintf(w, "// %s\n", preambleDelimiter)
262 // dumpFnPreamble writes out a function-level preamble for a given
263 // Go function as part of a function properties dump. See the
264 // README.txt file in testdata/props for more on the format of
266 func dumpFnPreamble(w io.Writer, fih *fnInlHeur, ecst encodedCallSiteTab, idx, atl uint) error {
267 fmt.Fprintf(w, "// %s %s %d %d %d\n",
268 fih.file, fih.fname, fih.line, idx, atl)
269 // emit props as comments, followed by delimiter
270 fmt.Fprintf(w, "%s// %s\n", fih.props.ToString("// "), comDelimiter)
271 data, err := json.Marshal(fih.props)
273 return fmt.Errorf("marshall error %v\n", err)
275 fmt.Fprintf(w, "// %s\n", string(data))
276 dumpCallSiteComments(w, fih.cstab, ecst)
277 fmt.Fprintf(w, "// %s\n", fnDelimiter)
281 // sortFnInlHeurSlice sorts a slice of fnInlHeur based on
282 // the starting line of the function definition, then by name.
283 func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
284 sort.SliceStable(sl, func(i, j int) bool {
285 if sl[i].line != sl[j].line {
286 return sl[i].line < sl[j].line
288 return sl[i].fname < sl[j].fname
293 // delimiters written to various preambles to make parsing of
295 const preambleDelimiter = "<endfilepreamble>"
296 const fnDelimiter = "<endfuncpreamble>"
297 const comDelimiter = "<endpropsdump>"
298 const csDelimiter = "<endcallsites>"
300 // dumpBuffer stores up function properties dumps when
301 // "-d=dumpinlfuncprops=..." is in effect.
302 var dumpBuffer map[*ir.Func]fnInlHeur