Skip to content

Commit ba9fc48

Browse files
committed
cmd/compile: boost inlining into FORs
As already Than McIntosh mentioned it's a common practise to boost inlining to FORs, since the callsite could be "hotter". This patch implements this functionality. The implementation uses a stack of FORs to recognise calls which are in a loop. The stack is maintained alongside inlnode function works and contains information about ancenstor FORs relative to a current node in inlnode. There is "big" FOR which cost is >= inlineBigForCost(105). In such FORs no boost is applied. Updates #17566 The following results on GO1, while binary size not increased significantly 10454800 -> 10475120, which is less than 0.3%. goos: linux goarch: amd64 pkg: test/bench/go1 cpu: Intel(R) Xeon(R) Gold 6230N CPU @ 2.30GHz name old time/op new time/op delta BinaryTree17-8 2.15s ± 1% 2.17s ± 1% ~ (p=0.065 n=6+6) Fannkuch11-8 2.70s ± 0% 2.69s ± 0% -0.25% (p=0.010 n=6+4) FmtFprintfEmpty-8 31.9ns ± 0% 31.4ns ± 0% -1.61% (p=0.008 n=5+5) FmtFprintfString-8 57.0ns ± 0% 57.1ns ± 0% +0.26% (p=0.013 n=6+5) FmtFprintfInt-8 65.2ns ± 0% 63.9ns ± 0% -1.95% (p=0.008 n=5+5) FmtFprintfIntInt-8 103ns ± 0% 102ns ± 0% -1.01% (p=0.000 n=5+4) FmtFprintfPrefixedInt-8 119ns ± 0% 118ns ± 0% -0.50% (p=0.008 n=5+5) FmtFprintfFloat-8 169ns ± 0% 174ns ± 0% +2.75% (p=0.008 n=5+5) FmtManyArgs-8 445ns ± 0% 447ns ± 0% +0.46% (p=0.002 n=6+6) GobDecode-8 4.37ms ± 1% 4.40ms ± 0% +0.62% (p=0.009 n=6+6) GobEncode-8 3.07ms ± 0% 3.04ms ± 0% -0.78% (p=0.004 n=5+6) Gzip-8 195ms ± 0% 195ms ± 0% ~ (p=0.429 n=5+6) Gunzip-8 28.2ms ± 0% 28.2ms ± 0% ~ (p=0.662 n=5+6) HTTPClientServer-8 45.0µs ± 1% 45.4µs ± 1% ~ (p=0.093 n=6+6) JSONEncode-8 8.01ms ± 0% 8.03ms ± 0% +0.31% (p=0.008 n=5+5) JSONDecode-8 35.3ms ± 1% 35.1ms ± 0% -0.72% (p=0.008 n=5+5) Mandelbrot200-8 4.50ms ± 0% 4.49ms ± 1% ~ (p=0.937 n=6+6) GoParse-8 3.03ms ± 1% 3.00ms ± 1% ~ (p=0.180 n=6+6) RegexpMatchEasy0_32-8 55.4ns ± 0% 53.2ns ± 3% -3.92% (p=0.004 n=5+6) RegexpMatchEasy0_1K-8 178ns ± 0% 175ns ± 1% -1.57% (p=0.004 n=5+6) RegexpMatchEasy1_32-8 50.1ns ± 0% 48.3ns ± 5% ~ (p=0.082 n=5+6) RegexpMatchEasy1_1K-8 271ns ± 1% 262ns ± 1% -3.26% (p=0.004 n=6+5) RegexpMatchMedium_32-8 949ns ± 0% 886ns ± 7% ~ (p=0.329 n=5+6) RegexpMatchMedium_1K-8 27.1µs ± 7% 28.1µs ± 6% ~ (p=0.394 n=6+6) RegexpMatchHard_32-8 1.28µs ± 2% 1.29µs ± 0% ~ (p=0.056 n=6+6) RegexpMatchHard_1K-8 38.5µs ± 0% 38.4µs ± 0% -0.25% (p=0.009 n=6+5) Revcomp-8 397ms ± 0% 396ms ± 0% ~ (p=0.429 n=6+5) Template-8 48.1ms ± 1% 48.1ms ± 0% ~ (p=0.222 n=5+5) TimeParse-8 213ns ± 0% 213ns ± 0% ~ (p=0.210 n=4+6) TimeFormat-8 295ns ± 1% 259ns ± 0% -12.22% (p=0.002 n=6+6) [Geo mean] 40.5µs 40.1µs -1.00% name old speed new speed delta GobDecode-8 176MB/s ± 1% 174MB/s ± 0% -0.61% (p=0.009 n=6+6) GobEncode-8 250MB/s ± 0% 252MB/s ± 0% +0.79% (p=0.004 n=5+6) Gzip-8 100MB/s ± 0% 100MB/s ± 0% ~ (p=0.351 n=5+6) Gunzip-8 687MB/s ± 0% 687MB/s ± 0% ~ (p=0.662 n=5+6) JSONEncode-8 242MB/s ± 0% 242MB/s ± 0% -0.31% (p=0.008 n=5+5) JSONDecode-8 54.9MB/s ± 1% 55.3MB/s ± 0% +0.71% (p=0.008 n=5+5) GoParse-8 19.1MB/s ± 1% 19.3MB/s ± 1% ~ (p=0.143 n=6+6) RegexpMatchEasy0_32-8 578MB/s ± 0% 601MB/s ± 3% +4.10% (p=0.004 n=5+6) RegexpMatchEasy0_1K-8 5.74GB/s ± 1% 5.85GB/s ± 1% +1.90% (p=0.002 n=6+6) RegexpMatchEasy1_32-8 639MB/s ± 0% 663MB/s ± 4% ~ (p=0.082 n=5+6) RegexpMatchEasy1_1K-8 3.78GB/s ± 1% 3.91GB/s ± 1% +3.38% (p=0.004 n=6+5) RegexpMatchMedium_32-8 33.7MB/s ± 0% 36.2MB/s ± 7% ~ (p=0.268 n=5+6) RegexpMatchMedium_1K-8 37.9MB/s ± 6% 36.5MB/s ± 6% ~ (p=0.411 n=6+6) RegexpMatchHard_32-8 24.9MB/s ± 2% 24.8MB/s ± 0% ~ (p=0.063 n=6+6) RegexpMatchHard_1K-8 26.6MB/s ± 0% 26.7MB/s ± 0% +0.25% (p=0.009 n=6+5) Revcomp-8 640MB/s ± 0% 641MB/s ± 0% ~ (p=0.429 n=6+5) Template-8 40.4MB/s ± 1% 40.3MB/s ± 0% ~ (p=0.222 n=5+5) [Geo mean] 175MB/s 177MB/s +1.05%
1 parent d0dd26a commit ba9fc48

