Skip to content

Commit 7af4e3e

Browse files
authored
Fixes #25202 (#25244)
1 parent 130eac2 commit 7af4e3e

File tree

5 files changed

+152
-76
lines changed

5 files changed

+152
-76
lines changed

compiler/closureiters.nim

Lines changed: 99 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
# the target state is `except` block. For all states in `except` block
6565
# the target state is `finally` block. For all other states there is no
6666
# target state (0, as the first state can never be except nor finally).
67-
# - env var :curExcLevel is created, finallies use it to decide their exit logic
67+
# - env var :curExc is created, where "current" exception within the iterator is stored,
68+
# also finallies use it to decide their exit logic
6869
# - if there are finallies, env var :finallyPath is created. It contains exit state labels
6970
# for every finally level, and is changed in runtime in try, except, break, and return
7071
# nodes to control finally exit behavior.
@@ -111,7 +112,6 @@
111112
# :state = 2 # And we continue to our finally
112113
# break :stateLoop
113114
# of 1: # Except
114-
# inc(:curExcLevel, -1) # Exception is caught
115115
# yield 1
116116
# :tmpResult = 3 # Return
117117
# :finalyPath[LEVEL] = 0 # Configure finally path.
@@ -123,7 +123,7 @@
123123
# of 2: # Finally
124124
# yield 2
125125
# if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode`
126-
# if :curExcLevel == 0:
126+
# if :curExc == nil:
127127
# :state = -1
128128
# return result = :tmpResult
129129
# else:
@@ -165,14 +165,16 @@ type
165165
fn: PSym
166166
tmpResultSym: PSym # Used when we return, but finally has to interfere
167167
finallyPathSym: PSym
168-
curExcLevelSym: PSym # Current exception level (because exceptions are stacked)
168+
curExcSym: PSym # Current exception
169+
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
169170

170171
states: seq[State] # The resulting states. Label is int literal.
171172
finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies
172173
stateLoopLabel: PSym # Label to break on, when jumping between states.
173174
tempVarId: int # unique name counter
174175
hasExceptions: bool # Does closure have yield in try?
175176
curExcLandingState: PNode
177+
curExceptLevel: int
176178
curFinallyLevel: int
177179
idgen: IdGenerator
178180
varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states
@@ -242,10 +244,11 @@ proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInf
242244
let fp = newFinallyPathAccess(ctx, level, info)
243245
result = newTree(nkAsgn, fp, label)
244246

245-
proc newCurExcLevelAccess(ctx: var Ctx): PNode =
246-
if ctx.curExcLevelSym.isNil:
247-
ctx.curExcLevelSym = ctx.newEnvVar(":curExcLevel", ctx.g.getSysType(ctx.fn.info, tyInt16))
248-
ctx.newEnvVarAccess(ctx.curExcLevelSym)
247+
proc newCurExcAccess(ctx: var Ctx): PNode =
248+
if ctx.curExcSym.isNil:
249+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
250+
ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ)
251+
ctx.newEnvVarAccess(ctx.curExcSym)
249252

250253
proc newStateLabel(ctx: Ctx): PNode =
251254
ctx.g.newIntLit(TLineInfo(), 0)
@@ -284,6 +287,15 @@ proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = n
284287
assert(not typ.isNil, "Temp var needs a type")
285288
parent.add(ctx.newTempVarDef(result, initialValue))
286289

290+
proc newExternExcAccess(ctx: var Ctx): PNode =
291+
if ctx.externExcSym == nil:
292+
ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info)
293+
ctx.externExcSym.typ = ctx.curExcSym.typ
294+
newSymNode(ctx.externExcSym, ctx.fn.info)
295+
296+
proc newRestoreExternException(ctx: var Ctx): PNode =
297+
ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess())
298+
287299
proc hasYields(n: PNode): bool =
288300
# TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
289301
case n.kind
@@ -298,21 +310,13 @@ proc hasYields(n: PNode): bool =
298310
result = true
299311
break
300312

