mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-01 04:59:05 +00:00
Fixes #23615 ## Root cause The leak does not require async at all -- this minimal closure iterator leaks the exception and its stacktrace seq under ARC/ORC: ```nim iterator it(): int {.closure.} = try: yield 1 # try spanning a yield => closureiters transform raise newException(ValueError, "x") except ValueError: # typed except => generated `of` check discard yield 2 ``` A bare `except:` does not leak; a *typed* `except` does: 1. `collectExceptState` in `compiler/closureiters.nim` generates the except-branch type check as `of(getCurrentException(), T)`, using the raw generic magic sym from `getSysMagic("of", mOf)`. 2. `injectdestructors` skips call arguments whose *formal* parameter type is `isCompileTimeOnly`, and the raw generic `of` sym's formal params are `tyGenericParam` so both arguments of the generated `of` call are never processed. 3. `getCurrentException()` increfs `currException` via `=copy` into its result. Since the arc pass never wraps that owned temp in a destroy (`--expandArc` shows the condition left untouched, while a user-written `if f() of ValueError` in the same iterator gets a `:tmpD` + `=destroy`), the caught exception's refcount stays +1 forever. Every `try: await x() except SomeError` in async code has this shape, so each caught async exception leaked once. ## Fix The state-machine wrapper already stores the active exception in the `:curExc` env field before jumping to the except landing state, and `currException == :curExc` on every path into that state. The generated condition now references the env field via `ctx.newCurExcAccess()` instead of calling `getCurrentException()` again -- no ownership transfer, no temp to destroy, one fewer runtime call. Note: the underlying `injectdestructors` behavior (skipping args of calls whose formal params are raw `tyGenericParam`, e.g. from `getSysMagic`) is a separate latent gap that could affect other compiler-generated code; it is intentionally left untouched here. ## Valgrind, before and after Exact code and command from the issue, on Linux (Valgrind 3.19): ``` nim c -d:danger --mm:orc --debugger:native --threads:off -d:useMalloc bug.nim valgrind --leak-check=full --show-leak-kinds=all ./bug ``` Before (devel): ``` ==14663== HEAP SUMMARY: ==14663== in use at exit: 136 bytes in 2 blocks ==14663== total heap usage: 22 allocs, 20 frees, 116,466 bytes allocated ==14663== ==14663== 56 bytes in 1 blocks are indirectly lost in loss record 1 of 2 ==14663== at 0x488A1C4: realloc (vg_replace_malloc.c:1437) ==14663== by 0x10C663: prepareSeqAddUninit (seqs_v2.nim:212) ==14663== by 0x10CF6F: raiseExceptionEx (excpt.nim:538) ==14663== by 0x11661F: amain::amainX20X28AsyncX29_(Future<void>) (bug.nim:10) ==14663== ... ==14663== ==14663== 136 (80 direct, 56 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2 ==14663== at 0x48850C8: malloc (vg_replace_malloc.c:381) ==14663== by 0x10C8E3: nimNewObj (arc.nim:122) ==14663== by 0x115403: err::errX20X28AsyncX29_(Future<void>) (asyncmacro.nim:274) ==14663== by 0x116027: err::errNimAsyncContinue(Future<void>, ClosureIt<void>) (asyncmacro.nim:44) ==14663== by 0x1163D3: bug::err (bug.nim:3) ==14663== ... ==14663== ==14663== LEAK SUMMARY: ==14663== definitely lost: 80 bytes in 1 blocks ==14663== indirectly lost: 56 bytes in 1 blocks ==14663== possibly lost: 0 bytes in 0 blocks ==14663== still reachable: 0 bytes in 0 blocks ==14663== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ``` After (this PR): ``` ==14675== HEAP SUMMARY: ==14675== in use at exit: 0 bytes in 0 blocks ==14675== total heap usage: 22 allocs, 22 frees, 116,466 bytes allocated ==14675== ==14675== All heap blocks were freed -- no leaks are possible ==14675== ==14675== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ``` ## Testing - New `tests/async/t23615.nim` (modeled on `t23212.nim`: `valgrind: true` + alloc-stats assertion) covers both the pure closure-iterator form and the async form from the issue, with the caught exception looped 50x so the leak blows well past the slack threshold. It passes with this PR and fails against devel. - Testament categories `async`, `arc`, `iter`, `exception` all pass with the patched compiler (323 tests). - Behavior is unchanged on a sanity program covering multi-branch dispatch, `as e` binding, nested try, and re-raise across yields: output is byte-identical to devel; the patched build just frees 2 more blocks per caught exception.
1563 lines
51 KiB
Nim
1563 lines
51 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2018 Nim Contributors
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
# This file implements closure iterator transformations.
|
|
# The main idea is to split the closure iterator body to top level statements.
|
|
# The body is split by yield statement.
|
|
#
|
|
# Example:
|
|
# while a > 0:
|
|
# echo "hi"
|
|
# yield a
|
|
# dec a
|
|
#
|
|
# Should be transformed to:
|
|
# case :state
|
|
# of 0:
|
|
# if a > 0:
|
|
# echo "hi"
|
|
# :state = 1 # Next state
|
|
# return a # yield
|
|
# else:
|
|
# :state = 2 # Next state
|
|
# break :stateLoop # Proceed to the next state
|
|
# of 1:
|
|
# dec a
|
|
# :state = 0 # Next state
|
|
# break :stateLoop # Proceed to the next state
|
|
# of 2:
|
|
# :state = -1 # End of execution
|
|
# else:
|
|
# return
|
|
|
|
# Lambdalifting treats :state variable specially, it should always end up
|
|
# as the first field in env. Currently C codegen depends on this behavior.
|
|
|
|
# One special subtransformation is nkStmtListExpr lowering.
|
|
# Example:
|
|
# template foo(): int =
|
|
# yield 1
|
|
# 2
|
|
#
|
|
# iterator it(): int {.closure.} =
|
|
# if foo() == 2:
|
|
# yield 3
|
|
#
|
|
# If a nkStmtListExpr has yield inside, it has first to be lowered to:
|
|
# yield 1
|
|
# :tmpSlLower = 2
|
|
# if :tmpSlLower == 2:
|
|
# yield 3
|
|
|
|
# nkTryStmt Transformations:
|
|
# If the iter has an nkTryStmt with a yield inside
|
|
# - the closure iter is promoted to have exceptions (ctx.hasExceptions = true)
|
|
# - exception table is created. This is a const array, where
|
|
# `exceptionTable[i]` is exception landing state idx to which we should jump from state
|
|
# `i` should exception be raised in state `i`. For all states in `try` block
|
|
# 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 :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.
|
|
# - the iter body is wrapped into a
|
|
# var :tmp: Exception
|
|
# try:
|
|
# ...body...
|
|
# catch:
|
|
# :state = exceptionTable[:state]
|
|
# if :state == 0: raise # No state that could handle exception
|
|
# :tmp = getCurrentException()
|
|
# pushCurrentException(:tmp)
|
|
#
|
|
# nkReturnStmt within a try/except/finally now has to behave differently as we
|
|
# want parent finallies to be executed before the return, thus it is
|
|
# transformed to:
|
|
# :tmpResult = returnValue (if return doesn't have a value, this is skipped)
|
|
# :finallyPath[0] = 0 # Finally at the bottom should just exit
|
|
# :finallyPath[N] = finallyNMinus1State # Next finally should exit to its parent
|
|
# goto finallyNState (or -1 if not exists) # finallyN is the nearest finally
|
|
#
|
|
# Example:
|
|
#
|
|
# try:
|
|
# yield 0
|
|
# raise ...
|
|
# except:
|
|
# yield 1
|
|
# return 3
|
|
# finally:
|
|
# yield 2
|
|
# somethingElse()
|
|
#
|
|
# Is transformed to (yields are left in place for example simplicity,
|
|
# in reality the code is subdivided even more, as described above):
|
|
#
|
|
# case :state
|
|
# of 0: # Try
|
|
# :finallyPath[LEVEL] = curExcLandingState # should exception occur our finally
|
|
# # must jump to its landing
|
|
# yield 0
|
|
# raise ...
|
|
# :finallyPath[LEVEL] = 3 # Exception did not happen. Our finally can continue to state 3
|
|
# :state = 2 # And we continue to our finally
|
|
# break :stateLoop
|
|
# of 1: # Except
|
|
# yield 1
|
|
# :tmpResult = 3 # Return
|
|
# :finalyPath[LEVEL] = 0 # Configure finally path.
|
|
# :state = 2 # Goto Finally
|
|
# break :stateLoop
|
|
# popCurrentException() # XXX: This is likely wrong, see #25031
|
|
# :state = 2 # What would happen should we not return
|
|
# break :stateLoop
|
|
# of 2: # Finally
|
|
# yield 2
|
|
# if :finallyPath[LEVEL] == 0: # This node is created by `newEndFinallyNode`
|
|
# if :curExc == nil:
|
|
# :state = -1
|
|
# return result = :tmpResult
|
|
# else:
|
|
# raise
|
|
# :state = :finallyPath[LEVEL] # Go to next state
|
|
# break :stateLoop
|
|
# of 3:
|
|
# somethingElse()
|
|
# :state = -1 # Exit
|
|
# break :stateLoop
|
|
# else:
|
|
# return
|
|
|
|
import
|
|
ast, msgs, idents,
|
|
renderer, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos, trees
|
|
|
|
import std/tables
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/assertions
|
|
|
|
type
|
|
FinallyTarget = object
|
|
n: PNode # nkWhileStmt, nkBlock, nkFinally
|
|
label: PNode # exit state for blocks and whiles (used by breaks),
|
|
# or enter state for finallies (used by breaks and returns)
|
|
|
|
State = object
|
|
label: PNode # Int literal with state idx. It is filled after state split
|
|
body: PNode
|
|
excLandingState: PNode # label of exception landing state (except or finally)
|
|
inlinable: bool
|
|
deletable: bool
|
|
|
|
Ctx = object
|
|
g: ModuleGraph
|
|
fn: PSym
|
|
tmpResultSym: PSym # Used when we return, but finally has to interfere
|
|
finallyPathSym: PSym
|
|
curExcSym: PSym # Current exception
|
|
externExcSym: PSym # Extern exception: what would getCurrentException() return outside of closure iter
|
|
|
|
enclosingPragmas: seq[PNode] # stack of pragma blocks wrapping stmtlist
|
|
|
|
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
|
|
curFinallyLevel: int
|
|
idgen: IdGenerator
|
|
varStates: Table[ItemId, int] # Used to detect if local variable belongs to multiple states
|
|
finallyPathLen: PNode # int literal
|
|
|
|
nullifyCurExc: PNode # Empty node, if no yields in tries
|
|
restoreExternExc: PNode # Empty node, id no yields in tries
|
|
|
|
const
|
|
nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt,
|
|
nkCommentStmt, nkMixinStmt, nkBindStmt, nkTypeOfExpr} + procDefs
|
|
localNotSeen = -1
|
|
localRequiresLifting = -2
|
|
|
|
proc newStateAccess(ctx: var Ctx): PNode =
|
|
result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)),
|
|
getStateField(ctx.g, ctx.fn), ctx.fn.info)
|
|
|
|
proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode =
|
|
# Creates state assignment:
|
|
# :state = toValue
|
|
newTree(nkAsgn, ctx.newStateAccess(), toValue)
|
|
|
|
proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
|
|
result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.idgen, ctx.fn, ctx.fn.info)
|
|
result.typ = typ
|
|
result.flagsImpl.incl sfNoInit
|
|
assert(not typ.isNil, "Env var needs a type")
|
|
|
|
let envParam = getEnvParam(ctx.fn)
|
|
result = addUniqueField(envParam.typ.elementType, result, ctx.g.cache, ctx.idgen)
|
|
|
|
proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode =
|
|
result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info)
|
|
|
|
proc newTempVarAccess(ctx: Ctx, s: PSym): PNode =
|
|
result = newSymNode(s, ctx.fn.info)
|
|
|
|
proc newTmpResultAccess(ctx: var Ctx): PNode =
|
|
if ctx.tmpResultSym.isNil:
|
|
ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ.returnType)
|
|
ctx.newEnvVarAccess(ctx.tmpResultSym)
|
|
|
|
proc newArrayType(g: ModuleGraph; len: PNode, t: PType; idgen: IdGenerator; owner: PSym): PType =
|
|
result = newType(tyArray, idgen, owner)
|
|
|
|
let rng = newType(tyRange, idgen, owner)
|
|
rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), len)
|
|
rng.rawAddSon(t)
|
|
|
|
result.rawAddSon(rng)
|
|
result.rawAddSon(t)
|
|
|
|
proc newFinallyPathAccess(ctx: var Ctx, level: int, info: TLineInfo): PNode =
|
|
# ctx.:finallyPath[level]
|
|
let minPathLen = level + 1
|
|
if ctx.finallyPathSym.isNil:
|
|
ctx.finallyPathLen = ctx.g.newIntLit(ctx.fn.info, minPathLen)
|
|
let ty = ctx.g.newArrayType(ctx.finallyPathLen, ctx.g.getSysType(ctx.fn.info, tyInt16), ctx.idgen, ctx.fn)
|
|
ctx.finallyPathSym = ctx.newEnvVar(":finallyPath", ty)
|
|
elif ctx.finallyPathLen.intVal < minPathLen:
|
|
ctx.finallyPathLen.intVal = minPathLen
|
|
|
|
result = newTreeIT(nkBracketExpr, info, ctx.g.getSysType(info, tyInt),
|
|
ctx.newEnvVarAccess(ctx.finallyPathSym),
|
|
ctx.g.newIntLit(ctx.fn.info, level))
|
|
|
|
proc newFinallyPathAssign(ctx: var Ctx, level: int, label: PNode, info: TLineInfo): PNode =
|
|
assert(label != nil)
|
|
let fp = newFinallyPathAccess(ctx, level, info)
|
|
result = newTree(nkAsgn, fp, label)
|
|
|
|
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 =
|
|
result = nkIntLit.newIntNode(0)
|
|
result.typ = getSysType(ctx.g, TLineInfo(), tyInt16)
|
|
|
|
proc newState(ctx: var Ctx, n: PNode, inlinable: bool, label: PNode): PNode =
|
|
# Creates a new state, adds it to the context
|
|
# Returns label of the newly created state
|
|
result = label
|
|
if result.isNil: result = ctx.newStateLabel()
|
|
assert(result.kind == nkIntLit)
|
|
|
|
ctx.states.add(State(label: result, body: n, excLandingState: ctx.curExcLandingState, inlinable: inlinable))
|
|
|
|
proc toStmtList(n: PNode): PNode =
|
|
result = n
|
|
if result.kind notin {nkStmtList, nkStmtListExpr}:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
result.add(n)
|
|
|
|
proc addGotoOut(n: PNode, gotoOut: PNode): PNode =
|
|
# Make sure `n` is a stmtlist, and ends with `gotoOut`
|
|
result = toStmtList(n)
|
|
if result.len == 0 or result[^1].kind != nkGotoState:
|
|
result.add(gotoOut)
|
|
|
|
proc newTempVarDef(ctx: Ctx, s: PSym, initialValue: PNode): PNode =
|
|
var v = initialValue
|
|
if v == nil:
|
|
v = ctx.g.emptyNode
|
|
newTree(nkVarSection, newTree(nkIdentDefs, newSymNode(s), ctx.g.emptyNode, v))
|
|
|
|
proc newTempVar(ctx: var Ctx, typ: PType, parent: PNode, initialValue: PNode = nil): PSym =
|
|
result = newSym(skVar, getIdent(ctx.g.cache, ":tmpSlLower" & $ctx.tempVarId), ctx.idgen, ctx.fn, ctx.fn.info)
|
|
inc ctx.tempVarId
|
|
result.typ = typ
|
|
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
|
|
of nkYieldStmt:
|
|
result = true
|
|
of nkSkip:
|
|
result = false
|
|
else:
|
|
result = false
|
|
for i in ord(n.kind == nkCast)..<n.len:
|
|
if n[i].hasYields:
|
|
result = true
|
|
break
|
|
|
|
proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
|
|
# :curExc = nil
|
|
let curExc = ctx.newCurExcAccess()
|
|
curExc.info = info
|
|
let nilnode = newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil))
|
|
result = newTree(nkAsgn, curExc, nilnode)
|
|
|
|
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)
|
|
|
|
proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
|
var ifStmt = newNodeI(nkIfStmt, n.info)
|
|
let g = ctx.g
|
|
for c in n:
|
|
if c.kind == nkExceptBranch:
|
|
var ifBranch: PNode
|
|
|
|
if c.len > 1:
|
|
var cond: PNode = nil
|
|
for i in 0..<c.len - 1:
|
|
assert(c[i].kind == nkType)
|
|
# Use the :curExc env field (set by the wrapper before entering the
|
|
# except landing state) instead of calling getCurrentException():
|
|
# injectdestructors does not process the args of this raw generic
|
|
# `of` magic call, so an owning getCurrentException() temp would
|
|
# never be destroyed and the caught exception would leak (#23615).
|
|
let nextCond = newTreeIT(nkCall, c.info, ctx.g.getSysType(c.info, tyBool),
|
|
newSymNode(g.getSysMagic(c.info, "of", mOf)),
|
|
ctx.newCurExcAccess(),
|
|
c[i])
|
|
|
|
cond = if cond.isNil: nextCond
|
|
else: g.newOr(cond, nextCond)
|
|
|
|
ifBranch = newTreeI(nkElifBranch, c.info, cond)
|
|
else:
|
|
if ifStmt.len == 0:
|
|
ifStmt = newNodeI(nkStmtList, c.info)
|
|
ifBranch = newNodeI(nkStmtList, c.info)
|
|
else:
|
|
ifBranch = newNodeI(nkElse, c.info)
|
|
|
|
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): 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
|
|
# and we should go to parent exception landing state
|
|
let action =
|
|
if ctx.curFinallyLevel == 0:
|
|
# There is no suitable finally around. We must reraise.
|
|
newTreeI(nkRaiseStmt, n.info, ctx.g.emptyNode)
|
|
else:
|
|
# Jump to finally.
|
|
newTree(nkGotoState, ctx.curExcLandingState)
|
|
|
|
n[0].add(newTree(nkElse,
|
|
newTreeI(nkStmtList, n.info, action)))
|
|
|
|
# Exception is handled
|
|
# XXX: This is likely wrong. See #25031. We must clear exception not only here
|
|
# at the end of except but also when except flow is interrupted with break or return
|
|
# and if there's a raise in except flow, current exception must be replaced with the
|
|
# 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
|
|
n.add(ctx.newFinallyPathAssign(ctx.curFinallyLevel - 1, gotoOut[0], n.info))
|
|
|
|
proc getFinallyNode(ctx: var Ctx, n: PNode): PNode =
|
|
result = n[^1]
|
|
if result.kind == nkFinally:
|
|
result = result[0]
|
|
else:
|
|
result = ctx.g.emptyNode
|
|
|
|
proc hasYieldsInExpressions(n: PNode): bool =
|
|
case n.kind
|
|
of nkSkip:
|
|
result = false
|
|
of nkStmtListExpr:
|
|
if isEmptyType(n.typ):
|
|
result = false
|
|
for c in n:
|
|
if c.hasYieldsInExpressions:
|
|
return true
|
|
else:
|
|
result = n.hasYields
|
|
of nkCast:
|
|
result = false
|
|
for i in 1..<n.len:
|
|
if n[i].hasYieldsInExpressions:
|
|
return true
|
|
else:
|
|
result = false
|
|
for c in n:
|
|
if c.hasYieldsInExpressions:
|
|
return true
|
|
|
|
proc exprToStmtList(n: PNode): tuple[s, res: PNode] =
|
|
assert(n.kind == nkStmtListExpr)
|
|
result = (newNodeI(nkStmtList, n.info), nil)
|
|
result.s.sons = @[]
|
|
|
|
var n = n
|
|
while n.kind == nkStmtListExpr:
|
|
result.s.sons.add(n.sons)
|
|
result.s.sons.setLen(result.s.len - 1) # delete last son
|
|
n = n[^1]
|
|
|
|
result.res = n
|
|
|
|
proc newTempVarAsgn(ctx: Ctx, s: PSym, v: PNode): PNode =
|
|
if isEmptyType(v.typ):
|
|
result = v
|
|
else:
|
|
result = newTree(nkFastAsgn, ctx.newTempVarAccess(s), v)
|
|
result.info = v.info
|
|
|
|
proc addExprAssgn(ctx: Ctx, output, input: PNode, sym: PSym) =
|
|
if input.kind == nkStmtListExpr:
|
|
let (st, res) = exprToStmtList(input)
|
|
output.add(st)
|
|
output.add(ctx.newTempVarAsgn(sym, res))
|
|
else:
|
|
output.add(ctx.newTempVarAsgn(sym, input))
|
|
|
|
proc convertExprBodyToAsgn(ctx: Ctx, exprBody: PNode, res: PSym): PNode =
|
|
result = newNodeI(nkStmtList, exprBody.info)
|
|
ctx.addExprAssgn(result, exprBody, res)
|
|
|
|
proc newNotCall(g: ModuleGraph; e: PNode): PNode =
|
|
result = newTreeIT(nkCall, e.info, g.getSysType(e.info, tyBool),
|
|
newSymNode(g.getSysMagic(e.info, "not", mNot), e.info), e)
|
|
|
|
proc boolLit(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
|
|
result = newIntLit(g, info, ord value)
|
|
result.typ = getSysType(g, info, tyBool)
|
|
|
|
proc captureVar(c: var Ctx, s: PSym) =
|
|
if c.varStates.getOrDefault(s.itemId) != localRequiresLifting:
|
|
c.varStates[s.itemId] = localRequiresLifting # Mark this variable for lifting
|
|
let e = getEnvParam(c.fn)
|
|
discard addField(e.typ.elementType, s, c.g.cache, c.idgen)
|
|
|
|
proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
|
|
result = n
|
|
case n.kind
|
|
of nkSkip:
|
|
discard
|
|
|
|
of nkYieldStmt:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
result.add(n)
|
|
|
|
needsSplit = true
|
|
|
|
of nkPar, nkObjConstr, nkTupleConstr, nkBracket:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
|
|
if n.typ.isNil: internalError(ctx.g.config, "lowerStmtListExprs: constr typ.isNil")
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
|
|
for i in 0..<n.len:
|
|
case n[i].kind
|
|
of nkExprColonExpr:
|
|
if n[i][1].kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[i][1])
|
|
result.add(st)
|
|
n[i][1] = ex
|
|
of nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[i])
|
|
result.add(st)
|
|
n[i] = ex
|
|
else: discard
|
|
result.add(n)
|
|
|
|
of nkIfStmt, nkIfExpr:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
var tmp: PSym = nil
|
|
let isExpr = not isEmptyType(n.typ)
|
|
if isExpr:
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
tmp = ctx.newTempVar(n.typ, result)
|
|
else:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
|
|
var curS = result
|
|
|
|
for branch in n:
|
|
case branch.kind
|
|
of nkElseExpr, nkElse:
|
|
if isExpr:
|
|
let branchBody = newNodeI(nkStmtList, branch.info)
|
|
ctx.addExprAssgn(branchBody, branch[0], tmp)
|
|
let newBranch = newTree(nkElse, branchBody)
|
|
curS.add(newBranch)
|
|
else:
|
|
curS.add(branch)
|
|
|
|
of nkElifExpr, nkElifBranch:
|
|
var newBranch: PNode
|
|
if branch[0].kind == nkStmtListExpr:
|
|
let (st, res) = exprToStmtList(branch[0])
|
|
let elseBody = newTree(nkStmtList, st)
|
|
|
|
newBranch = newTree(nkElifBranch, res, branch[1])
|
|
|
|
let newIf = newTree(nkIfStmt, newBranch)
|
|
elseBody.add(newIf)
|
|
if curS.kind == nkIfStmt:
|
|
let newElse = newNodeI(nkElse, branch.info)
|
|
newElse.add(elseBody)
|
|
curS.add(newElse)
|
|
else:
|
|
curS.add(elseBody)
|
|
curS = newIf
|
|
else:
|
|
newBranch = branch
|
|
if curS.kind == nkIfStmt:
|
|
curS.add(newBranch)
|
|
else:
|
|
let newIf = newTree(nkIfStmt, newBranch)
|
|
curS.add(newIf)
|
|
curS = newIf
|
|
|
|
if isExpr:
|
|
let branchBody = newNodeI(nkStmtList, branch[1].info)
|
|
ctx.addExprAssgn(branchBody, branch[1], tmp)
|
|
newBranch[1] = branchBody
|
|
|
|
else:
|
|
internalError(ctx.g.config, "lowerStmtListExpr(nkIf): " & $branch.kind)
|
|
|
|
if isExpr: result.add(ctx.newTempVarAccess(tmp))
|
|
|
|
of nkTryStmt, nkHiddenTryStmt:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
let isExpr = not isEmptyType(n.typ)
|
|
|
|
if isExpr:
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
let tmp = ctx.newTempVar(n.typ, result)
|
|
|
|
n[0] = ctx.convertExprBodyToAsgn(n[0], tmp)
|
|
for i in 1..<n.len:
|
|
let branch = n[i]
|
|
case branch.kind
|
|
of nkExceptBranch:
|
|
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
|
|
of nkFinally:
|
|
discard
|
|
else:
|
|
internalError(ctx.g.config, "lowerStmtListExpr(nkTryStmt): " & $branch.kind)
|
|
result.add(n)
|
|
result.add(ctx.newTempVarAccess(tmp))
|
|
|
|
of nkCaseStmt:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
|
|
let isExpr = not isEmptyType(n.typ)
|
|
|
|
if isExpr:
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
let tmp = ctx.newTempVar(n.typ, result)
|
|
|
|
if n[0].kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
|
|
for i in 1..<n.len:
|
|
let branch = n[i]
|
|
case branch.kind
|
|
of nkOfBranch:
|
|
branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
|
|
of nkElse:
|
|
branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
|
|
else:
|
|
internalError(ctx.g.config, "lowerStmtListExpr(nkCaseStmt): " & $branch.kind)
|
|
result.add(n)
|
|
result.add(ctx.newTempVarAccess(tmp))
|
|
elif n[0].kind == nkStmtListExpr:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
result.add(n)
|
|
|
|
of nkCallKinds, nkChckRange, nkChckRangeF, nkChckRange64:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
let isExpr = not isEmptyType(n.typ)
|
|
|
|
if isExpr:
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
else:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
|
|
if n[0].kind == nkSym and n[0].sym.magic in {mAnd, mOr}: # `and`/`or` short cirquiting
|
|
var cond = n[1]
|
|
if cond.kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(cond)
|
|
result.add(st)
|
|
cond = ex
|
|
|
|
let tmp = ctx.newTempVar(cond.typ, result, cond)
|
|
# result.add(ctx.newTempVarAsgn(tmp, cond))
|
|
|
|
var check = ctx.newTempVarAccess(tmp)
|
|
if n[0].sym.magic == mOr:
|
|
check = ctx.g.newNotCall(check)
|
|
|
|
cond = n[2]
|
|
let ifBody = newNodeI(nkStmtList, cond.info)
|
|
if cond.kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(cond)
|
|
ifBody.add(st)
|
|
cond = ex
|
|
ifBody.add(ctx.newTempVarAsgn(tmp, cond))
|
|
|
|
let ifBranch = newTree(nkElifBranch, check, ifBody)
|
|
let ifNode = newTree(nkIfStmt, ifBranch)
|
|
result.add(ifNode)
|
|
result.add(ctx.newTempVarAccess(tmp))
|
|
else:
|
|
for i in 0..<n.len:
|
|
if n[i].kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[i])
|
|
result.add(st)
|
|
n[i] = ex
|
|
|
|
if n[i].kind in nkCallKinds: # XXX: This should better be some sort of side effect tracking
|
|
let tmp = ctx.newTempVar(n[i].typ, result, n[i])
|
|
# result.add(ctx.newTempVarAsgn(tmp, n[i]))
|
|
n[i] = ctx.newTempVarAccess(tmp)
|
|
|
|
result.add(n)
|
|
|
|
of nkVarSection, nkLetSection:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
for c in n:
|
|
let varSect = newNodeI(n.kind, n.info)
|
|
varSect.add(c)
|
|
var ns = false
|
|
c[^1] = ctx.lowerStmtListExprs(c[^1], ns)
|
|
if ns:
|
|
needsSplit = true
|
|
let (st, ex) = exprToStmtList(c[^1])
|
|
result.add(st)
|
|
c[^1] = ex
|
|
for i in 0 .. c.len - 3:
|
|
if c[i].kind == nkSym:
|
|
let s = c[i].sym
|
|
if sfForceLift in s.flags:
|
|
ctx.captureVar(s)
|
|
|
|
result.add(varSect)
|
|
|
|
of nkDiscardStmt, nkReturnStmt, nkRaiseStmt:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
result = newNodeI(nkStmtList, n.info)
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
result.add(n)
|
|
|
|
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, nkObjUpConv,
|
|
nkDerefExpr, nkHiddenDeref:
|
|
var ns = false
|
|
for i in ord(n.kind == nkCast)..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
let (st, ex) = exprToStmtList(n[^1])
|
|
result.add(st)
|
|
n[^1] = ex
|
|
result.add(n)
|
|
|
|
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
|
var ns = false
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], ns)
|
|
|
|
if ns:
|
|
needsSplit = true
|
|
result = newNodeI(nkStmtList, n.info)
|
|
if n[0].kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
|
|
if n[1].kind == nkStmtListExpr:
|
|
let (st, ex) = exprToStmtList(n[1])
|
|
result.add(st)
|
|
n[1] = ex
|
|
|
|
result.add(n)
|
|
|
|
of nkBracketExpr:
|
|
var lhsNeedsSplit = false
|
|
var rhsNeedsSplit = false
|
|
n[0] = ctx.lowerStmtListExprs(n[0], lhsNeedsSplit)
|
|
n[1] = ctx.lowerStmtListExprs(n[1], rhsNeedsSplit)
|
|
if lhsNeedsSplit or rhsNeedsSplit:
|
|
needsSplit = true
|
|
result = newNodeI(nkStmtListExpr, n.info)
|
|
if lhsNeedsSplit:
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
|
|
if rhsNeedsSplit:
|
|
let (st, ex) = exprToStmtList(n[1])
|
|
result.add(st)
|
|
n[1] = ex
|
|
result.add(n)
|
|
|
|
of nkWhileStmt:
|
|
var condNeedsSplit = false
|
|
n[0] = ctx.lowerStmtListExprs(n[0], condNeedsSplit)
|
|
var bodyNeedsSplit = false
|
|
n[1] = ctx.lowerStmtListExprs(n[1], bodyNeedsSplit)
|
|
|
|
if condNeedsSplit or bodyNeedsSplit:
|
|
needsSplit = true
|
|
|
|
if condNeedsSplit:
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
let brk = newTree(nkBreakStmt, ctx.g.emptyNode)
|
|
let branch = newTree(nkElifBranch, ctx.g.newNotCall(ex), brk)
|
|
let check = newTree(nkIfStmt, branch)
|
|
let newBody = newTree(nkStmtList, st, check, n[1])
|
|
|
|
n[0] = ctx.g.boolLit(n[0].info, true)
|
|
n[1] = newBody
|
|
|
|
of nkDotExpr, nkCheckedFieldExpr:
|
|
var ns = false
|
|
n[0] = ctx.lowerStmtListExprs(n[0], ns)
|
|
if ns:
|
|
needsSplit = true
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
let (st, ex) = exprToStmtList(n[0])
|
|
result.add(st)
|
|
n[0] = ex
|
|
result.add(n)
|
|
|
|
of nkBlockExpr:
|
|
var ns = false
|
|
n[1] = ctx.lowerStmtListExprs(n[1], ns)
|
|
if ns:
|
|
needsSplit = true
|
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
let (st, ex) = exprToStmtList(n[1])
|
|
n.transitionSonsKind(nkBlockStmt)
|
|
n.typ = nil
|
|
n[1] = st
|
|
result.add(n)
|
|
result.add(ex)
|
|
|
|
else:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.lowerStmtListExprs(n[i], needsSplit)
|
|
|
|
proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
|
|
# Generate the following code:
|
|
# if :finallyPath[FINALLY_LEVEL] == 0:
|
|
# if :curExc == nil:
|
|
# :state = -1
|
|
# return result = :tmpResult
|
|
# else:
|
|
# raise
|
|
|
|
let cmpStateToZero = newTreeIT(nkCall,
|
|
info, ctx.g.getSysType(info, tyBool),
|
|
newSymNode(ctx.g.getSysMagic(info, "==", mEqI), info),
|
|
ctx.newFinallyPathAccess(ctx.curFinallyLevel, info),
|
|
ctx.g.newIntLit(info, 0))
|
|
|
|
let excNilCmp = newTreeIT(nkCall,
|
|
info, ctx.g.getSysType(info, tyBool),
|
|
newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info),
|
|
ctx.newCurExcAccess(),
|
|
newNodeIT(nkNilLit, info, getSysType(ctx.g, info, tyNil)))
|
|
|
|
let retStmt =
|
|
block:
|
|
let retValue = if ctx.fn.typ.returnType.isNil:
|
|
ctx.g.emptyNode
|
|
else:
|
|
newTree(nkFastAsgn,
|
|
newSymNode(getClosureIterResult(ctx.g, ctx.fn, ctx.idgen), info),
|
|
ctx.newTmpResultAccess())
|
|
newTree(nkReturnStmt, retValue)
|
|
|
|
# XXX: nfNoRewrite flag is also used for term rewriting, which should not intersect
|
|
# with how it is used in closureiters transformation
|
|
retStmt.flags.incl(nfNoRewrite)
|
|
|
|
let ifBody = newTree(nkIfStmt,
|
|
newTree(nkElifBranch, excNilCmp, newTree(nkStmtList, ctx.newRestoreExternException(), retStmt)),
|
|
newTree(nkElse,
|
|
newTree(nkStmtList,
|
|
newTreeI(nkRaiseStmt, info, ctx.g.emptyNode))))
|
|
|
|
result = newTree(nkIfStmt,
|
|
newTreeI(nkElifBranch, info, cmpStateToZero, ifBody))
|
|
|
|
|
|
proc newJumpAlongFinallyChain(ctx: var Ctx, finallyChain: seq[PNode], info: TLineInfo): PNode =
|
|
# The chain must contain labels of all the states we should go through,
|
|
# where the first element is nearest finally, every following is parent finally,
|
|
# and the last is the target state.
|
|
# If len == 1, it only contains the target state.
|
|
|
|
# Setup finally path with finallyChain[1 .. ^1], and jump to finallyChain[0]
|
|
result = newNode(nkStmtList, info)
|
|
for i in countdown(finallyChain.high, 1):
|
|
result.add(ctx.newFinallyPathAssign(ctx.curFinallyLevel - i, finallyChain[i], info))
|
|
result.add(newTreeI(nkGotoState, info, finallyChain[0]))
|
|
|
|
proc transformBreakStmt(ctx: var Ctx, n: PNode): PNode =
|
|
# "Breaking" involves finding the corresponding target state in finallyPathStack,
|
|
# setting finallyPath so that it chains to the target state and jumping to nearest finally.
|
|
# If there are no finallies on the way, then jump to the target block right away
|
|
|
|
# The target can be either a named block (breaking in unnamed blocks is forbidden)
|
|
# or a while (if targetName is empty).
|
|
# Let's find it in finallyTarget stack, and remember all finallies on the way to it.
|
|
let targetName = if n[0].kind == nkSym: n[0].sym else: nil
|
|
var finallyChain = newSeq[PNode]()
|
|
|
|
var targetFound = false
|
|
for i in countdown(ctx.finallyPathStack.high, 0):
|
|
let b = ctx.finallyPathStack[i].n
|
|
# echo "STACK ", i, " ", b.kind
|
|
if b.kind == nkBlockStmt and targetName != nil and b[0].sym == targetName:
|
|
finallyChain.add(ctx.finallyPathStack[i].label)
|
|
targetFound = true
|
|
break
|
|
elif b.kind == nkWhileStmt and targetName == nil:
|
|
finallyChain.add(ctx.finallyPathStack[i].label)
|
|
targetFound = true
|
|
break
|
|
elif b.kind == nkFinally:
|
|
finallyChain.add(ctx.finallyPathStack[i].label)
|
|
|
|
if targetFound:# and finallyChain.len > 0:
|
|
result = ctx.newJumpAlongFinallyChain(finallyChain, n.info)
|
|
else:
|
|
# Target is not in finally path means that it doesn't have yields (no state split),
|
|
# so we don't have to transform this break.
|
|
result = n
|
|
|
|
proc transformReturnStmt(ctx: var Ctx, n: PNode): PNode =
|
|
# "Returning" involves jumping along all the current finally path.
|
|
# The last finally should exit to state 0 which is a special case for last exit
|
|
# (either return or propagating exception to the caller).
|
|
# It is eccounted for in newEndFinallyNode.
|
|
result = newNodeI(nkStmtList, n.info)
|
|
|
|
# Returns prevent exception propagation
|
|
result.add(ctx.nullifyCurExc)
|
|
|
|
|
|
var finallyChain = newSeq[PNode]()
|
|
|
|
for i in countdown(ctx.finallyPathStack.high, 0):
|
|
let b = ctx.finallyPathStack[i].n
|
|
# echo "STACK ", i, " ", b.kind
|
|
if b.kind == nkFinally:
|
|
finallyChain.add(ctx.finallyPathStack[i].label)
|
|
|
|
if finallyChain.len > 0:
|
|
# Add proc exit state
|
|
finallyChain.add(ctx.g.newIntLit(n.info, 0))
|
|
|
|
if n[0].kind != nkEmpty:
|
|
let asgnTmpResult = newNodeI(nkAsgn, n.info)
|
|
asgnTmpResult.add(ctx.newTmpResultAccess())
|
|
let x = if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}: n[0][1] else: n[0]
|
|
asgnTmpResult.add(x)
|
|
result.add(asgnTmpResult)
|
|
|
|
result.add(ctx.newJumpAlongFinallyChain(finallyChain, n.info))
|
|
else:
|
|
# There are no (split) finallies on the path, so we can return right away
|
|
result.add(ctx.restoreExternExc)
|
|
result.add(n)
|
|
|
|
proc transformBreaksAndReturns(ctx: var Ctx, n: PNode): PNode =
|
|
result = n
|
|
case n.kind
|
|
of nkSkip: discard
|
|
of nkBreakStmt: result = ctx.transformBreakStmt(n)
|
|
# of nkContinueStmt: # By this point all relevant continues should be
|
|
# lowered to breaks in transf.nim.
|
|
of nkReturnStmt:
|
|
if nfNoRewrite notin n.flags:
|
|
result = ctx.transformReturnStmt(n)
|
|
else:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.transformBreaksAndReturns(n[i])
|
|
|
|
proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode =
|
|
result = n
|
|
case n.kind
|
|
of nkSkip: discard
|
|
|
|
of nkStmtList, nkStmtListExpr:
|
|
result = addGotoOut(result, gotoOut)
|
|
for i in 0..<n.len:
|
|
if n[i].hasYields:
|
|
# Create a new split
|
|
let label = ctx.newStateLabel()
|
|
let go = newTreeI(nkGotoState, n[i].info, label)
|
|
n[i] = ctx.transformClosureIteratorBody(n[i], go)
|
|
|
|
let s = newNodeI(nkStmtList, n[i + 1].info)
|
|
for j in i + 1..<n.len:
|
|
s.add(n[j])
|
|
|
|
var body = s
|
|
for pragma in ctx.enclosingPragmas:
|
|
body = newTreeI(nkPragmaBlock, n[i + 1].info,
|
|
pragma[0].copyTree, body)
|
|
|
|
n.sons.setLen(i + 1)
|
|
discard ctx.newState(body, true, label)
|
|
if ctx.transformClosureIteratorBody(body, gotoOut) != body:
|
|
internalError(ctx.g.config, "transformClosureIteratorBody != s")
|
|
break
|
|
else:
|
|
n[i] = ctx.transformBreaksAndReturns(n[i])
|
|
|
|
of nkYieldStmt:
|
|
result = addGotoOut(result, gotoOut)
|
|
result = newTree(nkStmtList, ctx.restoreExternExc, result)
|
|
|
|
of nkElse, nkElseExpr:
|
|
result[0] = addGotoOut(result[0], gotoOut)
|
|
result[0] = ctx.transformClosureIteratorBody(result[0], gotoOut)
|
|
|
|
of nkElifBranch, nkElifExpr, nkOfBranch:
|
|
result[^1] = addGotoOut(result[^1], gotoOut)
|
|
result[^1] = ctx.transformClosureIteratorBody(result[^1], gotoOut)
|
|
|
|
of nkIfStmt, nkCaseStmt:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut)
|
|
if n[^1].kind != nkElse:
|
|
# We don't have an else branch, but every possible branch has to end with
|
|
# gotoOut, so add else here.
|
|
let elseBranch = newTree(nkElse, gotoOut)
|
|
n.add(elseBranch)
|
|
|
|
of nkWhileStmt:
|
|
# while e:
|
|
# s
|
|
# ->
|
|
# BEGIN_STATE:
|
|
# if e:
|
|
# s
|
|
# goto BEGIN_STATE
|
|
# else:
|
|
# goto OUT
|
|
|
|
# result = newNodeI(nkGotoState, n.info)
|
|
|
|
let s = newNodeI(nkStmtList, n.info)
|
|
let enterLabel = ctx.newState(s, false, nil)
|
|
|
|
let ifNode = newNodeI(nkIfStmt, n.info)
|
|
let elifBranch = newNodeI(nkElifBranch, n.info)
|
|
elifBranch.add(n[0])
|
|
let gotoBegin = newNodeI(nkGotoState, n.info)
|
|
gotoBegin.add(enterLabel)
|
|
result = gotoBegin
|
|
|
|
var body = addGotoOut(n[1], gotoBegin)
|
|
|
|
ctx.finallyPathStack.add(FinallyTarget(n: n, label: gotoOut[0]))
|
|
body = ctx.transformClosureIteratorBody(body, gotoBegin)
|
|
discard ctx.finallyPathStack.pop()
|
|
|
|
elifBranch.add(body)
|
|
ifNode.add(elifBranch)
|
|
|
|
let elseBranch = newTree(nkElse, gotoOut)
|
|
ifNode.add(elseBranch)
|
|
s.add(ifNode)
|
|
|
|
of nkBlockStmt:
|
|
result[1] = addGotoOut(result[1], gotoOut)
|
|
ctx.finallyPathStack.add(FinallyTarget(n: result, label: gotoOut[0]))
|
|
result[1] = ctx.transformClosureIteratorBody(result[1], gotoOut)
|
|
discard ctx.finallyPathStack.pop()
|
|
|
|
of nkTryStmt, nkHiddenTryStmt:
|
|
# See explanation above about how this works
|
|
ctx.hasExceptions = true
|
|
|
|
let tryLabel = ctx.newStateLabel()
|
|
result = newNodeI(nkGotoState, n.info)
|
|
result.add(tryLabel)
|
|
var tryBody = toStmtList(n[0])
|
|
|
|
var exceptBody = ctx.collectExceptState(n)
|
|
var finallyBody = ctx.getFinallyNode(n)
|
|
var exceptLabel, finallyLabel = ctx.g.emptyNode
|
|
|
|
if exceptBody.kind != nkEmpty:
|
|
exceptLabel = ctx.newStateLabel()
|
|
|
|
if finallyBody.kind != nkEmpty:
|
|
finallyBody = newTree(nkStmtList, finallyBody,
|
|
ctx.newEndFinallyNode(finallyBody.info))
|
|
finallyLabel = ctx.newStateLabel()
|
|
|
|
var tryOut = gotoOut
|
|
if finallyBody.kind != nkEmpty:
|
|
# Add finally path to try body
|
|
# START:
|
|
# finallyPath[level] = excHandlingState
|
|
# END:
|
|
# finallyPath[level] = gotoOut
|
|
tryBody = newTree(nkStmtList,
|
|
ctx.newFinallyPathAssign(ctx.curFinallyLevel, ctx.curExcLandingState, tryBody.info),
|
|
tryBody,
|
|
ctx.newFinallyPathAssign(ctx.curFinallyLevel, gotoOut[0], tryBody.info))
|
|
|
|
tryOut = newNodeI(nkGotoState, finallyBody.info)
|
|
tryOut.add(finallyLabel)
|
|
|
|
block: # Process the states
|
|
let oldExcLandingState = ctx.curExcLandingState
|
|
ctx.curExcLandingState = if exceptBody.kind != nkEmpty: exceptLabel
|
|
elif finallyBody.kind != nkEmpty: finallyLabel
|
|
else: oldExcLandingState
|
|
|
|
discard ctx.newState(tryBody, false, tryLabel)
|
|
|
|
if finallyBody.kind != nkEmpty:
|
|
inc ctx.curFinallyLevel
|
|
ctx.finallyPathStack.add(FinallyTarget(n: n[^1], label: finallyLabel))
|
|
|
|
tryBody = ctx.transformClosureIteratorBody(tryBody, tryOut)
|
|
|
|
if exceptBody.kind != nkEmpty:
|
|
ctx.curExcLandingState = if finallyBody.kind != nkEmpty: finallyLabel
|
|
else: oldExcLandingState
|
|
discard ctx.newState(exceptBody, false, exceptLabel)
|
|
|
|
let normalOut = if finallyBody.kind != nkEmpty: gotoOut else: nil
|
|
exceptBody = ctx.addElseToExcept(exceptBody, normalOut)
|
|
# echo "EXCEPT: ", renderTree(exceptBody)
|
|
exceptBody = ctx.transformClosureIteratorBody(exceptBody, tryOut)
|
|
|
|
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))
|
|
finallyBody = ctx.transformClosureIteratorBody(finallyBody, finallyExit)
|
|
dec ctx.curFinallyLevel
|
|
|
|
of nkPragmaBlock:
|
|
# Propagate the pragma blocks so that blocks like {.cast(uncheckedAssign).}
|
|
# remain effective
|
|
ctx.enclosingPragmas.add(n)
|
|
n[1] = ctx.transformClosureIteratorBody(n[1], gotoOut)
|
|
discard ctx.enclosingPragmas.pop()
|
|
result = n
|
|
|
|
of nkGotoState, nkForStmt:
|
|
internalError(ctx.g.config, "closure iter " & $n.kind)
|
|
|
|
else:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut)
|
|
|
|
proc stateFromGotoState(n: PNode): PNode =
|
|
assert(n.kind == nkGotoState)
|
|
result = n[0]
|
|
|
|
proc transformStateAssignments(ctx: var Ctx, n: PNode): PNode =
|
|
# This transforms 3 patterns:
|
|
########################## 1
|
|
# yield e
|
|
# goto STATE
|
|
# ->
|
|
# :state = STATE
|
|
# return e
|
|
########################## 2
|
|
# goto STATE
|
|
# ->
|
|
# :state = STATE
|
|
# break :stateLoop
|
|
########################## 3
|
|
# return e
|
|
# ->
|
|
# :state = -1
|
|
# return e
|
|
result = n
|
|
case n.kind
|
|
of nkStmtList, nkStmtListExpr:
|
|
if n.len != 0 and n[0].kind == nkYieldStmt:
|
|
assert(n.len == 2)
|
|
assert(n[1].kind == nkGotoState)
|
|
|
|
result = newNodeI(nkStmtList, n.info)
|
|
result.add(ctx.newStateAssgn(stateFromGotoState(n[1])))
|
|
|
|
var retStmt = newNodeI(nkReturnStmt, n.info)
|
|
if n[0][0].kind != nkEmpty:
|
|
var a = newNodeI(nkAsgn, n[0][0].info)
|
|
var retVal = n[0][0] #liftCapturedVars(n[0], owner, d, c)
|
|
a.add newSymNode(getClosureIterResult(ctx.g, ctx.fn, ctx.idgen))
|
|
a.add retVal
|
|
retStmt.add(a)
|
|
else:
|
|
retStmt.add(ctx.g.emptyNode)
|
|
|
|
result.add(retStmt)
|
|
else:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.transformStateAssignments(n[i])
|
|
|
|
of nkSkip:
|
|
discard
|
|
|
|
of nkReturnStmt:
|
|
result = newTreeI(nkStmtList, n.info,
|
|
ctx.newStateAssgn(ctx.g.newIntLit(n.info, -1)),
|
|
n)
|
|
|
|
of nkGotoState:
|
|
result = newNodeI(nkStmtList, n.info)
|
|
result.add(ctx.newStateAssgn(stateFromGotoState(n)))
|
|
|
|
let breakState = newNodeI(nkBreakStmt, n.info)
|
|
breakState.add(newSymNode(ctx.stateLoopLabel))
|
|
result.add(breakState)
|
|
|
|
else:
|
|
for i in 0..<n.len:
|
|
n[i] = ctx.transformStateAssignments(n[i])
|
|
|
|
proc createExceptionTable(ctx: var Ctx): PNode {.inline.} =
|
|
let typ = ctx.g.newArrayType(ctx.g.newIntLit(ctx.fn.info, ctx.states.high),
|
|
ctx.g.getSysType(ctx.fn.info, tyInt16), ctx.idgen, ctx.fn)
|
|
|
|
result = newNodeIT(nkBracket, ctx.fn.info, typ)
|
|
|
|
for i in 0 .. ctx.states.high:
|
|
result.add(ctx.states[i].excLandingState)
|
|
|
|
proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
|
|
# Generates code:
|
|
# var :tmp = nil
|
|
# try:
|
|
# body
|
|
# except:
|
|
# :state = exceptionTable[:state]
|
|
# :curExc = getCurrentException()
|
|
# if :state == 0:
|
|
# closureIterSetExc(:externExc)
|
|
# raise
|
|
#
|
|
# pushCurrentException(:curExc)
|
|
|
|
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))
|
|
|
|
result = newTree(nkStmtList)
|
|
result.add newTree(nkTryStmt,
|
|
newTree(nkStmtList, n),
|
|
newTree(nkExceptBranch, exceptBody))
|
|
|
|
# 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.newCurExcAccess())
|
|
let ifBody = newTree(nkStmtList, ctx.newRestoreExternException(), raiseStmt)
|
|
let ifBranch = newTree(nkElifBranch, cond, ifBody)
|
|
let ifStmt = newTree(nkIfStmt, ifBranch)
|
|
result.add(ifStmt)
|
|
|
|
result.add newTree(nkCall, newSymNode(ctx.g.getCompilerProc("pushCurrentException")), ctx.newCurExcAccess())
|
|
|
|
proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
|
|
# while true:
|
|
# block :stateLoop:
|
|
# local vars decl (if needed)
|
|
# body # Might get wrapped in try-except
|
|
let loopBody = newNodeI(nkStmtList, n.info)
|
|
result = newTree(nkWhileStmt, ctx.g.boolLit(n.info, true), loopBody)
|
|
result.info = n.info
|
|
|
|
let localVars = newNodeI(nkStmtList, n.info)
|
|
|
|
let blockStmt = newNodeI(nkBlockStmt, n.info)
|
|
blockStmt.add(newSymNode(ctx.stateLoopLabel))
|
|
|
|
var blockBody = newTree(nkStmtList, localVars, n)
|
|
if ctx.hasExceptions:
|
|
blockBody = ctx.wrapIntoTryExcept(blockBody)
|
|
|
|
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]
|
|
for i, c in n:
|
|
if c.kind == nkGotoState and c[0].kind == nkIntLit and (i > 0 and n[i - 1].kind != nkYieldStmt):
|
|
let stateIdx = c[0].intVal
|
|
if stateIdx >= 0:
|
|
inc stateOccurences[stateIdx]
|
|
elif c.kind == nkIntLit:
|
|
let idx = c.intVal
|
|
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == c:
|
|
ctx.states[idx].inlinable = false
|
|
else:
|
|
ctx.countStateOccurences(c, stateOccurences)
|
|
|
|
proc replaceDeletedStates(ctx: var Ctx, n: PNode): PNode =
|
|
result = n
|
|
if n.kind == nkIntLit:
|
|
let idx = n.intVal
|
|
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == n and ctx.states[idx].deletable:
|
|
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
|
|
assert(gt.kind == nkGotoState)
|
|
result = gt[0]
|
|
else:
|
|
for i in 0 ..< n.safeLen:
|
|
n[i] = ctx.replaceDeletedStates(n[i])
|
|
|
|
proc replaceInlinedStates(ctx: var Ctx, n: PNode): PNode =
|
|
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
|
|
## For every such node increment stateOccurences[stateIdx]
|
|
result = n
|
|
for i in 0 ..< n.safeLen:
|
|
let c = n[i]
|
|
if c.kind == nkGotoState and c[0].kind == nkIntLit and (i > 0 and n[i - 1].kind != nkYieldStmt):
|
|
let stateIdx = c[0].intVal
|
|
if stateIdx >= 0:
|
|
if ctx.states[stateIdx].inlinable:
|
|
n[i] = ctx.states[stateIdx].body
|
|
else:
|
|
n[i] = ctx.replaceInlinedStates(c)
|
|
|
|
proc optimizeStates(ctx: var Ctx) =
|
|
# Optimize empty states away and inline inlinable states
|
|
# This step requires that unique indexes are already assigned to state labels
|
|
|
|
# Find empty states (those consisting only of gotoState node) and mark
|
|
# them deletable.
|
|
for i in 0 .. ctx.states.high:
|
|
let s = ctx.states[i]
|
|
let body = skipStmtList(s.body)
|
|
if body.kind == nkGotoState and body[0].kind == nkIntLit and body[0].intVal >= 0:
|
|
ctx.states[i].deletable = true
|
|
|
|
# Replace deletable state labels to labels of respective non-empty states
|
|
for i in 0 .. ctx.states.high:
|
|
ctx.states[i].body = ctx.replaceDeletedStates(ctx.states[i].body)
|
|
ctx.states[i].excLandingState = ctx.replaceDeletedStates(ctx.states[i].excLandingState)
|
|
|
|
# Remove deletable states
|
|
var i = 0
|
|
while i < ctx.states.len:
|
|
if ctx.states[i].deletable:
|
|
ctx.states.delete(i)
|
|
else:
|
|
inc i
|
|
|
|
# Reassign state label indexes
|
|
for i in 0 .. ctx.states.high:
|
|
ctx.states[i].label.intVal = i
|
|
|
|
# Count state occurences
|
|
var stateOccurences = newSeq[int](ctx.states.len)
|
|
for s in ctx.states:
|
|
ctx.countStateOccurences(s.body, stateOccurences)
|
|
|
|
# If there are inlinable states refered not exactly once, prevent them from inlining
|
|
for i, o in stateOccurences:
|
|
if o != 1:
|
|
ctx.states[i].inlinable = false
|
|
|
|
# echo "States to optimize:"
|
|
# for i, s in ctx.states:
|
|
# if s.deletable: echo i, ": delete"
|
|
# elif s.inlinable: echo i, ": inline"
|
|
|
|
# Inline states
|
|
for i in 0 .. ctx.states.high:
|
|
ctx.states[i].body = ctx.replaceInlinedStates(ctx.states[i].body)
|
|
|
|
# Remove inlined states
|
|
i = 0
|
|
while i < ctx.states.len:
|
|
if ctx.states[i].inlinable:
|
|
ctx.states.delete(i)
|
|
else:
|
|
inc i
|
|
|
|
# Reassign state label indexes one last time
|
|
for i in 0 .. ctx.states.high:
|
|
ctx.states[i].label.intVal = i
|
|
|
|
proc detectCapturedSym(c: var Ctx, s: PSym, stateIdx: int) =
|
|
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
|
|
elif vs == localRequiresLifting:
|
|
discard # Sym already marked
|
|
elif vs != stateIdx:
|
|
c.captureVar(s)
|
|
|
|
proc isClosureIterLocal(c: Ctx, s: PSym): bool =
|
|
s.kind in {skResult, skVar, skLet, skForVar, skTemp} and
|
|
sfGlobal notin s.flags and s.owner == c.fn and s != c.externExcSym
|
|
|
|
proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) =
|
|
case n.kind
|
|
of nkSym:
|
|
let s = n.sym
|
|
detectCapturedSym(c, s, stateIdx)
|
|
of nkAddr, nkHiddenAddr:
|
|
let s = getRoot(n)
|
|
if s != nil and isClosureIterLocal(c, s):
|
|
detectCapturedSym(c, s, stateIdx)
|
|
# bug #25596; lifetime extension for `addr`-taken locals as
|
|
# we claim ARC/ORC do destruction based on scopes, not on last-usages.
|
|
c.captureVar(s)
|
|
for i in 0 ..< n.safeLen:
|
|
detectCapturedVars(c, n[i], stateIdx)
|
|
of nkReturnStmt:
|
|
if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
|
|
# we have a `result = result` expression produced by the closure
|
|
# transform, let's not touch the LHS in order to make the lifting pass
|
|
# correct when `result` is lifted
|
|
detectCapturedVars(c, n[0][1], stateIdx)
|
|
else:
|
|
detectCapturedVars(c, n[0], stateIdx)
|
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
|
|
nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef,
|
|
nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt,
|
|
nkTypeOfExpr, nkMixinStmt, nkBindStmt:
|
|
discard
|
|
of nkLambdaKinds, nkIteratorDef:
|
|
if n.typ != nil:
|
|
detectCapturedVars(c, n[namePos], stateIdx)
|
|
else:
|
|
for i in 0 ..< n.safeLen:
|
|
detectCapturedVars(c, n[i], stateIdx)
|
|
|
|
proc detectCapturedVars(c: var Ctx) =
|
|
for i, s in c.states:
|
|
detectCapturedVars(c, s.body, i)
|
|
|
|
proc liftLocals(c: var Ctx, n: PNode): PNode =
|
|
result = n
|
|
case n.kind
|
|
of nkSym:
|
|
let s = n.sym
|
|
if c.varStates.getOrDefault(s.itemId) == localRequiresLifting:
|
|
# lift
|
|
let e = getEnvParam(c.fn)
|
|
let field = getFieldFromObj(e.typ.elementType, s)
|
|
assert(field != nil)
|
|
result = rawIndirectAccess(newSymNode(e), field, n.info)
|
|
# elif c.varStates.getOrDefault(s.itemId, localNotSeen) != localNotSeen:
|
|
# echo "Not lifting ", s.name.s
|
|
|
|
of nkReturnStmt:
|
|
if n[0].kind in {nkAsgn, nkFastAsgn, nkSinkAsgn}:
|
|
# we have a `result = result` expression produced by the closure
|
|
# transform, let's not touch the LHS in order to make the lifting pass
|
|
# correct when `result` is lifted
|
|
n[0][1] = liftLocals(c, n[0][1])
|
|
else:
|
|
n[0] = liftLocals(c, n[0])
|
|
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
|
|
nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef,
|
|
nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt,
|
|
nkTypeOfExpr, nkMixinStmt, nkBindStmt,
|
|
nkLambdaKinds, nkIteratorDef:
|
|
discard
|
|
else:
|
|
for i in 0 ..< n.safeLen:
|
|
n[i] = liftLocals(c, n[i])
|
|
|
|
proc transformClosureIterator*(g: ModuleGraph; idgen: IdGenerator; fn: PSym, n: PNode): PNode =
|
|
var ctx = Ctx(g: g, fn: fn, idgen: idgen)
|
|
|
|
# The transformation should always happen after at least partial lambdalifting
|
|
# is performed, so that the closure iter environment is always created upfront.
|
|
doAssert(getEnvParam(fn) != nil, "Env param not created before iter transformation")
|
|
|
|
ctx.curExcLandingState = ctx.newStateLabel()
|
|
ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), idgen, fn, fn.info)
|
|
|
|
|
|
ctx.nullifyCurExc = newTree(nkStmtList)
|
|
ctx.restoreExternExc = newTree(nkStmtList)
|
|
|
|
var n = n.toStmtList
|
|
# echo "transformed into ", n
|
|
|
|
discard ctx.newState(n, false, nil)
|
|
|
|
let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
|
|
|
|
var ns = false
|
|
n = ctx.lowerStmtListExprs(n, ns)
|
|
# echo "LOWERED: ", renderTree(n)
|
|
|
|
if n.hasYieldsInExpressions():
|
|
internalError(ctx.g.config, n.info, "yield in expr not lowered")
|
|
|
|
# Splitting transformation
|
|
discard ctx.transformClosureIteratorBody(n, gotoOut)
|
|
|
|
if ctx.hasExceptions:
|
|
ctx.nullifyCurExc.add(ctx.newNullifyCurExc(fn.info))
|
|
ctx.restoreExternExc.add(ctx.newRestoreExternException())
|
|
|
|
# Assign state label indexes
|
|
for i in 0 .. ctx.states.high:
|
|
ctx.states[i].label.intVal = i
|
|
|
|
ctx.optimizeStates()
|
|
|
|
let caseDispatcher = newTreeI(nkCaseStmt, n.info,
|
|
ctx.newStateAccess())
|
|
|
|
# Lamdalifting will not touch our locals, it is our responsibility to lift those that
|
|
# need it.
|
|
detectCapturedVars(ctx)
|
|
|
|
for s in ctx.states:
|
|
let body = ctx.transformStateAssignments(s.body)
|
|
caseDispatcher.add newTreeI(nkOfBranch, body.info, s.label, body)
|
|
|
|
caseDispatcher.add newTreeI(nkElse, n.info,
|
|
newTree(nkStmtList, ctx.restoreExternExc,
|
|
newTreeI(nkReturnStmt, n.info, g.emptyNode)))
|
|
|
|
result = wrapIntoStateLoop(ctx, caseDispatcher)
|
|
result = liftLocals(ctx, result)
|
|
|
|
when false:
|
|
echo "TRANSFORM TO STATES:"
|
|
echo renderTree(result)
|
|
|
|
# echo "exception table:"
|
|
# for i, s in ctx.states:
|
|
# echo i, " -> ", s.excLandingState
|
|
|
|
# echo "ENV: ", renderTree(getEnvParam(fn).typ.elementType.n)
|