File tree

3 files changed

+267
-51
lines changed

3 files changed

+267
-51
lines changed

src/cmd/compile/internal/inline/inl.go

+174-51
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,107 @@ const (
4545
inlineMaxBudget = 80
4646
inlineExtraAppendCost = 0
4747
// default is to inline if there's at most one call. -l=4 overrides this by using 1 instead.
48-
inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
48+
inlineExtraCallCost = 57 // 57 was benchmarked to provide most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
4949
inlineExtraPanicCost = 1 // do not penalize inlining panics.
5050
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
5151

5252
inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
5353
inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
54+
55+
// These values were benchmarked to provide most benefit with no bad surprises.
56+
inlineBigForCost = 105 // FORs with at least this cost are considered "big".
57+
inlineIntoForExtraCallCost = 14
58+
inlineIntoForExtraBudget = 18 // Extra budget when inlining into FORs which are not "big".
59+
60+
// The upper budget for a visitor. It accounts the maximum cost with which a function could be inlined.
61+
inlineVisitorBudget = inlineMaxBudget + inlineIntoForExtraBudget
5462
)
5563

64+
// isInlinable checks if the function can be inlined in a 'typical' scenario
65+
// when no boosts are applied.
66+
func isInlinable(fn *ir.Func) bool {
67+
return fn != nil && fn.Inl != nil && fn.Inl.Cost <= inlineMaxBudget
68+
}
69+
70+
type forContext struct {
71+
cost int32 // Helps to determine if FOR is a "big" one.
72+
}
73+
74+
type inlContext struct {
75+
// Map to keep track of functions that have been inlined at a particular
76+
// call site, in order to stop inlining when we reach the beginning of a
77+
// recursion cycle again. We don't inline immediately recursive functions,
78+
// but allow inlining if there is a recursion cycle of many functions.
79+
// Most likely, the inlining will stop before we even hit the beginning of
80+
// the cycle again, but the map catches the unusual case.
81+
inlinedCallees map[*ir.Func]bool
82+
83+
// Stack to recognise which call nodes are located inside fors, while doing inlnode.
84+
forsStack []forContext
85+
initialInlineBudget int32 // Initial inline budget. Boosts are calculated related to this.
86+
}
87+
88+
// Current decision is made on whether all FORs in current scope are not "big".
89+
func (ctx inlContext) canBoostInliningIntoFor() bool {
90+
for i := 0; i < len(ctx.forsStack); i++ {
91+
if ctx.forsStack[i].cost >= inlineBigForCost {
92+
return false
93+
}
94+
}
95+
return len(ctx.forsStack) > 0
96+
}
97+
98+
func (ctx *inlContext) Init(fn *ir.Func) {
99+
ctx.inlinedCallees = make(map[*ir.Func]bool)
100+
101+
if isBigFunc(fn) {
102+
ctx.initialInlineBudget = inlineBigFunctionMaxCost
103+
} else {
104+
ctx.initialInlineBudget = inlineMaxBudget
105+
}
106+
}
107+
108+
func (ctx *inlContext) PushFor(n ir.Node) {
109+
ctx.forsStack = append(ctx.forsStack, forContext{forCost(n)})
110+
111+
if base.Flag.LowerM > 1 {
112+
fmt.Printf("%v: add FOR to stack %v\n", ir.Line(n), ctx.forsStack)
113+
}
114+
}
115+
116+
func (ctx *inlContext) PopFor() {
117+
ctx.forsStack = ctx.forsStack[:len(ctx.forsStack)-1]
118+
}
119+
120+
func (ctx inlContext) InlineBudget() int32 {
121+
finalBudget := ctx.initialInlineBudget
122+
if ctx.canBoostInliningIntoFor() && ctx.initialInlineBudget == inlineMaxBudget {
123+
// Boosts only regular functions
124+
finalBudget += inlineIntoForExtraBudget
125+
}
126+
127+
return finalBudget
128+
}
129+
130+
// forCost calculates the cost of FORs. It is used to determine if functions
131+
// will be boosted to inline into the FOR.
132+
// We don't want to boost inlining into "big" FORs to keep their body
133+
// in the instruction cache.
134+
func forCost(n ir.Node) int32 {
135+
exceededCostReason := func(remainingBudget int32) string {
136+
return fmt.Sprintf("FOR is big: cost %d exceeds maximum cost %d", inlineBigForCost-remainingBudget, inlineBigForCost)
137+
}
138+
139+
visitor := hairyVisitor{
140+
budget: inlineBigForCost,
141+
extraCallCost: inlineIntoForExtraCallCost,
142+
onlyCost: true,
143+
exceededCostReasonCallback: exceededCostReason,
144+
}
145+
visitor.tooHairy(n)
146+
return inlineBigForCost - visitor.budget
147+
}
148+
56149
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
57150
func InlinePackage() {
58151
ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
@@ -166,30 +259,40 @@ func CanInline(fn *ir.Func) {
166259
// locals, and we use this map to produce a pruned Inline.Dcl
167260
// list. See issue 25249 for more context.
168261

262+
exceededCostReason := func(remainingBudget int32) string {
263+
return fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineVisitorBudget-remainingBudget, inlineVisitorBudget)
264+
}
265+
169266
visitor := hairyVisitor{
170-
budget: inlineMaxBudget,
171-
extraCallCost: cc,
267+
budget: inlineVisitorBudget,
268+
extraCallCost: cc,
269+
onlyCost: false,
270+
exceededCostReasonCallback: exceededCostReason,
172271
}
173272
if visitor.tooHairy(fn) {
174273
reason = visitor.reason
175274
return
176275
}
177276

178277
n.Func.Inl = &ir.Inline{
179-
Cost: inlineMaxBudget - visitor.budget,
278+
Cost: inlineVisitorBudget - visitor.budget,
180279
Dcl: pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
181280
Body: inlcopylist(fn.Body),
182281

183282
CanDelayResults: canDelayResults(fn),
184283
}
185284

186285
if base.Flag.LowerM > 1 {
187-
fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, inlineMaxBudget-visitor.budget, fn.Type(), ir.Nodes(n.Func.Inl.Body))
188-
} else if base.Flag.LowerM != 0 {
286+
if isInlinable(n.Func) {
287+
fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, n.Func.Inl.Cost, fn.Type(), ir.Nodes(n.Func.Inl.Body))
288+
} else {
289+
fmt.Printf("%v: can inline only into small FORs %v with cost %d as: %v { %v }\n", ir.Line(fn), n, n.Func.Inl.Cost, fn.Type(), ir.Nodes(n.Func.Inl.Body))
290+
}
291+
} else if base.Flag.LowerM != 0 && isInlinable(n.Func) {
189292
fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
190293
}
191294
if logopt.Enabled() {
192-
logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget))
295+
logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", n.Func.Inl.Cost))
193296
}
194297
}
195298