301-
proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo, decrement = false): PNode =
302-
# :curEcx = 0
303-
let curExc = ctx.newCurExcLevelAccess()
313+
proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
314+
# :curEcx = nil
315+
let curExc = ctx.newCurExcAccess()
304316
curExc.info = info
305-
let nilnode = ctx.g.newIntLit(info, 0)
317+
let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))
306318
result = newTree(nkAsgn, curExc, nilnode)
307319

308-
proc newChangeCurExcLevel(ctx: var Ctx, info: TLineInfo, by: int): PNode =
309-
# inc(:curEcxLevel, by)
310-
let curExc = ctx.newCurExcLevelAccess()
311-
curExc.info = info
312-
result = newTreeIT(nkCall, info, ctx.g.getSysType(info, tyVoid),
313-
newSymNode(ctx.g.getSysMagic(info, "inc", mInc)), curExc,
314-
ctx.g.newIntLit(info, by))
315-
316320
proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} =
317321
result = newTreeIT(nkCall, a.info, g.getSysType(a.info, tyBool),
318322
newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b)
@@ -344,17 +348,18 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
344348
else:
345349
ifBranch = newNodeI(nkElse, c.info)
346350

347-
ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1]))
351+
ifBranch.add(c[^1])
348352
ifStmt.add(ifBranch)
349353

350354
if ifStmt.len != 0:
351355
result = newTree(nkStmtList, ifStmt)
352356
else:
353357
result = ctx.g.emptyNode
354358

355-
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
359+
proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode =
356360
# We should adjust finallyPath to gotoOut if exception is handled
357361
# if there is no finally node next to this except, gotoOut must be nil
362+
result = n
358363
if n.kind == nkStmtList:
359364
if n[0].kind == nkIfStmt and n[0][^1].kind != nkElse:
360365
# Not all cases are covered, which means exception is not handled
@@ -377,6 +382,7 @@ proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) =
377382
# raised one.
378383
n.add newTree(nkCall,
379384
newSymNode(ctx.g.getCompilerProc("popCurrentException")))
385+
n.add ctx.newNullifyCurExc(n.info)
380386
if gotoOut != nil:
381387
# We have a finally node following this except block, and exception is handled
382388
# Configure its path to continue normally
@@ -823,7 +829,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
823829
proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
824830
# Generate the following code:
825831
# if :finallyPath[FINALLY_LEVEL] == 0:
826-
# if :curExcLevel == 0:
832+
# if :curExc == nil:
827833
# :state = -1
828834
# return result = :tmpResult
829835
# else:
@@ -837,9 +843,9 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
837843

838844
let excNilCmp = newTreeIT(nkCall,
839845
info, ctx.g.getSysType(info, tyBool),
840-
newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info),
841-
ctx.newCurExcLevelAccess(),
842-
ctx.g.newIntLit(info, 0))
846+
newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info),
847+
ctx.newCurExcAccess(),
848+
newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)))
843849

844850
let retStmt =
845851
block:
@@ -918,7 +924,9 @@ proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
918924
result = newNodeI(nkStmtList, n.info)
919925

920926
# Returns prevent exception propagation
921-
result.add(ctx.newNullifyCurExcLevel(n.info))
927+
result.add(ctx.newNullifyCurExc(n.info))
928+
929+
result.add(ctx.newRestoreExternException())
922930

923931
var finallyChain = newSeq[PNode]()
924932

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

987995
of nkYieldStmt:
988996
result = addGotoOut(result, gotoOut)
997+
if ctx.curExceptLevel > 0 or ctx.curFinallyLevel > 0:
998+
result = newTree(nkStmtList, ctx.newRestoreExternException(), result)
989999

9901000
of nkElse, nkElseExpr:
9911001
result[0] = addGotoOut(result[0], gotoOut)
@@ -1055,7 +1065,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
10551065
result.add(tryLabel)
10561066
var tryBody = toStmtList(n[0])
10571067

