1
2
3
4
5 package inlheur
6
7 import (
8 "cmd/compile/internal/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/pgoir"
11 "cmd/compile/internal/types"
12 "cmp"
13 "fmt"
14 "os"
15 "slices"
16 "strconv"
17 "strings"
18 )
19
20
21
22 type scoreAdjustTyp uint
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 const (
47
48 panicPathAdj scoreAdjustTyp = (1 << iota)
49 initFuncAdj
50 inLoopAdj
51
52
53 passConstToIfAdj
54 passConstToNestedIfAdj
55 passConcreteToItfCallAdj
56 passConcreteToNestedItfCallAdj
57 passFuncToIndCallAdj
58 passFuncToNestedIndCallAdj
59 passInlinableFuncToIndCallAdj
60 passInlinableFuncToNestedIndCallAdj
61
62
63 returnFeedsConstToIfAdj
64 returnFeedsFuncToIndCallAdj
65 returnFeedsInlinableFuncToIndCallAdj
66 returnFeedsConcreteToInterfaceCallAdj
67
68 sentinelScoreAdj
69 )
70
71
72
73
74
75
76
77 var adjValues = map[scoreAdjustTyp]int{
78 panicPathAdj: 40,
79 initFuncAdj: 20,
80 inLoopAdj: -5,
81 passConstToIfAdj: -20,
82 passConstToNestedIfAdj: -15,
83 passConcreteToItfCallAdj: -30,
84 passConcreteToNestedItfCallAdj: -25,
85 passFuncToIndCallAdj: -25,
86 passFuncToNestedIndCallAdj: -20,
87 passInlinableFuncToIndCallAdj: -45,
88 passInlinableFuncToNestedIndCallAdj: -40,
89 returnFeedsConstToIfAdj: -15,
90 returnFeedsFuncToIndCallAdj: -25,
91 returnFeedsInlinableFuncToIndCallAdj: -40,
92 returnFeedsConcreteToInterfaceCallAdj: -25,
93 }
94
95
96
97
98
99 func SetupScoreAdjustments() {
100 if base.Debug.InlScoreAdj == "" {
101 return
102 }
103 if err := parseScoreAdj(base.Debug.InlScoreAdj); err != nil {
104 base.Fatalf("malformed -d=inlscoreadj argument %q: %v",
105 base.Debug.InlScoreAdj, err)
106 }
107 }
108
109 func adjStringToVal(s string) (scoreAdjustTyp, bool) {
110 for adj := scoreAdjustTyp(1); adj < sentinelScoreAdj; adj <<= 1 {
111 if adj.String() == s {
112 return adj, true
113 }
114 }
115 return 0, false
116 }
117
118 func parseScoreAdj(val string) error {
119 clauses := strings.Split(val, "/")
120 if len(clauses) == 0 {
121 return fmt.Errorf("no clauses")
122 }
123 for _, clause := range clauses {
124 elems := strings.Split(clause, ":")
125 if len(elems) < 2 {
126 return fmt.Errorf("clause %q: expected colon", clause)
127 }
128 if len(elems) != 2 {
129 return fmt.Errorf("clause %q has %d elements, wanted 2", clause,
130 len(elems))
131 }
132 adj, ok := adjStringToVal(elems[0])
133 if !ok {
134 return fmt.Errorf("clause %q: unknown adjustment", clause)
135 }
136 val, err := strconv.Atoi(elems[1])
137 if err != nil {
138 return fmt.Errorf("clause %q: malformed value: %v", clause, err)
139 }
140 adjValues[adj] = val
141 }
142 return nil
143 }
144
145 func adjValue(x scoreAdjustTyp) int {
146 if val, ok := adjValues[x]; ok {
147 return val
148 } else {
149 panic("internal error unregistered adjustment type")
150 }
151 }
152
153 var mayMustAdj = [...]struct{ may, must scoreAdjustTyp }{
154 {may: passConstToNestedIfAdj, must: passConstToIfAdj},
155 {may: passConcreteToNestedItfCallAdj, must: passConcreteToItfCallAdj},
156 {may: passFuncToNestedIndCallAdj, must: passFuncToNestedIndCallAdj},
157 {may: passInlinableFuncToNestedIndCallAdj, must: passInlinableFuncToIndCallAdj},
158 }
159
160 func isMay(x scoreAdjustTyp) bool {
161 return mayToMust(x) != 0
162 }
163
164 func isMust(x scoreAdjustTyp) bool {
165 return mustToMay(x) != 0
166 }
167
168 func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
169 for _, v := range mayMustAdj {
170 if x == v.may {
171 return v.must
172 }
173 }
174 return 0
175 }
176
177 func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
178 for _, v := range mayMustAdj {
179 if x == v.must {
180 return v.may
181 }
182 }
183 return 0
184 }
185
186
187
188
189
190
191
192
193 func (cs *CallSite) computeCallSiteScore(csa *callSiteAnalyzer, calleeProps *FuncProps) {
194 callee := cs.Callee
195 csflags := cs.Flags
196 call := cs.Call
197
198
199 score := int(callee.Inl.Cost)
200 var tmask scoreAdjustTyp
201
202 if debugTrace&debugTraceScoring != 0 {
203 fmt.Fprintf(os.Stderr, "=-= scoring call to %s at %s , initial=%d\n",
204 callee.Sym().Name, fmtFullPos(call.Pos()), score)
205 }
206
207
208 if csflags&CallSiteOnPanicPath != 0 {
209 score, tmask = adjustScore(panicPathAdj, score, tmask)
210 }
211 if csflags&CallSiteInInitFunc != 0 {
212 score, tmask = adjustScore(initFuncAdj, score, tmask)
213 }
214
215
216 if csflags&CallSiteInLoop != 0 {
217 score, tmask = adjustScore(inLoopAdj, score, tmask)
218 }
219
220
221 if calleeProps == nil {
222 cs.Score, cs.ScoreMask = score, tmask
223 return
224 }
225
226
227 calleeRecvrParms := callee.Type().RecvParams()
228 for idx := range call.Args {
229
230 if calleeRecvrParms[idx].Sym == nil ||
231 calleeRecvrParms[idx].Sym.IsBlank() {
232 continue
233 }
234 arg := call.Args[idx]
235 pflag := calleeProps.ParamFlags[idx]
236 if debugTrace&debugTraceScoring != 0 {
237 fmt.Fprintf(os.Stderr, "=-= arg %d of %d: val %v flags=%s\n",
238 idx, len(call.Args), arg, pflag.String())
239 }
240
241 if len(cs.ArgProps) == 0 {
242 continue
243 }
244 argProps := cs.ArgProps[idx]
245
246 if debugTrace&debugTraceScoring != 0 {
247 fmt.Fprintf(os.Stderr, "=-= arg %d props %s value %v\n",
248 idx, argProps.String(), arg)
249 }
250
251 if argProps&ActualExprConstant != 0 {
252 if pflag&ParamMayFeedIfOrSwitch != 0 {
253 score, tmask = adjustScore(passConstToNestedIfAdj, score, tmask)
254 }
255 if pflag&ParamFeedsIfOrSwitch != 0 {
256 score, tmask = adjustScore(passConstToIfAdj, score, tmask)
257 }
258 }
259
260 if argProps&ActualExprIsConcreteConvIface != 0 {
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 if pflag&ParamMayFeedInterfaceMethodCall != 0 {
286 score, tmask = adjustScore(passConcreteToNestedItfCallAdj, score, tmask)
287 }
288 if pflag&ParamFeedsInterfaceMethodCall != 0 {
289 score, tmask = adjustScore(passConcreteToItfCallAdj, score, tmask)
290 }
291 }
292
293 if argProps&(ActualExprIsFunc|ActualExprIsInlinableFunc) != 0 {
294 mayadj := passFuncToNestedIndCallAdj
295 mustadj := passFuncToIndCallAdj
296 if argProps&ActualExprIsInlinableFunc != 0 {
297 mayadj = passInlinableFuncToNestedIndCallAdj
298 mustadj = passInlinableFuncToIndCallAdj
299 }
300 if pflag&ParamMayFeedIndirectCall != 0 {
301 score, tmask = adjustScore(mayadj, score, tmask)
302 }
303 if pflag&ParamFeedsIndirectCall != 0 {
304 score, tmask = adjustScore(mustadj, score, tmask)
305 }
306 }
307 }
308
309 cs.Score, cs.ScoreMask = score, tmask
310 }
311
312 func adjustScore(typ scoreAdjustTyp, score int, mask scoreAdjustTyp) (int, scoreAdjustTyp) {
313
314 if isMust(typ) {
315 if mask&typ != 0 {
316 return score, mask
317 }
318 may := mustToMay(typ)
319 if mask&may != 0 {
320
321 score -= adjValue(may)
322 mask &^= may
323 }
324 } else if isMay(typ) {
325 must := mayToMust(typ)
326 if mask&(must|typ) != 0 {
327 return score, mask
328 }
329 }
330 if mask&typ == 0 {
331 if debugTrace&debugTraceScoring != 0 {
332 fmt.Fprintf(os.Stderr, "=-= applying adj %d for %s\n",
333 adjValue(typ), typ.String())
334 }
335 score += adjValue(typ)
336 mask |= typ
337 }
338 return score, mask
339 }
340
341 var resultFlagToPositiveAdj map[ResultPropBits]scoreAdjustTyp
342 var paramFlagToPositiveAdj map[ParamPropBits]scoreAdjustTyp
343
344 func setupFlagToAdjMaps() {
345 resultFlagToPositiveAdj = map[ResultPropBits]scoreAdjustTyp{
346 ResultIsAllocatedMem: returnFeedsConcreteToInterfaceCallAdj,
347 ResultAlwaysSameFunc: returnFeedsFuncToIndCallAdj,
348 ResultAlwaysSameConstant: returnFeedsConstToIfAdj,
349 }
350 paramFlagToPositiveAdj = map[ParamPropBits]scoreAdjustTyp{
351 ParamMayFeedInterfaceMethodCall: passConcreteToNestedItfCallAdj,
352 ParamFeedsInterfaceMethodCall: passConcreteToItfCallAdj,
353 ParamMayFeedIndirectCall: passInlinableFuncToNestedIndCallAdj,
354 ParamFeedsIndirectCall: passInlinableFuncToIndCallAdj,
355 }
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 func LargestNegativeScoreAdjustment(fn *ir.Func, props *FuncProps) int {
378 if resultFlagToPositiveAdj == nil {
379 setupFlagToAdjMaps()
380 }
381 var tmask scoreAdjustTyp
382 score := adjValues[inLoopAdj]
383 for _, pf := range props.ParamFlags {
384 if adj, ok := paramFlagToPositiveAdj[pf]; ok {
385 score, tmask = adjustScore(adj, score, tmask)
386 }
387 }
388 for _, rf := range props.ResultFlags {
389 if adj, ok := resultFlagToPositiveAdj[rf]; ok {
390 score, tmask = adjustScore(adj, score, tmask)
391 }
392 }
393
394 if debugTrace&debugTraceScoring != 0 {
395 fmt.Fprintf(os.Stderr, "=-= largestScore(%v) is %d\n",
396 fn, score)
397 }
398
399 return score
400 }
401
402
403
404
405
406
407
408
409
410
411
412 var callSiteTab CallSiteTab
413
414
415
416
417 var scoreCallsCache scoreCallsCacheType
418
419 type scoreCallsCacheType struct {
420 tab CallSiteTab
421 csl []*CallSite
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442 func ScoreCalls(fn *ir.Func) {
443 if len(fn.Body) == 0 {
444 return
445 }
446 enableDebugTraceIfEnv()
447
448 nameFinder := newNameFinder(fn)
449
450 if debugTrace&debugTraceScoring != 0 {
451 fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
452 }
453
454
455
456
457 var cstab CallSiteTab
458 if funcInlHeur, ok := fpmap[fn]; ok {
459 cstab = funcInlHeur.cstab
460 } else {
461 if len(scoreCallsCache.tab) != 0 {
462 panic("missing call to ScoreCallsCleanup")
463 }
464 if scoreCallsCache.tab == nil {
465 scoreCallsCache.tab = make(CallSiteTab)
466 }
467 if debugTrace&debugTraceScoring != 0 {
468 fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
469 ir.FuncName(fn))
470 }
471 cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0,
472 nameFinder)
473 }
474
475 csa := makeCallSiteAnalyzer(fn)
476 const doCallResults = true
477 csa.scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
478
479 disableDebugTrace()
480 }
481
482
483
484
485
486 func (csa *callSiteAnalyzer) scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
487 if debugTrace&debugTraceScoring != 0 {
488 fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s) len(cstab)=%d\n",
489 ir.FuncName(fn), region[0].Op().String(), len(cstab))
490 }
491
492
493
494
495 csl := scoreCallsCache.csl[:0]
496 for _, cs := range cstab {
497 csl = append(csl, cs)
498 }
499 scoreCallsCache.csl = csl[:0]
500 slices.SortFunc(csl, func(a, b *CallSite) int {
501 return cmp.Compare(a.ID, b.ID)
502 })
503
504
505 var resultNameTab map[*ir.Name]resultPropAndCS
506 for _, cs := range csl {
507 var cprops *FuncProps
508 fihcprops := false
509 desercprops := false
510 if funcInlHeur, ok := fpmap[cs.Callee]; ok {
511 cprops = funcInlHeur.props
512 fihcprops = true
513 } else if cs.Callee.Inl != nil {
514 cprops = DeserializeFromString(cs.Callee.Inl.Properties)
515 desercprops = true
516 } else {
517 if base.Debug.DumpInlFuncProps != "" {
518 fmt.Fprintf(os.Stderr, "=-= *** unable to score call to %s from %s\n", cs.Callee.Sym().Name, fmtFullPos(cs.Call.Pos()))
519 panic("should never happen")
520 } else {
521 continue
522 }
523 }
524 cs.computeCallSiteScore(csa, cprops)
525
526 if doCallResults {
527 if debugTrace&debugTraceScoring != 0 {
528 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)
529 }
530 resultNameTab = csa.examineCallResults(cs, resultNameTab)
531 }
532
533 if debugTrace&debugTraceScoring != 0 {
534 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)
535 }
536 }
537
538 if resultNameTab != nil {
539 csa.rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
540 }
541
542 disableDebugTrace()
543
544 if ic != nil && callSiteTab != nil {
545
546 if err := callSiteTab.merge(cstab); err != nil {
547 base.FatalfAt(ic.Pos(), "%v", err)
548 }
549 } else {
550 callSiteTab = cstab
551 }
552 }
553
554
555
556 func ScoreCallsCleanup() {
557 if base.Debug.DumpInlCallSiteScores != 0 {
558 if allCallSites == nil {
559 allCallSites = make(CallSiteTab)
560 }
561 for call, cs := range callSiteTab {
562 allCallSites[call] = cs
563 }
564 }
565 clear(scoreCallsCache.tab)
566 }
567
568
569
570 func GetCallSiteScore(fn *ir.Func, call *ir.CallExpr) (int, bool) {
571 if funcInlHeur, ok := fpmap[fn]; ok {
572 if cs, ok := funcInlHeur.cstab[call]; ok {
573 return cs.Score, true
574 }
575 }
576 if cs, ok := callSiteTab[call]; ok {
577 return cs.Score, true
578 }
579 return 0, false
580 }
581
582
583
584
585
586
587
588
589
590
591
592
593 func BudgetExpansion(maxBudget int32) int32 {
594 if base.Debug.InlBudgetSlack != 0 {
595 return int32(base.Debug.InlBudgetSlack)
596 }
597
598
599
600 return maxBudget
601 }
602
603 var allCallSites CallSiteTab
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632 func DumpInlCallSiteScores(profile *pgoir.Profile, budgetCallback func(fn *ir.Func, profile *pgoir.Profile) (int32, bool)) {
633
634 var indirectlyDueToPromotion func(cs *CallSite) bool
635 indirectlyDueToPromotion = func(cs *CallSite) bool {
636 bud, _ := budgetCallback(cs.Callee, profile)
637 hairyval := cs.Callee.Inl.Cost
638 score := int32(cs.Score)
639 if hairyval > bud && score <= bud {
640 return true
641 }
642 if cs.parent != nil {
643 return indirectlyDueToPromotion(cs.parent)
644 }
645 return false
646 }
647
648 genstatus := func(cs *CallSite) string {
649 hairyval := cs.Callee.Inl.Cost
650 bud, isPGO := budgetCallback(cs.Callee, profile)
651 score := int32(cs.Score)
652 st := "---"
653 expinl := false
654 switch {
655 case hairyval <= bud && score <= bud:
656
657
658 expinl = true
659 case hairyval > bud && score > bud:
660
661
662 case hairyval > bud && score <= bud:
663
664
665 st = "PROMOTED"
666 expinl = true
667 case hairyval <= bud && score > bud:
668
669
670 st = "DEMOTED"
671 }
672 inlined := cs.aux&csAuxInlined != 0
673 indprom := false
674 if cs.parent != nil {
675 indprom = indirectlyDueToPromotion(cs.parent)
676 }
677 if inlined && indprom {
678 st += "|INDPROM"
679 }
680 if inlined && !expinl {
681 st += "|[NI?]"
682 } else if !inlined && expinl {
683 st += "|[IN?]"
684 }
685 if isPGO {
686 st += "|PGO"
687 }
688 return st
689 }
690
691 if base.Debug.DumpInlCallSiteScores != 0 {
692 var sl []*CallSite
693 for _, cs := range allCallSites {
694 sl = append(sl, cs)
695 }
696 slices.SortFunc(sl, func(a, b *CallSite) int {
697 if a.Score != b.Score {
698 return cmp.Compare(a.Score, b.Score)
699 }
700 fni := ir.PkgFuncName(a.Callee)
701 fnj := ir.PkgFuncName(b.Callee)
702 if fni != fnj {
703 return cmp.Compare(fni, fnj)
704 }
705 ecsi := EncodeCallSiteKey(a)
706 ecsj := EncodeCallSiteKey(b)
707 return cmp.Compare(ecsi, ecsj)
708 })
709
710 mkname := func(fn *ir.Func) string {
711 var n string
712 if fn == nil || fn.Nname == nil {
713 return "<nil>"
714 }
715 if fn.Sym().Pkg == types.LocalPkg {
716 n = "ยท" + fn.Sym().Name
717 } else {
718 n = ir.PkgFuncName(fn)
719 }
720
721 if len(n) <= 64 {
722 return n
723 }
724 return n[:32] + "..." + n[len(n)-32:len(n)]
725 }
726
727 if len(sl) != 0 {
728 fmt.Fprintf(os.Stdout, "# scores for package %s\n", types.LocalPkg.Path)
729 fmt.Fprintf(os.Stdout, "# Score Adjustment Status Callee CallerPos Flags ScoreFlags\n")
730 }
731 for _, cs := range sl {
732 hairyval := cs.Callee.Inl.Cost
733 adj := int32(cs.Score) - hairyval
734 nm := mkname(cs.Callee)
735 ecc := EncodeCallSiteKey(cs)
736 fmt.Fprintf(os.Stdout, "%d %d\t%s\t%s\t%s\t%s\n",
737 cs.Score, adj, genstatus(cs),
738 nm, ecc,
739 cs.ScoreMask.String())
740 }
741 }
742 }
743
View as plain text