Skip to content

Commit 5cf6327

Browse files
committed
Fixes #25202
1 parent b8ce11d commit 5cf6327

File tree

5 files changed

+147
-33
lines changed

5 files changed

+147
-33
lines changed

compiler/closureiters.nim

Lines changed: 93 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,16 @@ type
166166
tmpResultSym: PSym # Used when we return, but finally has to interfere
167167
finallyPathSym: PSym
168168
curExcLevelSym: PSym # Current exception level (because exceptions are stacked)
169+
curExcSym: PSym # Current exception
170+
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
169171

170172
states: seq[State] # The resulting states. Label is int literal.
171173
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
172174
stateLoopLabel: PSym # Label to break on, when jumping between states.
173175
tempVarId: int # unique name counter
174176
hasExceptions: bool # Does closure have yield in try?
175177
curExcLandingState: PNode
178+
curExceptLevel: int
176179
curFinallyLevel: int
177180
idgen: IdGenerator
178181
varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states
@@ -242,6 +245,12 @@ proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInf
242245
let fp = newFinallyPathAccess(ctx, level, info)
243246
result = newTree(nkAsgn, fp, label)
244247

248+
proc newCurExcAccess(ctx: var Ctx): PNode =
249+
if ctx.curExcSym.isNil:
250+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
251+
ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ)
252+
ctx.newEnvVarAccess(ctx.curExcSym)
253+
245254
proc newCurExcLevelAccess(ctx: var Ctx): PNode =
246255
if ctx.curExcLevelSym.isNil:
247256
ctx.curExcLevelSym = ctx.newEnvVar(":curExcLevel", ctx.g.getSysType(ctx.fn.info, tyInt16))
@@ -284,6 +293,15 @@ proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = n
284293
assert(not typ.isNil, "Temp var needs a type")
285294
parent.add(ctx.newTempVarDef(result, initialValue))
286295

296+
proc newExternExcAccess(ctx: var Ctx): PNode =
297+
if ctx.externExcSym == nil:
298+
ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info)
299+
ctx.externExcSym.typ = ctx.curExcSym.typ
300+
newSymNode(ctx.externExcSym, ctx.fn.info)
301+
302+
proc newRestoreExternException(ctx: var Ctx): PNode =
303+
ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess())
304+
287305
proc hasYields(n: PNode): bool =
288306
# TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
289307
case n.kind
@@ -298,8 +316,15 @@ proc hasYields(n: PNode): bool =
298316
result = true
299317
break
300318

301-
proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo, decrement = false): PNode =
302-
# :curEcx = 0
319+
proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
320+
# :curEcx = nil
321+
let curExc = ctx.newCurExcAccess()
322+
curExc.info = info
323+
let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))
324+
result = newTree(nkAsgn, curExc, nilnode)
325+
326+
proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo): PNode =
327+
# :curEcxLevel = 0
303328
let curExc = ctx.newCurExcLevelAccess()
304329
curExc.info = info
305330
let nilnode = ctx.g.newIntLit(info, 0)
@@ -344,17 +369,20 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
344369
else:
345370
ifBranch = newNodeI(nkElse, c.info)
346371

347-
ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1]))
372+
ifBranch.add(c[^1])
373+
# ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1]))
348374
ifStmt.add(ifBranch)
349375

350376
if ifStmt.len != 0:
351377
result = newTree(nkStmtList, ifStmt)
378+
# result = newTree(nkStmtList, ctx.newChangeCurExcLevel(n.info, -1), ifStmt)
352379
else:
353380
result = ctx.g.emptyNode
354381

355-
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
382+
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
356383
# We should adjust finallyPath to gotoOut if exception is handled
357384
# if there is no finally node next to this except, gotoOut must be nil
385+
result = n
358386
if n.kind == nkStmtList:
359387
if n[0].kind == nkIfStmt and n[0][^1].kind != nkElse:
360388
# Not all cases are covered, which means exception is not handled
@@ -377,10 +405,12 @@ proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
377405
# raised one.
378406
n.add newTree(nkCall,
379407
newSymNode(ctx.g.getCompilerProc("popCurrentException")))
408+
n.add ctx.newNullifyCurExc(n.info)
380409
if gotoOut != nil:
381410
# We have a finally node following this except block, and exception is handled
382411
# Configure its path to continue normally
383412
n.add(ctx.newFinallyPathAssign(ctx.curFinallyLevel - 1, gotoOut[0], n.info))
413+
384414