1058-
let exceptBody = ctx.collectExceptState(n)
1068+
var exceptBody = ctx.collectExceptState(n)
10591069
var finallyBody = ctx.getFinallyNode(n)
10601070
var exceptLabel, finallyLabel = ctx.g.emptyNode
10611071

@@ -1094,28 +1104,27 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode
10941104
inc ctx.curFinallyLevel
10951105
ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel))
10961106

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

11001109
if exceptBody.kind != nkEmpty:
1110+
inc ctx.curExceptLevel
11011111
ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel
11021112
else: oldExcLandingState
11031113
discard ctx.newState(exceptBody, false, exceptLabel)
11041114

11051115
let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil
1106-
ctx.addElseToExcept(exceptBody, normalOut)
1116+
exceptBody = ctx.addElseToExcept(exceptBody, normalOut)
11071117
# echo "EXCEPT: ", renderTree(exceptBody)
1108-
if ctx.transformClosureIteratorBody(exceptBody, tryOut) != exceptBody:
1109-
internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody")
1118+
exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut)
1119+
inc ctx.curExceptLevel
11101120

11111121
ctx.curExcLandingState = oldExcLandingState
11121122

11131123
if finallyBody.kind != nkEmpty:
11141124
discard ctx.finallyPathStack.pop()
11151125
discard ctx.newState(finallyBody, false, finallyLabel)
11161126
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")
1127+
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
11191128
dec ctx.curFinallyLevel
11201129

11211130
of nkGotoState, nkForStmt:
@@ -1201,59 +1210,52 @@ proc createExceptionTable(ctx: var Ctx): PNode {.inline.} =
12011210
for i in 0 .. ctx.states.high:
12021211
result.add(ctx.states[i].excLandingState)
12031212

1204-
proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
1213+
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
12051214
# Generates code:
1215+
# var :tmp = nil
1216+
# try:
1217+
# body
1218+
# except:
12061219
# :state = exceptionTable[:state]
1207-
# if :state == 0:
1208-
# raise
1209-
result = newNodeI(nkStmtList, info)
1220+
# :curExc = getCurrentException()
1221+
# if :state == 0:
1222+
# closureIterSetExc(:externExc)
1223+
# raise
1224+
#
1225+
# pushCurrentException(:curExc)
12101226

1211-
let intTyp = ctx.g.getSysType(info, tyInt)
1212-
let boolTyp = ctx.g.getSysType(info, tyBool)
1227+
let info = ctx.fn.info
1228+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
1229+
let exceptBody = newTreeI(nkStmtList, info,
1230+
ctx.newStateAssgn(
1231+
newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt),
1232+
ctx.createExceptionTable(),
1233+
ctx.newStateAccess())),
1234+
newTreeI(nkFastAsgn, info, ctx.newCurExcAccess(), getCurExc))
12131235

1214-
# :state = exceptionTable[:state]
1215-
result.add ctx.newStateAssgn(
1216-
newTreeIT(nkBracketExpr, info, intTyp,
1217-
ctx.createExceptionTable(),
1218-
ctx.newStateAccess()))
1236+
result = newTree(nkStmtList)
1237+
result.add newTree(nkTryStmt,
1238+
newTree(nkStmtList, n),
1239+
newTree(nkExceptBranch, exceptBody))
12191240

1220-
# if :state == 0: raise
1241+
# if :state == 0:
1242+
# closureIterSetExc(:externExc)
1243+
# raise
12211244
block:
1245+
let boolTyp = ctx.g.getSysType(info, tyBool)
1246+
let intTyp = ctx.g.getSysType(info, tyInt)
12221247
let cond = newTreeIT(nkCall, info, boolTyp,
12231248
ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
12241249
ctx.newStateAccess(),
12251250
newIntTypeNode(0, intTyp))
12261251

1227-
let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode)
1228-
let ifBranch = newTree(nkElifBranch, cond, raiseStmt)
1252+
let raiseStmt = newTree(nkRaiseStmt, ctx.newCurExcAccess())
1253+
let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt)
1254+
let ifBranch = newTree(nkElifBranch, cond, ifBody)
12291255
let ifStmt = newTree(nkIfStmt, ifBranch)
12301256
result.add(ifStmt)
12311257

