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