diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index ee3e4adcaca8..8b61106abc33 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -64,7 +64,8 @@ # the target state is `except` block. For all states in `except` block # the target state is `finally` block. For all other states there is no # target state (0, as the first state can never be except nor finally). -# - env var :curExcLevel is created, finallies use it to decide their exit logic +# - env var :curExc is created, where "current" exception within the iterator is stored, +# also finallies use it to decide their exit logic # - if there are finallies, env var :finallyPath is created. It contains exit state labels # for every finally level, and is changed in runtime in try, except, break, and return # nodes to control finally exit behavior. @@ -111,7 +112,6 @@ # :state = 2 # And we continue to our finally # break :stateLoop # of 1: # Except -# inc(:curExcLevel, -1) # Exception is caught # yield 1 # :tmpResult = 3 # Return # :finalyPath[LEVEL] = 0 # Configure finally path. @@ -123,7 +123,7 @@ # of 2: # Finally # yield 2 # if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode` -# if :curExcLevel == 0: +# if :curExc == nil: # :state = -1 # return result = :tmpResult # else: @@ -165,7 +165,8 @@ type fn: PSym tmpResultSym: PSym # Used when we return, but finally has to interfere finallyPathSym: PSym - curExcLevelSym: PSym # Current exception level (because exceptions are stacked) + curExcSym: PSym # Current exception + externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter states: seq[State] # The resulting states. Label is int literal. finallyPathStack: seq[FinallyTarget] # Stack of split blocks, whiles and finallies @@ -173,6 +174,7 @@ type tempVarId: int # unique name counter hasExceptions: bool # Does closure have yield in try? curExcLandingState: PNode + curExceptLevel: int curFinallyLevel: int idgen: IdGenerator 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 let fp = newFinallyPathAccess(ctx, level, info) result = newTree(nkAsgn, fp, label) -proc newCurExcLevelAccess(ctx: var Ctx): PNode = - if ctx.curExcLevelSym.isNil: - ctx.curExcLevelSym = ctx.newEnvVar(":curExcLevel", ctx.g.getSysType(ctx.fn.info, tyInt16)) - ctx.newEnvVarAccess(ctx.curExcLevelSym) +proc newCurExcAccess(ctx: var Ctx): PNode = + if ctx.curExcSym.isNil: + let getCurExc = ctx.g.callCodegenProc("getCurrentException") + ctx.curExcSym = ctx.newEnvVar(":curExc", getCurExc.typ) + ctx.newEnvVarAccess(ctx.curExcSym) proc newStateLabel(ctx: Ctx): PNode = ctx.g.newIntLit(TLineInfo(), 0) @@ -284,6 +287,15 @@ proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = n assert(not typ.isNil, "Temp var needs a type") parent.add(ctx.newTempVarDef(result, initialValue)) +proc newExternExcAccess(ctx: var Ctx): PNode = + if ctx.externExcSym == nil: + ctx.externExcSym = newSym(skVar, getIdent(ctx.g.cache, ":externExc"), ctx.idgen, ctx.fn, ctx.fn.info) + ctx.externExcSym.typ = ctx.curExcSym.typ + newSymNode(ctx.externExcSym, ctx.fn.info) + +proc newRestoreExternException(ctx: var Ctx): PNode = + ctx.g.callCodegenProc("closureIterSetExc", ctx.fn.info, ctx.newExternExcAccess()) + proc hasYields(n: PNode): bool = # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt. case n.kind @@ -298,21 +310,13 @@ proc hasYields(n: PNode): bool = result = true break -proc newNullifyCurExcLevel(ctx: var Ctx, info: TLineInfo, decrement = false): PNode = - # :curEcx = 0 - let curExc = ctx.newCurExcLevelAccess() +proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode = + # :curEcx = nil + let curExc = ctx.newCurExcAccess() curExc.info = info - let nilnode = ctx.g.newIntLit(info, 0) + let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)) result = newTree(nkAsgn, curExc, nilnode) -proc newChangeCurExcLevel(ctx: var Ctx, info: TLineInfo, by: int): PNode = - # inc(:curEcxLevel, by) - let curExc = ctx.newCurExcLevelAccess() - curExc.info = info - result = newTreeIT(nkCall, info, ctx.g.getSysType(info, tyVoid), - newSymNode(ctx.g.getSysMagic(info, "inc", mInc)), curExc, - ctx.g.newIntLit(info, by)) - proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} = result = newTreeIT(nkCall, a.info, g.getSysType(a.info, tyBool), newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b) @@ -344,7 +348,7 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} = else: ifBranch = newNodeI(nkElse, c.info) - ifBranch.add(newTreeI(nkStmtList, c.info, ctx.newChangeCurExcLevel(c.info, -1), c[^1])) + ifBranch.add(c[^1]) ifStmt.add(ifBranch) if ifStmt.len != 0: @@ -352,9 +356,10 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} = else: result = ctx.g.emptyNode -proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) = +proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode): PNode = # We should adjust finallyPath to gotoOut if exception is handled # if there is no finally node next to this except, gotoOut must be nil + result = n if n.kind == nkStmtList: if n[0].kind == nkIfStmt and n[0][^1].kind != nkElse: # Not all cases are covered, which means exception is not handled @@ -377,6 +382,7 @@ proc addElseToExcept(ctx: var Ctx, n, gotoOut: PNode) = # raised one. n.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("popCurrentException"))) + n.add ctx.newNullifyCurExc(n.info) if gotoOut != nil: # We have a finally node following this except block, and exception is handled # Configure its path to continue normally @@ -823,7 +829,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode = # Generate the following code: # if :finallyPath[FINALLY_LEVEL] == 0: - # if :curExcLevel == 0: + # if :curExc == nil: # :state = -1 # return result = :tmpResult # else: @@ -837,9 +843,9 @@ proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode = let excNilCmp = newTreeIT(nkCall, info, ctx.g.getSysType(info, tyBool), - newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info), - ctx.newCurExcLevelAccess(), - ctx.g.newIntLit(info, 0)) + newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info), + ctx.newCurExcAccess(), + newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))) let retStmt = block: @@ -918,7 +924,9 @@ proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode = result = newNodeI(nkStmtList, n.info) # Returns prevent exception propagation - result.add(ctx.newNullifyCurExcLevel(n.info)) + result.add(ctx.newNullifyCurExc(n.info)) + + result.add(ctx.newRestoreExternException()) var finallyChain = newSeq[PNode]() @@ -986,6 +994,8 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode of nkYieldStmt: result = addGotoOut(result, gotoOut) + if ctx.curExceptLevel > 0 or ctx.curFinallyLevel > 0: + result = newTree(nkStmtList, ctx.newRestoreExternException(), result) of nkElse, nkElseExpr: result[0] = addGotoOut(result[0], gotoOut) @@ -1055,7 +1065,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode result.add(tryLabel) var tryBody = toStmtList(n[0]) - let exceptBody = ctx.collectExceptState(n) + var exceptBody = ctx.collectExceptState(n) var finallyBody = ctx.getFinallyNode(n) var exceptLabel, finallyLabel = ctx.g.emptyNode @@ -1094,19 +1104,19 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode inc ctx.curFinallyLevel ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel)) - if ctx.transformClosureIteratorBody(tryBody, tryOut) != tryBody: - internalError(ctx.g.config, "transformClosureIteratorBody != tryBody") + tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut) if exceptBody.kind != nkEmpty: + inc ctx.curExceptLevel ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel else: oldExcLandingState discard ctx.newState(exceptBody, false, exceptLabel) let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil - ctx.addElseToExcept(exceptBody, normalOut) + exceptBody = ctx.addElseToExcept(exceptBody, normalOut) # echo "EXCEPT: ", renderTree(exceptBody) - if ctx.transformClosureIteratorBody(exceptBody, tryOut) != exceptBody: - internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody") + exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut) + inc ctx.curExceptLevel ctx.curExcLandingState = oldExcLandingState @@ -1114,8 +1124,7 @@ proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode discard ctx.finallyPathStack.pop() discard ctx.newState(finallyBody, false, finallyLabel) let finallyExit = newTree(nkGotoState, ctx.newFinallyPathAccess(ctx.curFinallyLevel - 1, finallyBody.info)) - if ctx.transformClosureIteratorBody(finallyBody, finallyExit) != finallyBody: - internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody") + finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit) dec ctx.curFinallyLevel of nkGotoState, nkForStmt: @@ -1201,59 +1210,52 @@ proc createExceptionTable(ctx: var Ctx): PNode {.inline.} = for i in 0 .. ctx.states.high: result.add(ctx.states[i].excLandingState) -proc newExceptBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} = +proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} = # Generates code: + # var :tmp = nil + # try: + # body + # except: # :state = exceptionTable[:state] - # if :state == 0: - # raise - result = newNodeI(nkStmtList, info) + # :curExc = getCurrentException() + # if :state == 0: + # closureIterSetExc(:externExc) + # raise + # + # pushCurrentException(:curExc) - let intTyp = ctx.g.getSysType(info, tyInt) - let boolTyp = ctx.g.getSysType(info, tyBool) + let info = ctx.fn.info + let getCurExc = ctx.g.callCodegenProc("getCurrentException") + let exceptBody = newTreeI(nkStmtList, info, + ctx.newStateAssgn( + newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt), + ctx.createExceptionTable(), + ctx.newStateAccess())), + newTreeI(nkFastAsgn, info, ctx.newCurExcAccess(), getCurExc)) - # :state = exceptionTable[:state] - result.add ctx.newStateAssgn( - newTreeIT(nkBracketExpr, info, intTyp, - ctx.createExceptionTable(), - ctx.newStateAccess())) + result = newTree(nkStmtList) + result.add newTree(nkTryStmt, + newTree(nkStmtList, n), + newTree(nkExceptBranch, exceptBody)) - # if :state == 0: raise + # if :state == 0: + # closureIterSetExc(:externExc) + # raise block: + let boolTyp = ctx.g.getSysType(info, tyBool) + let intTyp = ctx.g.getSysType(info, tyInt) let cond = newTreeIT(nkCall, info, boolTyp, ctx.g.getSysMagic(info, "==", mEqI).newSymNode(), ctx.newStateAccess(), newIntTypeNode(0, intTyp)) - let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode) - let ifBranch = newTree(nkElifBranch, cond, raiseStmt) + let raiseStmt = newTree(nkRaiseStmt, ctx.newCurExcAccess()) + let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt) + let ifBranch = newTree(nkElifBranch, cond, ifBody) let ifStmt = newTree(nkIfStmt, ifBranch) result.add(ifStmt) -proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} = - # Generates code: - # var :tmp = nil - # try: - # body - # except: - # :state = exceptionTable[:state] - # if :state == 0: - # raise - # :tmp = getCurrentException() - # - # pushCurrentException(:tmp) - - let tryBody = newTree(nkStmtList, n) - let exceptBody = ctx.newExceptBody(ctx.fn.info) - let exceptBranch = newTree(nkExceptBranch, exceptBody) - - result = newTree(nkStmtList) - let getCurExc = ctx.g.callCodegenProc("getCurrentException") - let tempExc = ctx.newTempVar(getCurExc.typ, result) - result.add newTree(nkTryStmt, tryBody, exceptBranch) - exceptBody.add ctx.newTempVarAsgn(tempExc, getCurExc) - - result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newTempVarAccess(tempExc)) - result.add ctx.newChangeCurExcLevel(n.info, 1) + result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess()) proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode = # while true: @@ -1276,6 +1278,19 @@ proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode = blockStmt.add(blockBody) loopBody.add(blockStmt) + if ctx.hasExceptions: + # Since we have yields in tries, we must switch current exception + # between the iter and "outer world" + # var :externExc = getCurrentException() + # closureIterSetExc(:curExc) + let getCurExc = ctx.g.callCodegenProc("getCurrentException") + discard ctx.newExternExcAccess() + let setCurExc = ctx.g.callCodegenProc("closureIterSetExc", n.info, ctx.newCurExcAccess()) + result = newTreeI(nkStmtList, n.info, + ctx.newTempVarDef(ctx.externExcSym, getCurExc), + setCurExc, + result) + proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray[int]) = ## Find all nkGotoState(stateIdx) nodes that do not follow nkYield. ## For every such node increment stateOccurences[stateIdx] @@ -1381,7 +1396,7 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) = case n.kind of nkSym: let s = n.sym - if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn: + if s.kind in {skResult, skVar, skLet, skForVar, skTemp} and sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym: let vs = c.varStates.getOrDefault(s.itemId, localNotSeen) if vs == localNotSeen: # First seing this variable c.varStates[s.itemId] = stateIdx @@ -1458,7 +1473,9 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n: # echo "transformed into ", n discard ctx.newState(n, false, nil) - let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1)) + + let finalState = ctx.newStateLabel() + let gotoOut = newTree(nkGotoState, finalState) var ns = false n = ctx.lowerStmtListExprs(n, ns) @@ -1470,6 +1487,12 @@ proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n: # Splitting transformation discard ctx.transformClosureIteratorBody(n, gotoOut) + let finalStateBody = newTree(nkStmtList) + if ctx.hasExceptions: + finalStateBody.add(ctx.newRestoreExternException()) + finalStateBody.add(newTree(nkGotoState, g.newIntLit(n.info, -1))) + discard ctx.newState(finalStateBody, true, finalState) + # Assign state label indexes for i in 0 .. ctx.states.high: ctx.states[i].label.intVal = i diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index 5abbdef24883..5d1d12accb0a 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -24,6 +24,7 @@ when not gotoBasedExceptions: proc popSafePoint {.compilerRtl, inl.} = discard proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} = discard proc popCurrentException {.compilerRtl, inl.} = discard +proc closureIterSetExc(e: ref Exception) {.compilerRtl, inl.} = discard # some platforms have native support for stack traces: const diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index a6a92ee127f6..511839914f0f 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -159,6 +159,9 @@ proc popCurrentException {.compilerRtl, inl.} = currException = currException.up #showErrorMessage2 "B" +proc closureIterSetExc(e: ref Exception) {.compilerRtl, inl.} = + currException = e + proc popCurrentExceptionEx(id: uint) {.compilerRtl.} = discard "only for bootstrapping compatbility" diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 3e2ad9ec24e5..6934e67ee4b0 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -72,6 +72,9 @@ proc getCurrentExceptionMsg*(): string = proc setCurrentException*(exc: ref Exception) = lastJSError = cast[PJSError](exc) +proc closureIterSetExc(e: ref Exception) {.compilerRtl, benign.} = + setCurrentException(e) + proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inline.} = ## Used to set up exception handling for closure iterators. diff --git a/tests/iter/tyieldintry.nim b/tests/iter/tyieldintry.nim index fbf163ab05ca..4e7afcfe40b1 100644 --- a/tests/iter/tyieldintry.nim +++ b/tests/iter/tyieldintry.nim @@ -752,3 +752,49 @@ block: #25038 0 test(d) + +block: #25202 + proc p() = + iterator p_1073741828(checkpoints: var seq[int]): int {. + closure, raises: [].} = + var closureSucceeded_1073741827 = true + try: + try: + try: + yield 0 + raise newException(ValueError, "value error") + except ValueError: + checkpoints.add(1) + raise newException(IOError, "io error") + finally: + yield 2 + except IOError as exc: + closureSucceeded_1073741827 = false + checkpoints.add(3) + finally: + checkpoints.add(4) + if closureSucceeded_1073741827: + discard + + var internalClosure = p_1073741828 + var internalClosure2 = p_1073741828 + + var checkpoints1 = newSeq[int]() + var checkpoints2 = newSeq[int]() + + while true: + if not internalClosure.finished(): + checkpoints1.add internalClosure(checkpoints1) + doAssert(getCurrentException() == nil) + if not internalClosure2.finished(): + checkpoints2.add internalClosure2(checkpoints2) + doAssert(getCurrentException() == nil) + if internalClosure.finished() and internalClosure2.finished(): + break + + if checkpoints1[^1] == 0: checkpoints1.del(checkpoints1.high) + if checkpoints2[^1] == 0: checkpoints2.del(checkpoints2.high) + doAssert(checkpoints1 == @[0, 1, 2, 3, 4]) + doAssert(checkpoints1 == checkpoints2) + + p()