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"
20 debugTraceFuncs = 1 << iota
24 // propAnalyzer interface is used for defining one or more analyzer
25 // helper objects, each tasked with computing some specific subset of
26 // the properties we're interested in. The assumption is that
27 // properties are independent, so each new analyzer that implements
28 // this interface can operate entirely on its own. For a given analyzer
29 // there will be a sequence of calls to nodeVisitPre and nodeVisitPost
30 // as the nodes within a function are visited, then a followup call to
31 // setResults so that the analyzer can transfer its results into the
32 // final properties object.
33 type propAnalyzer interface {
34 nodeVisitPre(n ir.Node)
35 nodeVisitPost(n ir.Node)
36 setResults(fp *FuncProps)
39 // fnInlHeur contains inline heuristics state information about
40 // a specific Go function being analyzed/considered by the inliner.
41 type fnInlHeur struct {
48 // computeFuncProps examines the Go function 'fn' and computes for it
49 // a function "properties" object, to be used to drive inlining
50 // heuristics. See comments on the FuncProps type for more info.
51 func computeFuncProps(fn *ir.Func) *FuncProps {
52 if debugTrace&debugTraceFuncs != 0 {
53 fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
56 ffa := makeFuncFlagsAnalyzer(fn)
57 analyzers := []propAnalyzer{ffa}
59 runAnalyzersOnFunction(fn, analyzers)
60 for _, a := range analyzers {
66 func runAnalyzersOnFunction(fn *ir.Func, analyzers []propAnalyzer) {
67 var doNode func(ir.Node) bool
68 doNode = func(n ir.Node) bool {
69 for _, a := range analyzers {
72 ir.DoChildren(n, doNode)
73 for _, a := range analyzers {
81 func fnFileLine(fn *ir.Func) (string, uint) {
82 p := base.Ctxt.InnermostPos(fn.Pos())
83 return filepath.Base(p.Filename()), p.Line()
86 // DumpFuncProps computes and caches function properties for the func
87 // 'fn', or if fn is nil, writes out the cached set of properties to
88 // the file given in 'dumpfile'. Used for the "-d=dumpinlfuncprops=..."
89 // command line flag, intended for use primarily in unit testing.
90 func DumpFuncProps(fn *ir.Func, dumpfile string) {
92 captureFuncDumpEntry(fn)
94 emitDumpToFile(dumpfile)
98 // emitDumpToFile writes out the buffer function property dump entries
99 // to a file, for unit testing. Dump entries need to be sorted by
100 // definition line, and due to generics we need to account for the
101 // possibility that several ir.Func's will have the same def line.
102 func emitDumpToFile(dumpfile string) {
103 outf, err := os.OpenFile(dumpfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
105 base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
108 dumpFilePreamble(outf)
110 atline := map[uint]uint{}
111 sl := make([]fnInlHeur, 0, len(dumpBuffer))
112 for _, e := range dumpBuffer {
114 atline[e.line] = atline[e.line] + 1
116 sl = sortFnInlHeurSlice(sl)
119 for _, entry := range sl {
121 if prevline == entry.line {
124 prevline = entry.line
125 atl := atline[entry.line]
126 if err := dumpFnPreamble(outf, &entry, idx, atl); err != nil {
127 base.Fatalf("function props dump: %v\n", err)
133 // captureFuncDumpEntry analyzes function 'fn' and adds a entry
134 // for it to 'dumpBuffer'. Used for unit testing.
135 func captureFuncDumpEntry(fn *ir.Func) {
136 // avoid capturing compiler-generated equality funcs.
137 if strings.HasPrefix(fn.Sym().Name, ".eq.") {
140 if dumpBuffer == nil {
141 dumpBuffer = make(map[*ir.Func]fnInlHeur)
143 if _, ok := dumpBuffer[fn]; ok {
144 // we can wind up seeing closures multiple times here,
145 // so don't add them more than once.
148 fp := computeFuncProps(fn)
149 file, line := fnFileLine(fn)
151 fname: fn.Sym().Name,
156 dumpBuffer[fn] = entry
159 // dumpFilePreamble writes out a file-level preamble for a given
160 // Go function as part of a function properties dump.
161 func dumpFilePreamble(w io.Writer) {
162 fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
163 fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
164 fmt.Fprintf(w, "// for more information on the format of this file.\n")
165 fmt.Fprintf(w, "// %s\n", preambleDelimiter)
168 // dumpFilePreamble writes out a function-level preamble for a given
169 // Go function as part of a function properties dump. See the
170 // README.txt file in testdata/props for more on the format of
172 func dumpFnPreamble(w io.Writer, fih *fnInlHeur, idx, atl uint) error {
173 fmt.Fprintf(w, "// %s %s %d %d %d\n",
174 fih.file, fih.fname, fih.line, idx, atl)
175 // emit props as comments, followed by delimiter
176 fmt.Fprintf(w, "%s// %s\n", fih.props.ToString("// "), comDelimiter)
177 data, err := json.Marshal(fih.props)
179 return fmt.Errorf("marshall error %v\n", err)
181 fmt.Fprintf(w, "// %s\n// %s\n", string(data), fnDelimiter)
185 // sortFnInlHeurSlice sorts a slice of fnInlHeur based on
186 // the starting line of the function definition, then by name.
187 func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
188 sort.SliceStable(sl, func(i, j int) bool {
189 if sl[i].line != sl[j].line {
190 return sl[i].line < sl[j].line
192 return sl[i].fname < sl[j].fname
197 // delimiters written to various preambles to make parsing of
199 const preambleDelimiter = "<endfilepreamble>"
200 const fnDelimiter = "<endfuncpreamble>"
201 const comDelimiter = "<endpropsdump>"
203 // dumpBuffer stores up function properties dumps when
204 // "-d=dumpinlfuncprops=..." is in effect.
205 var dumpBuffer map[*ir.Func]fnInlHeur