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.
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.
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
250253proc 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+
287299proc 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-
316320proc 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 =
823829proc 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:
@@ -1209,25 +1218,16 @@ proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
12091218 result = newNodeI (nkStmtList, info)
12101219
12111220 let intTyp = ctx.g.getSysType (info, tyInt)
1212- let boolTyp = ctx.g.getSysType (info, tyBool)
12131221
12141222 # :state = exceptionTable[:state]
12151223 result .add ctx.newStateAssgn (
12161224 newTreeIT (nkBracketExpr, info, intTyp,
12171225 ctx.createExceptionTable (),
12181226 ctx.newStateAccess ()))
12191227
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))
1228+ let getCurExc = ctx.g.callCodegenProc (" getCurrentException" )
1229+ result .add newTree (nkFastAsgn, ctx.newCurExcAccess (), getCurExc)
12261230
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)
12311231
12321232proc wrapIntoTryExcept (ctx: var Ctx , n: PNode ): PNode {.inline .} =
12331233 # Generates code:
@@ -1236,24 +1236,36 @@ proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
12361236 # body
12371237 # except:
12381238 # :state = exceptionTable[:state]
1239- # if :state == 0:
1240- # raise
1241- # :tmp = getCurrentException()
1239+ # :curExc = getCurrentException()
1240+ # if :state == 0:
1241+ # closureIterSetExc(:externExc)
1242+ # raise
12421243 #
1243- # pushCurrentException(:tmp )
1244+ # pushCurrentException(:curExc )
12441245
12451246 let tryBody = newTree (nkStmtList, n)
12461247 let exceptBody = ctx.newExceptBody (ctx.fn.info)
12471248 let exceptBranch = newTree (nkExceptBranch, exceptBody)
12481249
12491250 result = newTree (nkStmtList)
1250- let getCurExc = ctx.g.callCodegenProc (" getCurrentException" )
1251- let tempExc = ctx.newTempVar (getCurExc.typ, result )
12521251 result .add newTree (nkTryStmt, tryBody, exceptBranch)
1253- exceptBody.add ctx.newTempVarAsgn (tempExc, getCurExc)
12541252
1255- result .add newTree (nkCall, newSymNode (ctx.g.getCompilerProc (" pushCurrentException" )), ctx.newTempVarAccess (tempExc))
1256- result .add ctx.newChangeCurExcLevel (n.info, 1 )
1253+ # if :state == 0: raise
1254+ block :
1255+ let boolTyp = ctx.g.getSysType (ctx.fn.info, tyBool)
1256+ let intTyp = ctx.g.getSysType (ctx.fn.info, tyInt)
1257+ let cond = newTreeIT (nkCall, ctx.fn.info, boolTyp,
1258+ ctx.g.getSysMagic (ctx.fn.info, " ==" , mEqI).newSymNode (),
1259+ ctx.newStateAccess (),
1260+ newIntTypeNode (0 , intTyp))
1261+
1262+ let raiseStmt = newTree (nkRaiseStmt, ctx.newCurExcAccess ())
1263+ let ifBody = newTree (nkStmtList, ctx.newRestoreExternException (), raiseStmt)
1264+ let ifBranch = newTree (nkElifBranch, cond, ifBody)
1265+ let ifStmt = newTree (nkIfStmt, ifBranch)
1266+ result .add (ifStmt)
1267+
1268+ result .add newTree (nkCall, newSymNode (ctx.g.getCompilerProc (" pushCurrentException" )), ctx.newCurExcAccess ())
12571269
12581270proc wrapIntoStateLoop (ctx: var Ctx , n: PNode ): PNode =
12591271 # while true:
@@ -1276,6 +1288,19 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
12761288 blockStmt.add (blockBody)
12771289 loopBody.add (blockStmt)
12781290
1291+ if ctx.hasExceptions:
1292+ # Since we have yields in tries, we must switch current exception
1293+ # between the iter and "outer world"
1294+ # var :externExc = getCurrentException()
1295+ # closureIterSetExc(:curExc)
1296+ let getCurExc = ctx.g.callCodegenProc (" getCurrentException" )
1297+ discard ctx.newExternExcAccess ()
1298+ let setCurExc = ctx.g.callCodegenProc (" closureIterSetExc" , n.info, ctx.newCurExcAccess ())
1299+ result = newTreeI (nkStmtList, n.info,
1300+ ctx.newTempVarDef (ctx.externExcSym, getCurExc),
1301+ setCurExc,
1302+ result )
1303+
12791304proc countStateOccurences (ctx: var Ctx , n: PNode , stateOccurences: var openArray [int ]) =
12801305 # # Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
12811306 # # For every such node increment stateOccurences[stateIdx]
@@ -1381,7 +1406,7 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
13811406 case n.kind
13821407 of nkSym:
13831408 let s = n.sym
1384- if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn:
1409+ if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym :
13851410 let vs = c.varStates.getOrDefault (s.itemId, localNotSeen)
13861411 if vs == localNotSeen: # First seing this variable
13871412 c.varStates[s.itemId] = stateIdx
@@ -1458,7 +1483,9 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14581483 # echo "transformed into ", n
14591484
14601485 discard ctx.newState (n, false , nil )
1461- let gotoOut = newTree (nkGotoState, g.newIntLit (n.info, - 1 ))
1486+
1487+ let finalState = ctx.newStateLabel ()
1488+ let gotoOut = newTree (nkGotoState, finalState)
14621489
14631490 var ns = false
14641491 n = ctx.lowerStmtListExprs (n, ns)
@@ -1470,6 +1497,12 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n:
14701497 # Splitting transformation
14711498 discard ctx.transformClosureIteratorBody (n, gotoOut)
14721499
1500+ let finalStateBody = newTree (nkStmtList)
1501+ if ctx.hasExceptions:
1502+ finalStateBody.add (ctx.newRestoreExternException ())
1503+ finalStateBody.add (newTree (nkGotoState, g.newIntLit (n.info, - 1 )))
1504+ discard ctx.newState (finalStateBody, true , finalState)
1505+
14731506 # Assign state label indexes
14741507 for i in 0 .. ctx.states.high:
14751508 ctx.states[i].label.intVal = i
0 commit comments