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 func LargestPositiveScoreAdjustment(fn *ir.Func) int {
407 return adjValues[panicPathAdj] + adjValues[initFuncAdj]
408 }
409
410
411
412
413
414
415
416
417
418
419
420 var callSiteTab CallSiteTab
421
422
423
424
425 var scoreCallsCache scoreCallsCacheType
426
427 type scoreCallsCacheType struct {
428 tab CallSiteTab
429 csl []*CallSite
430 }
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450 func ScoreCalls(fn *ir.Func) {
451 if len(fn.Body) == 0 {
452 return
453 }
454 enableDebugTraceIfEnv()
455
456 nameFinder := newNameFinder(fn)
457
458 if debugTrace&debugTraceScoring != 0 {
459 fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
460 }
461
462
463
464
465 var cstab CallSiteTab
466 if funcInlHeur, ok := fpmap[fn]; ok {
467 cstab = funcInlHeur.cstab
468 } else {
469 if len(scoreCallsCache.tab) != 0 {
470 panic("missing call to ScoreCallsCleanup")
471 }
472 if scoreCallsCache.tab == nil {
473 scoreCallsCache.tab = make(CallSiteTab)
474 }
475 if debugTrace&debugTraceScoring != 0 {
476 fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
477 ir.FuncName(fn))
478 }
479 cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0,
480 nameFinder)
481 }
482
483 csa := makeCallSiteAnalyzer(fn)
484 const doCallResults = true
485 csa.scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
486
487 disableDebugTrace()
488 }
489
490
491
492
493
494 func (csa *callSiteAnalyzer) scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
495 if debugTrace&debugTraceScoring != 0 {
496 fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s) len(cstab)=%d\n",
497 ir.FuncName(fn), region[0].Op().String(), len(cstab))
498 }
499
500
501
502
503 csl := scoreCallsCache.csl[:0]
504 for _, cs := range cstab {
505 csl = append(csl, cs)
506 }
507 scoreCallsCache.csl = csl[:0]
508 slices.SortFunc(csl, func(a, b *CallSite) int {
509 return cmp.Compare(a.ID, b.ID)
510 })
511
512
513 var resultNameTab map[*ir.Name]resultPropAndCS
514 for _, cs := range csl {
515 var cprops *FuncProps
516 fihcprops := false
517 desercprops := false
518 if funcInlHeur, ok := fpmap[cs.Callee]; ok {
519 cprops = funcInlHeur.props
520 fihcprops = true
521 } else if cs.Callee.Inl != nil {
522 cprops = DeserializeFromString(cs.Callee.Inl.Properties)
523 desercprops = true
524 } else {
525 if base.Debug.DumpInlFuncProps != "" {
526 fmt.Fprintf(os.Stderr, "=-= *** unable to score call to %s from %s\n", cs.Callee.Sym().Name, fmtFullPos(cs.Call.Pos()))
527 panic("should never happen")
528 } else {
529 continue
530 }
531 }
532 cs.computeCallSiteScore(csa, cprops)
533
534 if doCallResults {
535 if debugTrace&debugTraceScoring != 0 {
536 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)
537 }
538 resultNameTab = csa.examineCallResults(cs, resultNameTab)
539 }
540
541 if debugTrace&debugTraceScoring != 0 {
542 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)
543 }
544 }
545
546 if resultNameTab != nil {
547 csa.rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
548 }
549
550 disableDebugTrace()
551
552 if ic != nil && callSiteTab != nil {
553
554 if err := callSiteTab.merge(cstab); err != nil {
555 base.FatalfAt(ic.Pos(), "%v", err)
556 }
557 } else {
558 callSiteTab = cstab
559 }
560 }
561
562
563
564 func ScoreCallsCleanup() {
565 if base.Debug.DumpInlCallSiteScores != 0 {
566 if allCallSites == nil {
567 allCallSites = make(CallSiteTab)
568 }
569 for call, cs := range callSiteTab {
570 allCallSites[call] = cs
571 }
572 }
573 clear(scoreCallsCache.tab)
574 }
575
576
577
578 func GetCallSiteScore(fn *ir.Func, call *ir.CallExpr) (int, bool) {
579 if funcInlHeur, ok := fpmap[fn]; ok {
580 if cs, ok := funcInlHeur.cstab[call]; ok {
581 return cs.Score, true
582 }
583 }
584 if cs, ok := callSiteTab[call]; ok {
585 return cs.Score, true
586 }
587 return 0, false
588 }
589
590
591
592
593
594
595
596
597
598
599
600
601 func BudgetExpansion(maxBudget int32) int32 {
602 if base.Debug.InlBudgetSlack != 0 {
603 return int32(base.Debug.InlBudgetSlack)
604 }
605
606
607
608 return maxBudget
609 }
610
611 var allCallSites CallSiteTab
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640 func DumpInlCallSiteScores(profile *pgoir.Profile, budgetCallback func(fn *ir.Func, profile *pgoir.Profile) (int32, bool)) {
641
642 var indirectlyDueToPromotion func(cs *CallSite) bool
643 indirectlyDueToPromotion = func(cs *CallSite) bool {
644 bud, _ := budgetCallback(cs.Callee, profile)
645 hairyval := cs.Callee.Inl.Cost
646 score := int32(cs.Score)
647 if hairyval > bud && score <= bud {
648 return true
649 }
650 if cs.parent != nil {
651 return indirectlyDueToPromotion(cs.parent)
652 }
653 return false
654 }
655
656 genstatus := func(cs *CallSite) string {
657 hairyval := cs.Callee.Inl.Cost
658 bud, isPGO := budgetCallback(cs.Callee, profile)
659 score := int32(cs.Score)
660 st := "---"
661 expinl := false
662 switch {
663 case hairyval <= bud && score <= bud:
664
665
666 expinl = true
667 case hairyval > bud && score > bud:
668
669
670 case hairyval > bud && score <= bud:
671
672
673 st = "PROMOTED"
674 expinl = true
675 case hairyval <= bud && score > bud:
676
677
678 st = "DEMOTED"
679 }
680 inlined := cs.aux&csAuxInlined != 0
681 indprom := false
682 if cs.parent != nil {
683 indprom = indirectlyDueToPromotion(cs.parent)
684 }
685 if inlined && indprom {
686 st += "|INDPROM"
687 }
688 if inlined && !expinl {
689 st += "|[NI?]"
690 } else if !inlined && expinl {
691 st += "|[IN?]"
692 }
693 if isPGO {
694 st += "|PGO"
695 }
696 return st
697 }
698
699 if base.Debug.DumpInlCallSiteScores != 0 {
700 var sl []*CallSite
701 for _, cs := range allCallSites {
702 sl = append(sl, cs)
703 }
704 slices.SortFunc(sl, func(a, b *CallSite) int {
705 if a.Score != b.Score {
706 return cmp.Compare(a.Score, b.Score)
707 }
708 fni := ir.PkgFuncName(a.Callee)
709 fnj := ir.PkgFuncName(b.Callee)
710 if fni != fnj {
711 return cmp.Compare(fni, fnj)
712 }
713 ecsi := EncodeCallSiteKey(a)
714 ecsj := EncodeCallSiteKey(b)
715 return cmp.Compare(ecsi, ecsj)
716 })
717
718 mkname := func(fn *ir.Func) string {
719 var n string
720 if fn == nil || fn.Nname == nil {
721 return "<nil>"
722 }
723 if fn.Sym().Pkg == types.LocalPkg {
724 n = "ยท" + fn.Sym().Name
725 } else {
726 n = ir.PkgFuncName(fn)
727 }
728
729 if len(n) <= 64 {
730 return n
731 }
732 return n[:32] + "..." + n[len(n)-32:len(n)]
733 }
734
735 if len(sl) != 0 {
736 fmt.Fprintf(os.Stdout, "# scores for package %s\n", types.LocalPkg.Path)
737 fmt.Fprintf(os.Stdout, "# Score Adjustment Status Callee CallerPos Flags ScoreFlags\n")
738 }
739 for _, cs := range sl {
740 hairyval := cs.Callee.Inl.Cost
741 adj := int32(cs.Score) - hairyval
742 nm := mkname(cs.Callee)
743 ecc := EncodeCallSiteKey(cs)
744 fmt.Fprintf(os.Stdout, "%d %d\t%s\t%s\t%s\t%s\n",
745 cs.Score, adj, genstatus(cs),
746 nm, ecc,
747 cs.ScoreMask.String())
748 }
749 }
750 }
751
View as plain text