1232-
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
1233-
# Generates code:
1234-
# var :tmp = nil
1235-
# try:
1236-
# body
1237-
# except:
1238-
# :state = exceptionTable[:state]
1239-
# if :state == 0:
1240-
# raise
1241-
# :tmp = getCurrentException()
1242-
#
1243-
# pushCurrentException(:tmp)
1244-
1245-
let tryBody = newTree(nkStmtList, n)
1246-
let exceptBody = ctx.newExceptBody(ctx.fn.info)
1247-
let exceptBranch = newTree(nkExceptBranch, exceptBody)
1248-
1249-
result = newTree(nkStmtList)
1250-
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
1251-
let tempExc = ctx.newTempVar(getCurExc.typ, result)
1252-
result.add newTree(nkTryStmt, tryBody, exceptBranch)
1253-
exceptBody.add ctx.newTempVarAsgn(tempExc, getCurExc)
1254-
1255-
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc))
1256-
result.add ctx.newChangeCurExcLevel(n.info, 1)
1258+
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess())
12571259

12581260
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
12591261
# while true:
@@ -1276,6 +1278,19 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
12761278
blockStmt.add(blockBody)
12771279
loopBody.add(blockStmt)
12781280

1281+
if ctx.hasExceptions:
1282+
# Since we have yields in tries, we must switch current exception
1283+
# between the iter and "outer world"
1284+
# var :externExc = getCurrentException()
1285+
# closureIterSetExc(:curExc)
1286+
let getCurExc = ctx.g.callCodegenProc("getCurrentException")
1287+
discard ctx.newExternExcAccess()
1288+
let setCurExc = ctx.g.callCodegenProc("closureIterSetExc", n.info, ctx.newCurExcAccess())
1289+
result = newTreeI(nkStmtList, n.info,
1290+
ctx.newTempVarDef(ctx.externExcSym, getCurExc),
1291+
setCurExc,
1292+
result)
1293+
12791294
proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray[int]) =
12801295
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
12811296
## For every such node increment stateOccurences[stateIdx]
@@ -1381,7 +1396,7 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
13811396
case n.kind
13821397
of nkSym:
13831398
let s = n.sym
1384-
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn:
1399+
if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym:
13851400
let vs = c.varStates.getOrDefault(s.itemId, localNotSeen)
13861401
if vs == localNotSeen: # First seing this variable
13871402
c.varStates[s.itemId] = stateIdx
@@ -1458,7 +1473,9 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14581473
# echo "transformed into ", n
14591474

14601475
discard ctx.newState(n, false, nil)
1461-
let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
1476+
1477+
let finalState = ctx.newStateLabel()
1478+
let gotoOut = newTree(nkGotoState, finalState)
14621479

14631480
var ns = false
14641481
n = ctx.lowerStmtListExprs(n, ns)
@@ -1470,6 +1487,12 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14701487
# Splitting transformation
14711488
discard ctx.transformClosureIteratorBody(n, gotoOut)
14721489

1490+
let finalStateBody = newTree(nkStmtList)
1491+
if ctx.hasExceptions:
1492+
finalStateBody.add(ctx.newRestoreExternException())
1493+
finalStateBody.add(newTree(nkGotoState, g.newIntLit(n.info, -1)))
1494+
discard ctx.newState(finalStateBody, true, finalState)
1495+
14731496
# Assign state label indexes
14741497
for i in 0 .. ctx.states.high:
14751498
ctx.states[i].label.intVal = i

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

lib/system/jssys.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ proc getCurrentExceptionMsg*(): string =
7272
proc setCurrentException*(exc: ref Exception) =
7373
lastJSError = cast[PJSError](exc)
7474

75+
proc closureIterSetExc(e: ref Exception) {.compilerRtl, benign.} =
76+
setCurrentException(e)
77+
7578
proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inline.} =
7679
## Used to set up exception handling for closure iterators.
7780

0 commit comments

Comments
 (0)