1
2
3
4
5 package inlheur
6
7 import (
8 "cmd/compile/internal/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/types"
11 "cmp"
12 "encoding/json"
13 "fmt"
14 "internal/buildcfg"
15 "io"
16 "os"
17 "path/filepath"
18 "slices"
19 "strings"
20 )
21
22 const (
23 debugTraceFuncs = 1 << iota
24 debugTraceFuncFlags
25 debugTraceResults
26 debugTraceParams
27 debugTraceExprClassify
28 debugTraceCalls
29 debugTraceScoring
30 )
31
32
33
34
35
36
37
38
39
40
41 type propAnalyzer interface {
42 nodeVisitPre(n ir.Node)
43 nodeVisitPost(n ir.Node)
44 setResults(funcProps *FuncProps)
45 }
46
47
48
49
50
51
52
53
54 type fnInlHeur struct {
55 props *FuncProps
56 cstab CallSiteTab
57 fname string
58 file string
59 line uint
60 }
61
62 var fpmap = map[*ir.Func]fnInlHeur{}
63
64
65
66
67
68
69
70 func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func), budgetForFunc func(*ir.Func) int32, inlineMaxBudget int) {
71 if fpmap == nil {
72
73
74
75 return
76 }
77 if fn.OClosure != nil {
78
79 return
80 }
81 enableDebugTraceIfEnv()
82 if debugTrace&debugTraceFuncs != 0 {
83 fmt.Fprintf(os.Stderr, "=-= AnalyzeFunc(%v)\n", fn)
84 }
85
86
87
88 funcs := []*ir.Func{fn}
89 ir.VisitFuncAndClosures(fn, func(n ir.Node) {
90 if clo, ok := n.(*ir.ClosureExpr); ok {
91 funcs = append(funcs, clo.Func)
92 }
93 })
94
95
96
97
98
99
100
101
102 nameFinder := newNameFinder(fn)
103 for i := len(funcs) - 1; i >= 0; i-- {
104 f := funcs[i]
105 if f.OClosure != nil && !f.InlinabilityChecked() {
106 canInline(f)
107 }
108 funcProps := analyzeFunc(f, inlineMaxBudget, nameFinder)
109 revisitInlinability(f, funcProps, budgetForFunc)
110 if f.Inl != nil {
111 f.Inl.Properties = funcProps.SerializeToString()
112 }
113 }
114 disableDebugTrace()
115 }
116
117
118
119
120
121 func TearDown() {
122 fpmap = nil
123 scoreCallsCache.tab = nil
124 scoreCallsCache.csl = nil
125 }
126
127 func analyzeFunc(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) *FuncProps {
128 if funcInlHeur, ok := fpmap[fn]; ok {
129 return funcInlHeur.props
130 }
131 funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget, nf)
132 file, line := fnFileLine(fn)
133 entry := fnInlHeur{
134 fname: fn.Sym().Name,
135 file: file,
136 line: line,
137 props: funcProps,
138 cstab: fcstab,
139 }
140 fn.SetNeverReturns(entry.props.Flags&FuncPropNeverReturns != 0)
141 fpmap[fn] = entry
142 if fn.Inl != nil && fn.Inl.Properties == "" {
143 fn.Inl.Properties = entry.props.SerializeToString()
144 }
145 return funcProps
146 }
147
148
149
150
151
152
153
154 func revisitInlinability(fn *ir.Func, funcProps *FuncProps, budgetForFunc func(*ir.Func) int32) {
155 if fn.Inl == nil {
156 return
157 }
158 maxAdj := int32(LargestNegativeScoreAdjustment(fn, funcProps))
159 budget := budgetForFunc(fn)
160 if fn.Inl.Cost+maxAdj > budget {
161 fn.Inl = nil
162 }
163 }
164
165
166
167
168 func computeFuncProps(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) (*FuncProps, CallSiteTab) {
169 if debugTrace&debugTraceFuncs != 0 {
170 fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
171 fn, fn)
172 }
173 funcProps := new(FuncProps)
174 ffa := makeFuncFlagsAnalyzer(fn)
175 analyzers := []propAnalyzer{ffa}
176 analyzers = addResultsAnalyzer(fn, analyzers, funcProps, inlineMaxBudget, nf)
177 analyzers = addParamsAnalyzer(fn, analyzers, funcProps, nf)
178 runAnalyzersOnFunction(fn, analyzers)
179 for _, a := range analyzers {
180 a.setResults(funcProps)
181 }
182 cstab := computeCallSiteTable(fn, fn.Body, nil, ffa.panicPathTable(), 0, nf)
183 return funcProps, cstab
184 }
185
186 func runAnalyzersOnFunction(fn *ir.Func, analyzers []propAnalyzer) {
187 var doNode func(ir.Node) bool
188 doNode = func(n ir.Node) bool {
189 for _, a := range analyzers {
190 a.nodeVisitPre(n)
191 }
192 ir.DoChildren(n, doNode)
193 for _, a := range analyzers {
194 a.nodeVisitPost(n)
195 }
196 return false
197 }
198 doNode(fn)
199 }
200
201 func propsForFunc(fn *ir.Func) *FuncProps {
202 if funcInlHeur, ok := fpmap[fn]; ok {
203 return funcInlHeur.props
204 } else if fn.Inl != nil && fn.Inl.Properties != "" {
205
206
207 return DeserializeFromString(fn.Inl.Properties)
208 }
209 return nil
210 }
211
212 func fnFileLine(fn *ir.Func) (string, uint) {
213 p := base.Ctxt.InnermostPos(fn.Pos())
214 return filepath.Base(p.Filename()), p.Line()
215 }
216
217 func Enabled() bool {
218 return buildcfg.Experiment.NewInliner || UnitTesting()
219 }
220
221 func UnitTesting() bool {
222 return base.Debug.DumpInlFuncProps != "" ||
223 base.Debug.DumpInlCallSiteScores != 0
224 }
225
226
227
228
229
230
231 func DumpFuncProps(fn *ir.Func, dumpfile string) {
232 if fn != nil {
233 if fn.OClosure != nil {
234
235 return
236 }
237 captureFuncDumpEntry(fn)
238 ir.VisitFuncAndClosures(fn, func(n ir.Node) {
239 if clo, ok := n.(*ir.ClosureExpr); ok {
240 captureFuncDumpEntry(clo.Func)
241 }
242 })
243 } else {
244 emitDumpToFile(dumpfile)
245 }
246 }
247
248
249
250
251
252 func emitDumpToFile(dumpfile string) {
253 mode := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
254 if dumpfile[0] == '+' {
255 dumpfile = dumpfile[1:]
256 mode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
257 }
258 if dumpfile[0] == '%' {
259 dumpfile = dumpfile[1:]
260 d, b := filepath.Dir(dumpfile), filepath.Base(dumpfile)
261 ptag := strings.ReplaceAll(types.LocalPkg.Path, "/", ":")
262 dumpfile = d + "/" + ptag + "." + b
263 }
264 outf, err := os.OpenFile(dumpfile, mode, 0644)
265 if err != nil {
266 base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
267 }
268 defer outf.Close()
269 dumpFilePreamble(outf)
270
271 atline := map[uint]uint{}
272 sl := make([]fnInlHeur, 0, len(dumpBuffer))
273 for _, e := range dumpBuffer {
274 sl = append(sl, e)
275 atline[e.line] = atline[e.line] + 1
276 }
277 sl = sortFnInlHeurSlice(sl)
278
279 prevline := uint(0)
280 for _, entry := range sl {
281 idx := uint(0)
282 if prevline == entry.line {
283 idx++
284 }
285 prevline = entry.line
286 atl := atline[entry.line]
287 if err := dumpFnPreamble(outf, &entry, nil, idx, atl); err != nil {
288 base.Fatalf("function props dump: %v\n", err)
289 }
290 }
291 dumpBuffer = nil
292 }
293
294
295
296
297
298 func captureFuncDumpEntry(fn *ir.Func) {
299
300 if strings.HasPrefix(fn.Sym().Name, ".eq.") {
301 return
302 }
303 funcInlHeur, ok := fpmap[fn]
304 if !ok {
305
306
307
308 funcInlHeur = fnInlHeur{cstab: callSiteTab}
309 }
310 if dumpBuffer == nil {
311 dumpBuffer = make(map[*ir.Func]fnInlHeur)
312 }
313 if _, ok := dumpBuffer[fn]; ok {
314 return
315 }
316 if debugTrace&debugTraceFuncs != 0 {
317 fmt.Fprintf(os.Stderr, "=-= capturing dump for %v:\n", fn)
318 }
319 dumpBuffer[fn] = funcInlHeur
320 }
321
322
323
324 func dumpFilePreamble(w io.Writer) {
325 fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
326 fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
327 fmt.Fprintf(w, "// for more information on the format of this file.\n")
328 fmt.Fprintf(w, "// %s\n", preambleDelimiter)
329 }
330
331
332
333
334
335 func dumpFnPreamble(w io.Writer, funcInlHeur *fnInlHeur, ecst encodedCallSiteTab, idx, atl uint) error {
336 fmt.Fprintf(w, "// %s %s %d %d %d\n",
337 funcInlHeur.file, funcInlHeur.fname, funcInlHeur.line, idx, atl)
338
339 fmt.Fprintf(w, "%s// %s\n", funcInlHeur.props.ToString("// "), comDelimiter)
340 data, err := json.Marshal(funcInlHeur.props)
341 if err != nil {
342 return fmt.Errorf("marshal error %v\n", err)
343 }
344 fmt.Fprintf(w, "// %s\n", string(data))
345 dumpCallSiteComments(w, funcInlHeur.cstab, ecst)
346 fmt.Fprintf(w, "// %s\n", fnDelimiter)
347 return nil
348 }
349
350
351
352 func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
353 slices.SortStableFunc(sl, func(a, b fnInlHeur) int {
354 if a.line != b.line {
355 return cmp.Compare(a.line, b.line)
356 }
357 return strings.Compare(a.fname, b.fname)
358 })
359 return sl
360 }
361
362
363
364 const preambleDelimiter = "<endfilepreamble>"
365 const fnDelimiter = "<endfuncpreamble>"
366 const comDelimiter = "<endpropsdump>"
367 const csDelimiter = "<endcallsites>"
368
369
370
371 var dumpBuffer map[*ir.Func]fnInlHeur
372
View as plain text