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