Skip to content

Commit 6a801d3

Browse files
mdempskygopherbot
authored andcommitted
cmd/compile/internal/noder: fix inlined function literal positions
When inlining function calls, we rewrite the position information on all of the nodes to keep track of the inlining context. This is necessary so that at runtime, we can synthesize additional stack frames so that the inlining is transparent to the user. However, for function literals, we *don't* want to apply this rewriting to the underlying function. Because within the function literal (when it's not itself inlined), the inlining context (if any) will have already be available at the caller PC instead. Unified IR was already getting this right in the case of user-written statements within the function literal, which is what the unit test for #46234 tested. However, it was still using inline-adjusted positions for the function declaration and its parameters, which occasionally end up getting used for generated code (e.g., loading captured values from the closure record). I've manually verified that this fixes the hang in https://go.dev/play/p/avQ0qgRzOgt, and spot-checked the -d=pctab=pctoinline output for kube-apiserver and kubelet and they seem better. However, I'm still working on a more robust test for this (hence "Updates" not "Fixes") and internal assertions to verify that we're emitting correct inline trees. In particular, there are still other cases (even in the non-unified frontend) where we're producing corrupt (but at least acyclic) inline trees. Updates #54625. Change-Id: Iacfd2e1eb06ae8dc299c0679f377461d3d46c15a Reviewed-on: https://go-review.googlesource.com/c/go/+/425395 Run-TryBot: Matthew Dempsky <mdempsky@google.com> Auto-Submit: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: David Chase <drchase@google.com>
1 parent 396b153 commit 6a801d3

File tree

3 files changed

+48
-13
lines changed

3 files changed

+48
-13
lines changed

src/cmd/compile/internal/noder/reader.go

+45-10
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ type reader struct {
136136
inlTreeIndex int
137137
inlPosBases map[*src.PosBase]*src.PosBase
138138

139+
// suppressInlPos tracks whether position base rewriting for
140+
// inlining should be suppressed. See funcLit.
141+
suppressInlPos int
142+
139143
delayResults bool
140144

141145
// Label to return to.
@@ -286,9 +290,15 @@ func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) *src.PosBase {
286290
return b
287291
}
288292

