Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 99 additions & 76 deletions compiler/closureiters.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -165,14 +165,16 @@ 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
stateLoopLabel: PSym # Label to break on, when jumping between states.
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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -344,17 +348,18 @@ 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:
result = newTree(nkStmtList, ifStmt)
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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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]()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1094,28 +1104,27 @@ 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

if finallyBody.kind != nkEmpty:
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:
Expand Down Expand Up @@ -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:
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/system/embedded.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions lib/system/excpt.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
3 changes: 3 additions & 0 deletions lib/system/jssys.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Loading