@@ -228,20 +331,22 @@ func canDelayResults(fn *ir.Func) bool {
228331
// hairyVisitor visits a function body to determine its inlining
229332
// hairiness and whether or not it can be inlined.
230333
type hairyVisitor struct {
231-
budget int32
232-
reason string
233-
extraCallCost int32
234-
usedLocals ir.NameSet
235-
do func(ir.Node) bool
334+
budget int32
335+
extraCallCost int32
336+
onlyCost bool // If set, tooHairy does NOT check inlinible nodes, only cost.
337+
reason string
338+
usedLocals ir.NameSet
339+
do func(ir.Node) bool
340+
exceededCostReasonCallback func(remainingBudget int32) string
236341
}
237342

238-
func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
343+
func (v *hairyVisitor) tooHairy(n ir.Node) bool {
239344
v.do = v.doNode // cache closure
240-
if ir.DoChildren(fn, v.do) {
345+
if ir.DoChildren(n, v.do) {
241346
return true
242347
}
243348
if v.budget < 0 {
244-
v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget)
349+
v.reason = v.exceededCostReasonCallback(v.budget)
245350
return true
246351
}
247352
return false
@@ -264,8 +369,12 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
264369
if name.Class == ir.PFUNC && types.IsRuntimePkg(name.Sym().Pkg) {
265370
fn := name.Sym().Name
266371
if fn == "getcallerpc" || fn == "getcallersp" {
267-
v.reason = "call to " + fn
268-
return true
372+
if !v.onlyCost {
373+
v.reason = "call to " + fn
374+
return true
375+
} else {
376+
break
377+
}
269378
}
270379
if fn == "throw" {
271380
v.budget -= inlineExtraThrowCost
@@ -292,7 +401,7 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
292401
break
293402
}
294403

295-
if fn := inlCallee(n.X); fn != nil && fn.Inl != nil {
404+
if fn := inlCallee(n.X); isInlinable(fn) {
296405
v.budget -= fn.Inl.Cost
297406
break
298407
}
@@ -321,13 +430,19 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
321430
case ir.ORECOVER:
322431
// recover matches the argument frame pointer to find
323432
// the right panic value, so it needs an argument frame.
324-
v.reason = "call to recover"
325-
return true
433+
if !v.onlyCost {
434+
v.reason = "call to recover"
435+
return true
436+
}
326437

327438
case ir.OCLOSURE:
328439
if base.Debug.InlFuncsWithClosures == 0 {
329-
v.reason = "not inlining functions with closures"
330-
return true
440+
if !v.onlyCost {
441+
v.reason = "not inlining functions with closures"
442+
return true
443+
} else {
444+
break
445+
}
331446
}
332447

333448
// TODO(danscales): Maybe make budget proportional to number of closure
@@ -338,7 +453,9 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
338453
// do) to check for disallowed ops in the body and include the
339454
// body in the budget.
340455
if doList(n.(*ir.ClosureExpr).Func.Body, v.do) {
341-
return true
456+
if !v.onlyCost {
457+
return true
458+
}
342459
}
343460

344461
case ir.ORANGE,
@@ -347,8 +464,10 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
347464
ir.ODEFER,
348465
ir.ODCLTYPE, // can't print yet
349466
ir.OTAILCALL:
350-
v.reason = "unhandled op " + n.Op().String()
351-
return true
467+
if !v.onlyCost {
468+
v.reason = "unhandled op " + n.Op().String()
469+
return true
470+
}
352471

353472
case ir.OAPPEND:
354473
v.budget -= inlineExtraAppendCost
@@ -376,22 +495,28 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
376495
case ir.OFOR, ir.OFORUNTIL:
377496
n := n.(*ir.ForStmt)
378497
if n.Label != nil {
379-
v.reason = "labeled control"
380-
return true
498+
if !v.onlyCost {
499+
v.reason = "labeled control"
500+
return true
501+
}
381502
}
382503
case ir.OSWITCH:
383504
n := n.(*ir.SwitchStmt)
384505
if n.Label != nil {
385-
v.reason = "labeled control"
386-
return true
506+
if !v.onlyCost {
507+
v.reason = "labeled control"
508+
return true
509+
}
387510
}
388511
// case ir.ORANGE, ir.OSELECT in "unhandled" above
389512

390513
case ir.OBREAK, ir.OCONTINUE:
391514
n := n.(*ir.BranchStmt)
392515
if n.Label != nil {
393-
// Should have short-circuited due to labeled control error above.
394-
base.Fatalf("unexpected labeled break/continue: %v", n)
516+
if !v.onlyCost {
517+
// Should have short-circuited due to labeled control error above.
518+
base.Fatalf("unexpected labeled break/continue: %v", n)
519+
}
395520
}
396521

397522
case ir.OIF:
@@ -497,20 +622,13 @@ func inlcopy(n ir.Node) ir.Node {
497622
func InlineCalls(fn *ir.Func) {
498623
savefn := ir.CurFunc
499624
ir.CurFunc = fn
500-
maxCost := int32(inlineMaxBudget)
501-
if isBigFunc(fn) {
502-
maxCost = inlineBigFunctionMaxCost
503-
}
504-
// Map to keep track of functions that have been inlined at a particular
505-
// call site, in order to stop inlining when we reach the beginning of a
506-
// recursion cycle again. We don't inline immediately recursive functions,
507-
// but allow inlining if there is a recursion cycle of many functions.
508-
// Most likely, the inlining will stop before we even hit the beginning of
509-
// the cycle again, but the map catches the unusual case.
510-
inlMap := make(map[*ir.Func]bool)
625+
626+
var inlCtx inlContext
627+
inlCtx.Init(fn)
628+
511629
var edit func(ir.Node) ir.Node
512630
edit = func(n ir.Node) ir.Node {
513-
return inlnode(n, maxCost, inlMap, edit)
631+
return inlnode(n, &inlCtx, edit)
514632
}
515633
ir.EditChildren(fn, edit)
516634
ir.CurFunc = savefn
@@ -529,11 +647,16 @@ func InlineCalls(fn *ir.Func) {
529647
// shorter and less complicated.
530648
// The result of inlnode MUST be assigned back to n, e.g.
531649
// n.Left = inlnode(n.Left)
532-
func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
650+
func inlnode(n ir.Node, ctx *inlContext, edit func(ir.Node) ir.Node) ir.Node {
533651
if n == nil {
534652
return n
535653
}
536654

655+
if n.Op() == ir.OFOR {
656+
ctx.PushFor(n)
657+
defer ctx.PopFor()
658+
}
659+
537660
switch n.Op() {
538661
case ir.ODEFER, ir.OGO:
539662
n := n.(*ir.GoDeferStmt)
@@ -591,7 +714,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
591714
break
592715
}
593716
if fn := inlCallee(call.X); fn != nil && fn.Inl != nil {
594-
n = mkinlcall(call, fn, maxCost, inlMap, edit)
717+
n = mkinlcall(call, fn, ctx, edit)
595718
}
596719
}
597720

@@ -664,20 +787,20 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa
664787
// parameters.
665788
// The result of mkinlcall MUST be assigned back to n, e.g.
666789
// n.Left = mkinlcall(n.Left, fn, isddd)
667-
func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
790+
func mkinlcall(n *ir.CallExpr, fn *ir.Func, ctx *inlContext, edit func(ir.Node) ir.Node) ir.Node {
668791
if fn.Inl == nil {
669792
if logopt.Enabled() {
670793
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
671794
fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(fn)))
672795
}
673796
return n
674797
}
675-
if fn.Inl.Cost > maxCost {
798+
if fn.Inl.Cost > ctx.InlineBudget() {
676799
// The inlined function body is too big. Typically we use this check to restrict
677800
// inlining into very big functions. See issue 26546 and 17566.
678801
if logopt.Enabled() {
679802
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
680-
fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Inl.Cost, ir.PkgFuncName(fn), maxCost))
803+
fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Inl.Cost, ir.PkgFuncName(fn), ctx.InlineBudget()))
681804
}
682805
return n
683806
}
@@ -700,15 +823,15 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
700823
return n
701824
}
702825

703-
if inlMap[fn] {
826+
if ctx.inlinedCallees[fn] {
704827
if base.Flag.LowerM > 1 {
705828
fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc))
706829
}
707830
return n
708831
}
709-
inlMap[fn] = true
832+
ctx.inlinedCallees[fn] = true
710833
defer func() {
711-
inlMap[fn] = false
834+
ctx.inlinedCallees[fn] = false
712835
}()
713836

714837
typecheck.FixVariadicCall(n)

0 commit comments

Comments
 (0)