Skip to content

Commit c06bb6c

Browse files
authored
don't traverse inner procs to lift locals in closure iters (#24876)
fixes #24863, refs #23787 and #24316 Working off the minimized example, my understanding of the issue is: `n` captures `r` as `:envP.r1` where `:envP` is the environment of `b`, then `proc () = n()` does the lambda lifting of `n` again (which isn't done if the `proc ()` is marked `{.closure.}`, hence the workaround) which then captures the `:envP` as another field inside the `:envP`, so it generates `:envP.:envP_2.r1` but the `.:envP_2` field is `nil`, so it causes a segfault. The problem is that the capture of `r` in `n` is done inside `detectCapturedVars` for the surrounding closure iterator: inner procs are not special cased and traversed as regular nodes, so it thinks it's inside the iterator and generates a field access of `:envP` freely. The lambda lifting version of `detectCapturedVars` ignores inner procs and works off of symbol uses (anonymous iterator and lambda declarations pretend their symbol is used). As a naive solution, closure iterators now also ignore inner proc declarations same as `lambdalifting.detectCapturedVars`, but unlike it they also don't do anything for the inner proc symbols. Lambdalifting seems to properly handle the lifted variables but in the worst case we can also make sure `closureiters.detectCapturedVars` traverses inner procs by marking every local of the closure iter used in them as needing lifting (but not doing the lifting). This does not seem necessary for now so it's not done (was done and reverted in [this commit](9bb39a9)), but regressions are still possible
1 parent 4d9e5e8 commit c06bb6c

File tree

4 files changed

+65
-1
lines changed

4 files changed

+65
-1
lines changed

compiler/closureiters.nim

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,14 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
14511451
detectCapturedVars(c, n[0][1], stateIdx)
14521452
else:
14531453
detectCapturedVars(c, n[0], stateIdx)
1454+
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
1455+
nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef,
1456+
nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt,
1457+
nkTypeOfExpr, nkMixinStmt, nkBindStmt:
1458+
discard
1459+
of nkLambdaKinds, nkIteratorDef:
1460+
if n.typ != nil:
1461+
detectCapturedVars(c, n[namePos], stateIdx)
14541462
else:
14551463
for i in 0 ..< n.safeLen:
14561464
detectCapturedVars(c, n[i], stateIdx)
@@ -1481,6 +1489,12 @@ proc liftLocals(c: var Ctx, n: PNode): PNode =
14811489
n[0][1] = liftLocals(c, n[0][1])
14821490
else:
14831491
n[0] = liftLocals(c, n[0])
1492+
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
1493+
nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef,
1494+
nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt,
1495+
nkTypeOfExpr, nkMixinStmt, nkBindStmt,
1496+
nkLambdaKinds, nkIteratorDef:
1497+
discard
14841498
else:
14851499
for i in 0 ..< n.safeLen:
14861500
n[i] = liftLocals(c, n[i])

compiler/lambdalifting.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ proc interestingVar(s: PSym): bool {.inline.} =
199199
proc illegalCapture(s: PSym): bool {.inline.} =
200200
result = classifyViewType(s.typ) != noView or s.kind == skResult
201201

202-
proc isInnerProc(s: PSym): bool =
202+
proc isInnerProc*(s: PSym): bool =
203203
if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and s.magic == mNone:
204204
result = s.skipGenericOwner.kind in routineKinds
205205
else:

tests/iter/t24863.nim

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# issue #24863
2+
3+
type M = object
4+
p: iterator(): M {.gcsafe.}
5+
6+
template h(f: M): int =
7+
yield f
8+
456
9+
10+
proc s(): M =
11+
iterator g(): M {.closure.} = discard
12+
let v = M(p: g)
13+
doAssert(not isNil(v.p))
14+
discard v.p()
15+
v
16+
17+
proc c(): M =
18+
iterator b(): M {.closure.} =
19+
let r = h(s())
20+
doAssert r == 456
21+
proc n(): M =
22+
iterator y(): M {.closure.} =
23+
let _ = r
24+
let _ = y
25+
let _ = proc () = discard n()
26+
let j = M(p: b)
27+
doAssert(not isNil(j.p))
28+
discard j.p()
29+
30+
let _ = c()

tests/iter/tnestedclosures.nim

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Test 7:
2525
0
2626
1
2727
2
28+
Test 8:
29+
123
30+
456
2831
'''
2932
"""
3033

@@ -156,3 +159,20 @@ block: # issue #12487
156159
doAssert s == @["something"]
157160

158161
main()
162+
163+
block: # minimized issue #24863
164+
echo "Test 8:"
165+
proc c() =
166+
iterator b(): int {.closure.} =
167+
let r = 456
168+
yield 123
169+
proc n() =
170+
echo r
171+
let a = proc () = n()
172+
a()
173+
174+
let j = b
175+
echo j()
176+
discard j()
177+
178+
c()

0 commit comments

Comments
 (0)