385415
proc getFinallyNode(ctx: var Ctx, n: PNode): PNode =
386416
result = n[^1]
@@ -837,9 +867,9 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
837867

838868
let excNilCmp = newTreeIT(nkCall,
839869
info, ctx.g.getSysType(info, tyBool),
840-
newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info),
841-
ctx.newCurExcLevelAccess(),
842-
ctx.g.newIntLit(info, 0))
870+
newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info),
871+
ctx.newCurExcAccess(),
872+
newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)))
843873

844874
let retStmt =
845875
block:
@@ -919,6 +949,7 @@ proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
919949

920950
# Returns prevent exception propagation
921951
result.add(ctx.newNullifyCurExcLevel(n.info))
952+
result.add(ctx.newNullifyCurExc(n.info))
922953

923954
var finallyChain = newSeq[PNode]()
924955

@@ -986,6 +1017,8 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
9861017

9871018
of nkYieldStmt:
9881019
result = addGotoOut(result, gotoOut)
1020+
if ctx.curExceptLevel > 0 or ctx.curFinallyLevel > 0:
1021+
result = newTree(nkStmtList, ctx.newRestoreExternException(), result)
9891022

9901023
of nkElse, nkElseExpr:
9911024
result[0] = addGotoOut(result[0], gotoOut)
@@ -1055,7 +1088,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
10551088
result.add(tryLabel)
10561089
var tryBody = toStmtList(n[0])
10571090

1058-
let exceptBody = ctx.collectExceptState(n)
1091+
var exceptBody = ctx.collectExceptState(n)
10591092
var finallyBody = ctx.getFinallyNode(n)
10601093
var exceptLabel, finallyLabel = ctx.g.emptyNode
10611094

@@ -1094,28 +1127,27 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
10941127
inc ctx.curFinallyLevel
10951128
ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel))
10961129

1097-
if ctx.transformClosureIteratorBody(tryBody, tryOut) != tryBody:
1098-
internalError(ctx.g.config, "transformClosureIteratorBody != tryBody")
1130+
tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut)
10991131

11001132
if exceptBody.kind != nkEmpty:
1133+
inc ctx.curExceptLevel
11011134
ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel
11021135
else: oldExcLandingState
11031136
discard ctx.newState(exceptBody, false, exceptLabel)
11041137

11051138
let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil
1106-
ctx.addElseToExcept(exceptBody, normalOut)
1139+
exceptBody = ctx.addElseToExcept(exceptBody, normalOut)
11071140
# echo "EXCEPT: ", renderTree(exceptBody)
1108-
if ctx.transformClosureIteratorBody(exceptBody, tryOut) != exceptBody:
1109-
internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody")
1141+
exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut)
1142+
inc ctx.curExceptLevel
11101143

11111144
ctx.curExcLandingState = oldExcLandingState
11121145

11131146
if finallyBody.kind != nkEmpty:
11141147
discard ctx.finallyPathStack.pop()
11151148
discard ctx.newState(finallyBody, false, finallyLabel)
11161149
let finallyExit = newTree(nkGotoState, ctx.newFinallyPathAccess(ctx.curFinallyLevel - 1, finallyBody.info))
1117-
if ctx.transformClosureIteratorBody(finallyBody, finallyExit) != finallyBody:
1118-
internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody")
1150+
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
11191151
dec ctx.curFinallyLevel
11201152

11211153
of nkGotoState, nkForStmt:
@@ -1209,25 +1241,16 @@ proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
12091241
result = newNodeI(nkStmtList, info)
12101242

12111243
let intTyp = ctx.g.getSysType(info, tyInt)
1212-
let boolTyp = ctx.g.getSysType(info, tyBool)
12131244

