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:
@@ -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
12581260proc 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+
12791294proc 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
0 commit comments