289-
// TODO(mdempsky): Document this.
293+
// inlPosBase returns the inlining-adjusted src.PosBase corresponding
294+
// to oldBase, which must be a non-inlined position. When not
295+
// inlining, this is just oldBase.
290296
func (r *reader) inlPosBase(oldBase *src.PosBase) *src.PosBase {
291-
if r.inlCall == nil {
297+
if index := oldBase.InliningIndex(); index >= 0 {
298+
base.Fatalf("oldBase %v already has inlining index %v", oldBase, index)
299+
}
300+
301+
if r.inlCall == nil || r.suppressInlPos != 0 {
292302
return oldBase
293303
}
294304

@@ -301,8 +311,10 @@ func (r *reader) inlPosBase(oldBase *src.PosBase) *src.PosBase {
301311
return newBase
302312
}
303313

304-
// TODO(mdempsky): Document this.
305-
func (r *reader) updatePos(xpos src.XPos) src.XPos {
314+
// inlPos returns the inlining-adjusted src.XPos corresponding to
315+
// xpos, which must be a non-inlined position. When not inlining, this
316+
// is just xpos.
317+
func (r *reader) inlPos(xpos src.XPos) src.XPos {
306318
pos := base.Ctxt.PosTable.Pos(xpos)
307319
pos.SetBase(r.inlPosBase(pos.Base()))
308320
return base.Ctxt.PosTable.XPos(pos)
@@ -1472,7 +1484,7 @@ func (r *reader) funcarg(param *types.Field, sym *types.Sym, ctxt ir.Class) {
14721484
return
14731485
}
14741486

1475-
name := ir.NewNameAt(r.updatePos(param.Pos), sym)
1487+
name := ir.NewNameAt(r.inlPos(param.Pos), sym)
14761488
setType(name, param.Type)
14771489
r.addLocal(name, ctxt)
14781490

@@ -2715,7 +2727,13 @@ func syntheticSig(sig *types.Type) (params, results []*types.Field) {
27152727
if sym == nil || sym.Name == "_" {
27162728
sym = typecheck.LookupNum(".anon", i)
27172729
}
2718-
res[i] = types.NewField(param.Pos, sym, param.Type)
2730+
// TODO(mdempsky): It would be nice to preserve the original
2731+
// parameter positions here instead, but at least
2732+
// typecheck.NewMethodType replaces them with base.Pos, making
2733+
// them useless. Worse, the positions copied from base.Pos may
2734+
// have inlining contexts, which we definitely don't want here
2735+
// (e.g., #54625).
2736+
res[i] = types.NewField(base.AutogeneratedPos, sym, param.Type)
27192737
res[i].SetIsDDD(param.IsDDD())
27202738
}
27212739
return res
@@ -2756,7 +2774,7 @@ func (r *reader) optExpr() ir.Node {
27562774
// otherwise, they need to create their own wrapper.
27572775
func (r *reader) methodExpr() (wrapperFn, baseFn, dictPtr ir.Node) {
27582776
recv := r.typ()
2759-
sig0 := r.signature(types.LocalPkg, nil)
2777+
sig0 := r.typ()
27602778
pos := r.pos()
27612779
_, sym := r.selector()
27622780

@@ -3019,13 +3037,30 @@ func wrapName(pos src.XPos, x ir.Node) ir.Node {
30193037
func (r *reader) funcLit() ir.Node {
30203038
r.Sync(pkgbits.SyncFuncLit)
30213039

3040+
// The underlying function declaration (including its parameters'
3041+
// positions, if any) need to remain the original, uninlined
3042+
// positions. This is because we track inlining-context on nodes so
3043+
// we can synthesize the extra implied stack frames dynamically when
3044+
// generating tracebacks, whereas those stack frames don't make
3045+
// sense *within* the function literal. (Any necessary inlining
3046+
// adjustments will have been applied to the call expression
3047+
// instead.)
3048+
//
3049+
// This is subtle, and getting it wrong leads to cycles in the
3050+
// inlining tree, which lead to infinite loops during stack
3051+
// unwinding (#46234, #54625).
3052+
//
3053+
// Note that we *do* want the inline-adjusted position for the
3054+
// OCLOSURE node, because that position represents where any heap
3055+
// allocation of the closure is credited (#49171).
3056+
r.suppressInlPos++
30223057
pos := r.pos()
30233058
xtype2 := r.signature(types.LocalPkg, nil)
3059+
r.suppressInlPos--
30243060

3025-
opos := pos
3026-
3027-
fn := ir.NewClosureFunc(opos, r.curfn != nil)
3061+
fn := ir.NewClosureFunc(pos, r.curfn != nil)
30283062
clo := fn.OClosure
3063+
clo.SetPos(r.inlPos(pos)) // see comment above
30293064
ir.NameClosure(clo, r.curfn)
30303065

30313066
setType(fn.Nname, xtype2)

src/cmd/compile/internal/noder/writer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1973,7 +1973,7 @@ func (w *writer) methodExpr(expr *syntax.SelectorExpr, recv types2.Type, sel *ty
19731973
sig := fun.Type().(*types2.Signature)
19741974

19751975
w.typ(recv)
1976-
w.signature(sig)
1976+
w.typ(sig)
19771977
w.pos(expr)
19781978
w.selector(fun)
19791979

test/inline_unified.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ func r(z int) int {
1313
return x + z
1414
}
1515
bar := func(x int) int { // ERROR "func literal does not escape" "can inline r.func2"
16-
return x + func(y int) int { // ERROR "can inline r.func2.1"
16+
return x + func(y int) int { // ERROR "can inline r.func2.1" "can inline r.func3"
1717
return 2*y + x*z
1818
}(x) // ERROR "inlining call to r.func2.1"
1919
}
20-
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "can inline r.func3" "inlining call to r.func3"
20+
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "inlining call to r.func3"
2121
}

0 commit comments

Comments
 (0)