12141245
# :state = exceptionTable[:state]
12151246
result.add ctx.newStateAssgn(
12161247
newTreeIT(nkBracketExpr, info, intTyp,
12171248
ctx.createExceptionTable(),
12181249
ctx.newStateAccess()))
12191250

1220-
# if :state == 0: raise
1221-
block:
1222-
let cond = newTreeIT(nkCall, info, boolTyp,
1223-
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
1224-
ctx.newStateAccess(),
1225-
newIntTypeNode(0, intTyp))
1251+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
1252+
result.add newTree(nkFastAsgn, ctx.newCurExcAccess(), getCurExc)
12261253

1227-
let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode)
1228-
let ifBranch = newTree(nkElifBranch, cond, raiseStmt)
1229-
let ifStmt = newTree(nkIfStmt, ifBranch)
1230-
result.add(ifStmt)
12311254

12321255
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
12331256
# Generates code:
@@ -1236,11 +1259,12 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
12361259
# body
12371260
# except:
12381261
# :state = exceptionTable[:state]
1239-
# if :state == 0:
1240-
# raise
1241-
# :tmp = getCurrentException()
1262+
# :curExc = getCurrentException()
1263+
# if :state == 0:
1264+
# raise
12421265
#
1243-
# pushCurrentException(:tmp)
1266+
# pushCurrentException(:curExc)
1267+
# inc :curExcLevel
12441268

12451269
let tryBody = newTree(nkStmtList, n)
12461270
let exceptBody = ctx.newExceptBody(ctx.fn.info)
@@ -1251,8 +1275,25 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
12511275
let tempExc = ctx.newTempVar(getCurExc.typ, result)
12521276
result.add newTree(nkTryStmt, tryBody, exceptBranch)
12531277
exceptBody.add ctx.newTempVarAsgn(tempExc, getCurExc)
1278+
# exceptBody.add newTree(nkFastAsgn, ctx.newCurExcAccess(), getCurExc)
1279+
1280+
# if :state == 0: raise
1281+
block:
1282+
let boolTyp = ctx.g.getSysType(ctx.fn.info, tyBool)
1283+
let intTyp = ctx.g.getSysType(ctx.fn.info, tyInt)
1284+
let cond = newTreeIT(nkCall, ctx.fn.info, boolTyp,
1285+
ctx.g.getSysMagic(ctx.fn.info, "==", mEqI).newSymNode(),
1286+
ctx.newStateAccess(),
1287+
newIntTypeNode(0, intTyp))
1288+
1289+
let raiseStmt = newTree(nkRaiseStmt, ctx.newCurExcAccess())
1290+
let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt)
1291+
let ifBranch = newTree(nkElifBranch, cond, ifBody)
1292+
let ifStmt = newTree(nkIfStmt, ifBranch)
1293+
result.add(ifStmt)
12541294

1255-
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc))
1295+
# result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc))
1296+
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess())
12561297
result.add ctx.newChangeCurExcLevel(n.info, 1)
12571298

12581299
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
@@ -1276,6 +1317,17 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
12761317
blockStmt.add(blockBody)
12771318
loopBody.add(blockStmt)
12781319

1320+
if ctx.hasExceptions:
1321+
let res = newNodeI(nkStmtList, n.info)
1322+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
1323+
discard ctx.newExternExcAccess()
1324+
res.add(ctx.newTempVarDef(ctx.externExcSym, getCurExc))
1325+
# let externExc = ctx.newTempVar(getCurExc.typ, res, getCurExc)
1326+
let setCurExc = ctx.g.callCodegenProc("closureIterSetExc", n.info, ctx.newCurExcAccess())
1327+
res.add(setCurExc)
1328+
res.add(result)
1329+
result = res
1330+
12791331
proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray[int]) =
12801332
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
12811333
## For every such node increment stateOccurences[stateIdx]
@@ -1381,7 +1433,7 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
13811433
case n.kind
13821434
of nkSym:
13831435
let s = n.sym
1384-
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn:
1436+
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
13851437
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
13861438
if vs == localNotSeen: # First seing this variable
13871439
c.varStates[s.itemId] = stateIdx
@@ -1458,7 +1510,9 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14581510
# echo "transformed into ", n
14591511

