]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/inline/inlheur/scoring.go
cmd/compile/internal/inline: rework call scoring for non-inlinable funcs
[gostls13.git] / src / cmd / compile / internal / inline / inlheur / scoring.go
1 // Copyright 2023 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package inlheur
6
7 import (
8         "cmd/compile/internal/base"
9         "cmd/compile/internal/ir"
10         "cmd/compile/internal/pgo"
11         "cmd/compile/internal/typecheck"
12         "cmd/compile/internal/types"
13         "fmt"
14         "os"
15         "sort"
16 )
17
18 // These constants enumerate the set of possible ways/scenarios
19 // in which we'll adjust the score of a given callsite.
20 type scoreAdjustTyp uint
21
22 // These constants capture the various ways in which the inliner's
23 // scoring phase can adjust a callsite score based on heuristics. They
24 // fall broadly into three categories:
25 //
26 // 1) adjustments based solely on the callsite context (ex: call
27 // appears on panic path)
28 //
29 // 2) adjustments that take into account specific interesting values
30 // passed at a call site (ex: passing a constant that could result in
31 // cprop/deadcode in the caller)
32 //
33 // 3) adjustments that take into account values returned from the call
34 // at a callsite (ex: call always returns the same inlinable function,
35 // and return value flows unmodified into an indirect call)
36 //
37 // For categories 2 and 3 above, each adjustment can have either a
38 // "must" version and a "may" version (but not both). Here the idea is
39 // that in the "must" version the value flow is unconditional: if the
40 // callsite executes, then the condition we're interested in (ex:
41 // param feeding call) is guaranteed to happen. For the "may" version,
42 // there may be control flow that could cause the benefit to be
43 // bypassed.
44 const (
45         // Category 1 adjustments (see above)
46         panicPathAdj scoreAdjustTyp = (1 << iota)
47         initFuncAdj
48         inLoopAdj
49
50         // Category 2 adjustments (see above).
51         passConstToIfAdj
52         passConstToNestedIfAdj
53         passConcreteToItfCallAdj
54         passConcreteToNestedItfCallAdj
55         passFuncToIndCallAdj
56         passFuncToNestedIndCallAdj
57         passInlinableFuncToIndCallAdj
58         passInlinableFuncToNestedIndCallAdj
59
60         // Category 3 adjustments.
61         returnFeedsConstToIfAdj
62         returnFeedsFuncToIndCallAdj
63         returnFeedsInlinableFuncToIndCallAdj
64         returnFeedsConcreteToInterfaceCallAdj
65 )
66
67 // This table records the specific values we use to adjust call
68 // site scores in a given scenario.
69 // NOTE: these numbers are chosen very arbitrarily; ideally
70 // we will go through some sort of turning process to decide
71 // what value for each one produces the best performance.
72
73 var adjValues = map[scoreAdjustTyp]int{
74         panicPathAdj:                          40,
75         initFuncAdj:                           20,
76         inLoopAdj:                             -5,
77         passConstToIfAdj:                      -20,
78         passConstToNestedIfAdj:                -15,
79         passConcreteToItfCallAdj:              -30,
80         passConcreteToNestedItfCallAdj:        -25,
81         passFuncToIndCallAdj:                  -25,
82         passFuncToNestedIndCallAdj:            -20,
83         passInlinableFuncToIndCallAdj:         -45,
84         passInlinableFuncToNestedIndCallAdj:   -40,
85         returnFeedsConstToIfAdj:               -15,
86         returnFeedsFuncToIndCallAdj:           -25,
87         returnFeedsInlinableFuncToIndCallAdj:  -40,
88         returnFeedsConcreteToInterfaceCallAdj: -25,
89 }
90
91 func adjValue(x scoreAdjustTyp) int {
92         if val, ok := adjValues[x]; ok {
93                 return val
94         } else {
95                 panic("internal error unregistered adjustment type")
96         }
97 }
98
99 var mayMustAdj = [...]struct{ may, must scoreAdjustTyp }{
100         {may: passConstToNestedIfAdj, must: passConstToIfAdj},
101         {may: passConcreteToNestedItfCallAdj, must: passConcreteToItfCallAdj},
102         {may: passFuncToNestedIndCallAdj, must: passFuncToNestedIndCallAdj},
103         {may: passInlinableFuncToNestedIndCallAdj, must: passInlinableFuncToIndCallAdj},
104 }
105
106 func isMay(x scoreAdjustTyp) bool {
107         return mayToMust(x) != 0
108 }
109
110 func isMust(x scoreAdjustTyp) bool {
111         return mustToMay(x) != 0
112 }
113
114 func mayToMust(x scoreAdjustTyp) scoreAdjustTyp {
115         for _, v := range mayMustAdj {
116                 if x == v.may {
117                         return v.must
118                 }
119         }
120         return 0
121 }
122
123 func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
124         for _, v := range mayMustAdj {
125                 if x == v.must {
126                         return v.may
127                 }
128         }
129         return 0
130 }
131
132 // computeCallSiteScore takes a given call site whose ir node is 'call' and
133 // callee function is 'callee' and with previously computed call site
134 // properties 'csflags', then computes a score for the callsite that
135 // combines the size cost of the callee with heuristics based on
136 // previously parameter and function properties.
137 func computeCallSiteScore(callee *ir.Func, calleeProps *FuncProps, call ir.Node, csflags CSPropBits) (int, scoreAdjustTyp) {
138         // Start with the size-based score for the callee.
139         score := int(callee.Inl.Cost)
140         var tmask scoreAdjustTyp
141
142         if debugTrace&debugTraceScoring != 0 {
143                 fmt.Fprintf(os.Stderr, "=-= scoring call to %s at %s , initial=%d\n",
144                         callee.Sym().Name, fmtFullPos(call.Pos()), score)
145         }
146
147         // First some score adjustments to discourage inlining in selected cases.
148         if csflags&CallSiteOnPanicPath != 0 {
149                 score, tmask = adjustScore(panicPathAdj, score, tmask)
150         }
151         if csflags&CallSiteInInitFunc != 0 {
152                 score, tmask = adjustScore(initFuncAdj, score, tmask)
153         }
154
155         // Then adjustments to encourage inlining in selected cases.
156         if csflags&CallSiteInLoop != 0 {
157                 score, tmask = adjustScore(inLoopAdj, score, tmask)
158         }
159
160         if calleeProps == nil {
161                 return score, tmask
162         }
163
164         // Walk through the actual expressions being passed at the call.
165         calleeRecvrParms := callee.Type().RecvParams()
166         ce := call.(*ir.CallExpr)
167         for idx := range ce.Args {
168                 // ignore blanks
169                 if calleeRecvrParms[idx].Sym == nil ||
170                         calleeRecvrParms[idx].Sym.IsBlank() {
171                         continue
172                 }
173                 arg := ce.Args[idx]
174                 pflag := calleeProps.ParamFlags[idx]
175                 if debugTrace&debugTraceScoring != 0 {
176                         fmt.Fprintf(os.Stderr, "=-= arg %d of %d: val %v flags=%s\n",
177                                 idx, len(ce.Args), arg, pflag.String())
178                 }
179                 _, islit := isLiteral(arg)
180                 iscci := isConcreteConvIface(arg)
181                 fname, isfunc, _ := isFuncName(arg)
182                 if debugTrace&debugTraceScoring != 0 {
183                         fmt.Fprintf(os.Stderr, "=-= isLit=%v iscci=%v isfunc=%v for arg %v\n", islit, iscci, isfunc, arg)
184                 }
185
186                 if islit {
187                         if pflag&ParamMayFeedIfOrSwitch != 0 {
188                                 score, tmask = adjustScore(passConstToNestedIfAdj, score, tmask)
189                         }
190                         if pflag&ParamFeedsIfOrSwitch != 0 {
191                                 score, tmask = adjustScore(passConstToIfAdj, score, tmask)
192                         }
193                 }
194
195                 if iscci {
196                         // FIXME: ideally here it would be nice to make a
197                         // distinction between the inlinable case and the
198                         // non-inlinable case, but this is hard to do. Example:
199                         //
200                         //    type I interface { Tiny() int; Giant() }
201                         //    type Conc struct { x int }
202                         //    func (c *Conc) Tiny() int { return 42 }
203                         //    func (c *Conc) Giant() { <huge amounts of code> }
204                         //
205                         //    func passConcToItf(c *Conc) {
206                         //        makesItfMethodCall(c)
207                         //    }
208                         //
209                         // In the code above, function properties will only tell
210                         // us that 'makesItfMethodCall' invokes a method on its
211                         // interface parameter, but we don't know whether it calls
212                         // "Tiny" or "Giant". If we knew if called "Tiny", then in
213                         // theory in addition to converting the interface call to
214                         // a direct call, we could also inline (in which case
215                         // we'd want to decrease the score even more).
216                         //
217                         // One thing we could do (not yet implemented) is iterate
218                         // through all of the methods of "*Conc" that allow it to
219                         // satisfy I, and if all are inlinable, then exploit that.
220                         if pflag&ParamMayFeedInterfaceMethodCall != 0 {
221                                 score, tmask = adjustScore(passConcreteToNestedItfCallAdj, score, tmask)
222                         }
223                         if pflag&ParamFeedsInterfaceMethodCall != 0 {
224                                 score, tmask = adjustScore(passConcreteToItfCallAdj, score, tmask)
225                         }
226                 }
227
228                 if isfunc {
229                         mayadj := passFuncToNestedIndCallAdj
230                         mustadj := passFuncToIndCallAdj
231                         if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
232                                 mayadj = passInlinableFuncToNestedIndCallAdj
233                                 mustadj = passInlinableFuncToIndCallAdj
234                         }
235                         if pflag&ParamMayFeedIndirectCall != 0 {
236                                 score, tmask = adjustScore(mayadj, score, tmask)
237                         }
238                         if pflag&ParamFeedsIndirectCall != 0 {
239                                 score, tmask = adjustScore(mustadj, score, tmask)
240                         }
241                 }
242         }
243
244         return score, tmask
245 }
246
247 func adjustScore(typ scoreAdjustTyp, score int, mask scoreAdjustTyp) (int, scoreAdjustTyp) {
248
249         if isMust(typ) {
250                 if mask&typ != 0 {
251                         return score, mask
252                 }
253                 may := mustToMay(typ)
254                 if mask&may != 0 {
255                         // promote may to must, so undo may
256                         score -= adjValue(may)
257                         mask &^= may
258                 }
259         } else if isMay(typ) {
260                 must := mayToMust(typ)
261                 if mask&(must|typ) != 0 {
262                         return score, mask
263                 }
264         }
265         if mask&typ == 0 {
266                 if debugTrace&debugTraceScoring != 0 {
267                         fmt.Fprintf(os.Stderr, "=-= applying adj %d for %s\n",
268                                 adjValue(typ), typ.String())
269                 }
270                 score += adjValue(typ)
271                 mask |= typ
272         }
273         return score, mask
274 }
275
276 var resultFlagToPositiveAdj map[ResultPropBits]scoreAdjustTyp
277 var paramFlagToPositiveAdj map[ParamPropBits]scoreAdjustTyp
278
279 func setupFlagToAdjMaps() {
280         resultFlagToPositiveAdj = map[ResultPropBits]scoreAdjustTyp{
281                 ResultIsAllocatedMem:     returnFeedsConcreteToInterfaceCallAdj,
282                 ResultAlwaysSameFunc:     returnFeedsFuncToIndCallAdj,
283                 ResultAlwaysSameConstant: returnFeedsConstToIfAdj,
284         }
285         paramFlagToPositiveAdj = map[ParamPropBits]scoreAdjustTyp{
286                 ParamMayFeedInterfaceMethodCall: passConcreteToNestedItfCallAdj,
287                 ParamFeedsInterfaceMethodCall:   passConcreteToItfCallAdj,
288                 ParamMayFeedIndirectCall:        passInlinableFuncToNestedIndCallAdj,
289                 ParamFeedsIndirectCall:          passInlinableFuncToIndCallAdj,
290         }
291 }
292
293 // largestScoreAdjustment tries to estimate the largest possible
294 // negative score adjustment that could be applied to a call of the
295 // function with the specified props. Example:
296 //
297 //      func foo() {                  func bar(x int, p *int) int {
298 //         ...                          if x < 0 { *p = x }
299 //      }                               return 99
300 //                                    }
301 //
302 // Function 'foo' above on the left has no interesting properties,
303 // thus as a result the most we'll adjust any call to is the value for
304 // "call in loop". If the calculated cost of the function is 150, and
305 // the in-loop adjustment is 5 (for example), then there is not much
306 // point treating it as inlinable. On the other hand "bar" has a param
307 // property (parameter "x" feeds unmodified to an "if" statement") and
308 // a return property (always returns same constant) meaning that a
309 // given call _could_ be rescored down as much as -35 points-- thus if
310 // the size of "bar" is 100 (for example) then there is at least a
311 // chance that scoring will enable inlining.
312 func largestScoreAdjustment(fn *ir.Func, props *FuncProps) int {
313         if resultFlagToPositiveAdj == nil {
314                 setupFlagToAdjMaps()
315         }
316         var tmask scoreAdjustTyp
317         score := adjValues[inLoopAdj] // any call can be in a loop
318         for _, pf := range props.ParamFlags {
319                 if adj, ok := paramFlagToPositiveAdj[pf]; ok {
320                         score, tmask = adjustScore(adj, score, tmask)
321                 }
322         }
323         for _, rf := range props.ResultFlags {
324                 if adj, ok := resultFlagToPositiveAdj[rf]; ok {
325                         score, tmask = adjustScore(adj, score, tmask)
326                 }
327         }
328
329         if debugTrace&debugTraceScoring != 0 {
330                 fmt.Fprintf(os.Stderr, "=-= largestScore(%v) is %d\n",
331                         fn, score)
332         }
333
334         return score
335 }
336
337 // callSiteTab contains entries for each call in the function
338 // currently being processed by InlineCalls; this variable will either
339 // be set to 'cstabCache' below (for non-inlinable routines) or to the
340 // local 'cstab' entry in the fnInlHeur object for inlinable routines.
341 //
342 // NOTE: this assumes that inlining operations are happening in a serial,
343 // single-threaded fashion,f which is true today but probably won't hold
344 // in the future (for example, we might want to score the callsites
345 // in multiple functions in parallel); if the inliner evolves in this
346 // direction we'll need to come up with a different approach here.
347 var callSiteTab CallSiteTab
348
349 // scoreCallsCache caches a call site table and call site list between
350 // invocations of ScoreCalls so that we can reuse previously allocated
351 // storage.
352 var scoreCallsCache scoreCallsCacheType
353
354 type scoreCallsCacheType struct {
355         tab CallSiteTab
356         csl []*CallSite
357 }
358
359 // ScoreCalls assigns numeric scores to each of the callsites in
360 // function 'fn'; the lower the score, the more helpful we think it
361 // will be to inline.
362 //
363 // Unlike a lot of the other inline heuristics machinery, callsite
364 // scoring can't be done as part of the CanInline call for a function,
365 // due to fact that we may be working on a non-trivial SCC. So for
366 // example with this SCC:
367 //
368 //      func foo(x int) {           func bar(x int, f func()) {
369 //        if x != 0 {                  f()
370 //          bar(x, func(){})           foo(x-1)
371 //        }                         }
372 //      }
373 //
374 // We don't want to perform scoring for the 'foo' call in "bar" until
375 // after foo has been analyzed, but it's conceivable that CanInline
376 // might visit bar before foo for this SCC.
377 func ScoreCalls(fn *ir.Func) {
378         enableDebugTraceIfEnv()
379
380         if debugTrace&debugTraceScoring != 0 {
381                 fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
382         }
383
384         // If this is an inlinable function, use the precomputed
385         // call site table for it. If the function wasn't an inline
386         // candidate, collect a callsite table for it now.
387         var cstab CallSiteTab
388         if funcInlHeur, ok := fpmap[fn]; ok {
389                 cstab = funcInlHeur.cstab
390         } else {
391                 if len(scoreCallsCache.tab) != 0 {
392                         panic("missing call to ScoreCallsCleanup")
393                 }
394                 if scoreCallsCache.tab == nil {
395                         scoreCallsCache.tab = make(CallSiteTab)
396                 }
397                 if debugTrace&debugTraceScoring != 0 {
398                         fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
399                                 ir.FuncName(fn))
400                 }
401                 cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0)
402         }
403
404         scoreCallsRegion(fn, fn.Body, cstab)
405 }
406
407 // scoreCallsRegion assigns numeric scores to each of the callsites in
408 // region 'region' within function 'fn'. This can be called on
409 // an entire function, or with 'region' set to a chunk of
410 // code corresponding to an inlined call.
411 func scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab) {
412         if debugTrace&debugTraceScoring != 0 {
413                 fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s)\n",
414                         ir.FuncName(fn), region[0].Op().String())
415         }
416
417         resultNameTab := make(map[*ir.Name]resultPropAndCS)
418
419         // Sort callsites to avoid any surprises with non deterministic
420         // map iteration order (this is probably not needed, but here just
421         // in case).
422         csl := scoreCallsCache.csl[:0]
423         for _, cs := range cstab {
424                 csl = append(csl, cs)
425         }
426         scoreCallsCache.csl = csl[:0]
427         sort.Slice(csl, func(i, j int) bool {
428                 return csl[i].ID < csl[j].ID
429         })
430
431         // Score each call site.
432         for _, cs := range csl {
433                 var cprops *FuncProps
434                 fihcprops := false
435                 desercprops := false
436                 if funcInlHeur, ok := fpmap[cs.Callee]; ok {
437                         cprops = funcInlHeur.props
438                         fihcprops = true
439                 } else if cs.Callee.Inl != nil {
440                         cprops = DeserializeFromString(cs.Callee.Inl.Properties)
441                         desercprops = true
442                 } else {
443                         if base.Debug.DumpInlFuncProps != "" {
444                                 fmt.Fprintf(os.Stderr, "=-= *** unable to score call to %s from %s\n", cs.Callee.Sym().Name, fmtFullPos(cs.Call.Pos()))
445                                 panic("should never happen")
446                         } else {
447                                 continue
448                         }
449                 }
450                 cs.Score, cs.ScoreMask = computeCallSiteScore(cs.Callee, cprops, cs.Call, cs.Flags)
451
452                 examineCallResults(cs, resultNameTab)
453
454                 if debugTrace&debugTraceScoring != 0 {
455                         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)
456                 }
457         }
458
459         rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
460
461         disableDebugTrace()
462
463         callSiteTab = cstab
464 }
465
466 // ScoreCallsCleanup resets the state of the callsite cache
467 // once ScoreCalls is done with a function.
468 func ScoreCallsCleanup() {
469         if base.Debug.DumpInlCallSiteScores != 0 {
470                 if allCallSites == nil {
471                         allCallSites = make(CallSiteTab)
472                 }
473                 for call, cs := range callSiteTab {
474                         allCallSites[call] = cs
475                 }
476         }
477         for k := range scoreCallsCache.tab {
478                 delete(scoreCallsCache.tab, k)
479         }
480 }
481
482 // GetCallSiteScore returns the previously calculated score for call
483 // within fn.
484 func GetCallSiteScore(fn *ir.Func, call *ir.CallExpr) (int, bool) {
485         if funcInlHeur, ok := fpmap[fn]; ok {
486                 if cs, ok := funcInlHeur.cstab[call]; ok {
487                         return cs.Score, true
488                 }
489         }
490         if cs, ok := callSiteTab[call]; ok {
491                 return cs.Score, true
492         }
493         return 0, false
494 }
495
496 var allCallSites CallSiteTab
497
498 // DumpInlCallSiteScores is invoked by the inliner if the debug flag
499 // "-d=dumpinlcallsitescores" is set; it dumps out a human-readable
500 // summary of all (potentially) inlinable callsites in the package,
501 // along with info on call site scoring and the adjustments made to a
502 // given score. Here profile is the PGO profile in use (may be
503 // nil), budgetCallback is a callback that can be invoked to find out
504 // the original pre-adjustment hairyness limit for the function, and
505 // inlineHotMaxBudget is the constant of the same name used in the
506 // inliner. Sample output lines:
507 //
508 // Score  Adjustment  Status  Callee  CallerPos ScoreFlags
509 // 115    40          DEMOTED cmd/compile/internal/abi.(*ABIParamAssignment).Offset     expand_calls.go:1679:14|6       panicPathAdj
510 // 76     -5n           PROMOTED runtime.persistentalloc   mcheckmark.go:48:45|3   inLoopAdj
511 // 201    0           --- PGO  unicode.DecodeRuneInString        utf8.go:312:30|1
512 // 7      -5          --- PGO  internal/abi.Name.DataChecked     type.go:625:22|0        inLoopAdj
513 //
514 // In the dump above, "Score" is the final score calculated for the
515 // callsite, "Adjustment" is the amount added to or subtracted from
516 // the original hairyness estimate to form the score. "Status" shows
517 // whether anything changed with the site -- did the adjustment bump
518 // it down just below the threshold ("PROMOTED") or instead bump it
519 // above the threshold ("DEMOTED"); this will be blank ("---") if no
520 // threshold was crossed as a result of the heuristics. Note that
521 // "Status" also shows whether PGO was involved. "Callee" is the name
522 // of the function called, "CallerPos" is the position of the
523 // callsite, and "ScoreFlags" is a digest of the specific properties
524 // we used to make adjustments to callsite score via heuristics.
525 func DumpInlCallSiteScores(profile *pgo.Profile, budgetCallback func(fn *ir.Func, profile *pgo.Profile) (int32, bool)) {
526
527         genstatus := func(cs *CallSite, prof *pgo.Profile) string {
528                 hairyval := cs.Callee.Inl.Cost
529                 bud, isPGO := budgetCallback(cs.Callee, prof)
530                 score := int32(cs.Score)
531                 st := "---"
532
533                 switch {
534                 case hairyval <= bud && score <= bud:
535                         // "Normal" inlined case: hairy val sufficiently low that
536                         // it would have been inlined anyway without heuristics.
537                 case hairyval > bud && score > bud:
538                         // "Normal" not inlined case: hairy val sufficiently high
539                         // and scoring didn't lower it.
540                 case hairyval > bud && score <= bud:
541                         // Promoted: we would not have inlined it before, but
542                         // after score adjustment we decided to inline.
543                         st = "PROMOTED"
544                 case hairyval <= bud && score > bud:
545                         // Demoted: we would have inlined it before, but after
546                         // score adjustment we decided not to inline.
547                         st = "DEMOTED"
548                 }
549                 if isPGO {
550                         st += " PGO"
551                 }
552                 return st
553         }
554
555         if base.Debug.DumpInlCallSiteScores != 0 {
556                 var sl []*CallSite
557                 for _, cs := range allCallSites {
558                         sl = append(sl, cs)
559                 }
560                 sort.Slice(sl, func(i, j int) bool {
561                         if sl[i].Score != sl[j].Score {
562                                 return sl[i].Score < sl[j].Score
563                         }
564                         fni := ir.PkgFuncName(sl[i].Callee)
565                         fnj := ir.PkgFuncName(sl[j].Callee)
566                         if fni != fnj {
567                                 return fni < fnj
568                         }
569                         ecsi := EncodeCallSiteKey(sl[i])
570                         ecsj := EncodeCallSiteKey(sl[j])
571                         return ecsi < ecsj
572                 })
573
574                 mkname := func(fn *ir.Func) string {
575                         var n string
576                         if fn == nil || fn.Nname == nil {
577                                 return "<nil>"
578                         }
579                         if fn.Sym().Pkg == types.LocalPkg {
580                                 n = "ยท" + fn.Sym().Name
581                         } else {
582                                 n = ir.PkgFuncName(fn)
583                         }
584                         // don't try to print super-long names
585                         if len(n) <= 64 {
586                                 return n
587                         }
588                         return n[:32] + "..." + n[len(n)-32:len(n)]
589                 }
590
591                 if len(sl) != 0 {
592                         fmt.Fprintf(os.Stdout, "# scores for package %s\n", types.LocalPkg.Path)
593                         fmt.Fprintf(os.Stdout, "# Score  Adjustment  Status  Callee  CallerPos Flags ScoreFlags\n")
594                 }
595                 for _, cs := range sl {
596                         hairyval := cs.Callee.Inl.Cost
597                         adj := int32(cs.Score) - hairyval
598                         fmt.Fprintf(os.Stdout, "%d  %d\t%s\t%s\t%s\t%s\n",
599                                 cs.Score, adj, genstatus(cs, profile),
600                                 mkname(cs.Callee),
601                                 EncodeCallSiteKey(cs),
602                                 cs.ScoreMask.String())
603                 }
604         }
605 }