14601512
discard ctx.newState(n, false, nil)
1461-
let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
1513+
1514+
let finalState = ctx.newStateLabel()
1515+
let gotoOut = newTree(nkGotoState, finalState)
14621516

14631517
var ns = false
14641518
n = ctx.lowerStmtListExprs(n, ns)
@@ -1470,6 +1524,12 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14701524
# Splitting transformation
14711525
discard ctx.transformClosureIteratorBody(n, gotoOut)
14721526

1527+
let finalStateBody = newTree(nkStmtList)
1528+
if ctx.hasExceptions:
1529+
finalStateBody.add(ctx.newRestoreExternException())
1530+
finalStateBody.add(newTree(nkGotoState, g.newIntLit(n.info, -1)))
1531+
discard ctx.newState(finalStateBody, true, finalState)
1532+
14731533
# Assign state label indexes
14741534
for i in 0 .. ctx.states.high:
14751535
ctx.states[i].label.intVal = i

lib/system.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,6 +2329,10 @@ when notJSnotNims and hostOS != "standalone":
23292329
## Retrieves the current exception; if there is none, `nil` is returned.
23302330
result = currException
23312331

2332+
proc setCurrentExceptionForClosureIter*(e: ref Exception) {.compilerRtl, inl, benign.} =
2333+
## Retrieves the current exception; if there is none, `nil` is returned.
2334+
currException = e
2335+
23322336
proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, benign, nodestroy.} =
23332337
# .nodestroy here so that we do not produce a write barrier as the
23342338
# C codegen only uses it in a borrowed way:

lib/system/embedded.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ when not gotoBasedExceptions:
2424
proc popSafePoint {.compilerRtl, inl.} = discard
2525
proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} = discard
2626
proc popCurrentException {.compilerRtl, inl.} = discard
27+
proc closureIterSetExc(e: ref Exception) {.compilerRtl, inl.} = discard
2728

2829
# some platforms have native support for stack traces:
2930
const

lib/system/excpt.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ proc popCurrentException {.compilerRtl, inl.} =
159159
currException = currException.up
160160
#showErrorMessage2 "B"
161161

162+
proc closureIterSetExc(e: ref Exception) {.compilerRtl, inl.} =
163+
currException = e
164+
162165
proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
163166
discard "only for bootstrapping compatbility"
164167

tests/iter/tyieldintry.nim

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,3 +752,49 @@ block: #25038
752752
0
753753

754754
test(d)
755+
756+
block: #25202
757+
proc p() =
758+
iterator p_1073741828(checkpoints: var seq[int]): int {.
759+
closure, raises: [].} =
760+
var closureSucceeded_1073741827 = true
761+
try:
762+
try:
763+
try:
764+
yield 0
765+
raise newException(ValueError, "value error")
766+
except ValueError:
767+
checkpoints.add(1)
768+
raise newException(IOError, "io error")
769+
finally:
770+
yield 2
771+
except IOError as exc:
772+
closureSucceeded_1073741827 = false
773+
checkpoints.add(3)
774+
finally:
775+
checkpoints.add(4)
776+
if closureSucceeded_1073741827:
777+
discard
778+
779+
var internalClosure = p_1073741828
780+
var internalClosure2 = p_1073741828
781+
782+
var checkpoints1 = newSeq[int]()
783+
var checkpoints2 = newSeq[int]()
784+
785+
while true:
786+
if not internalClosure.finished():
787+
checkpoints1.add internalClosure(checkpoints1)
788+
doAssert(getCurrentException() == nil)
789+
if not internalClosure2.finished():
790+
checkpoints2.add internalClosure2(checkpoints2)
791+
doAssert(getCurrentException() == nil)
792+
if internalClosure.finished() and internalClosure2.finished():
793+
break
794+
795+
if checkpoints1[^1] == 0: checkpoints1.del(checkpoints1.high)
796+
if checkpoints2[^1] == 0: checkpoints2.del(checkpoints2.high)
797+
doAssert(checkpoints1 == @[0, 1, 2, 3, 4])
798+
doAssert(checkpoints1 == checkpoints2)
799+
800+
p()

0 commit comments

Comments
 (0)