diff --git a/compiler/backend/ccgexprs.nim b/compiler/backend/ccgexprs.nim index 0410ff813bb..f16afe3d684 100644 --- a/compiler/backend/ccgexprs.nim +++ b/compiler/backend/ccgexprs.nim @@ -1753,8 +1753,14 @@ proc expr(p: BProc, n: CgNode, d: var TLoc) = of cnkLoopJoinStmt: startBlock(p, "while (1) {$n") of cnkFinally: - startBlock(p) - of cnkEnd, cnkContinueStmt, cnkLoopStmt: + startBlock(p, "$1: {$n", [$n[0].label]) + linefmt(p, cpsStmts, "*nimErr_ = NIM_FALSE;$n", []) + of cnkContinueStmt: + p.flags.incl nimErrorFlagAccessed + linefmt(p, cpsStmts, "*nimErr_ = NIM_TRUE;$n", []) + linefmt(p, cpsStmts, "$1$n", [raiseInstr(p, n[0])]) + endBlock(p) + of cnkEnd, cnkLoopStmt: endBlock(p) of cnkDef: genSingleVar(p, n[0], n[1]) of cnkCaseStmt: genCase(p, n) @@ -1770,11 +1776,12 @@ proc expr(p: BProc, n: CgNode, d: var TLoc) = of cnkExcept: genExcept(p, n) of cnkRaiseStmt: genRaiseStmt(p, n) - of cnkJoinStmt, cnkGotoStmt: - unreachable("handled separately") + of cnkJoinStmt: + linefmt(p, cpsStmts, "$1:;$n", [n[0].label]) + of cnkGotoStmt: + linefmt(p, cpsStmts, "goto $1;$n", [n[0].label]) of cnkInvalid, cnkType, cnkAstLit, cnkMagic, cnkRange, cnkBinding, cnkBranch, - cnkLabel, cnkTargetList, cnkField, cnkStmtList, - cnkLeave, cnkResume: + cnkLabel, cnkField, cnkStmtList, cnkResume: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind") proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo): Rope = diff --git a/compiler/backend/ccgflow.nim b/compiler/backend/ccgflow.nim deleted file mode 100644 index 8c54e85efe5..00000000000 --- a/compiler/backend/ccgflow.nim +++ /dev/null @@ -1,543 +0,0 @@ -## Implements the translation of CGIR to a code listing for an abstract -## machine that focuses on control-flow and exception handling. -## -## This code listing is intended for consumption by the C code generator. - -import - std/[ - options, - packedsets, - tables - ], - compiler/backend/[ - cgir - ], - compiler/utils/[ - idioms - ] - -type - COpcode* = enum - opJump ## unconditional jump - opErrJump ## jump if in error mode - opDispJump ## jump part of a dispatcher - opLabel ## jump target - - opSetTarget ## set the value of a dispatcher's discriminator - opDispatcher ## start of a dispatcher - - opBackup ## backup the error state in a local variable and clear it - opRestore ## restore the error from a local variable - - opStmts ## slice of statements - opStmt ## control-flow relevant single statement. A label - ## specifier is passed along - # future direction: ``CStmt`` should be removed and all unstructured - # control-flow bits modeled with the other instructions. Checked calls - # used as an assignment source currently block this, as they might - # require an assignment-to-temporary - - opAbort - opPopHandler - - JumpOp = range[opJump..opDispJump] - - CLabelId* = distinct uint32 - CLabelSpecifier* = uint32 - ## used for identifying the extra labels attached to finally sections - - CLabel* = tuple - ## Name of a label. - id: CLabelId - specifier: Option[CLabelSpecifier] - - CInstr* = object - case op*: COpcode - of opJump, opErrJump, opLabel, opDispJump: - label*: CLabel - of opSetTarget, opDispatcher: - discr*: uint32 ## ID of the discriminator variable - value*: int ## either the value, or number of dispatcher branches - of opStmts: - stmts*: Slice[int] - of opStmt: - stmt*: int - specifier*: CLabelSpecifier - of opBackup, opRestore, opAbort: - local*: uint32 ## ID of the backup variable - of opPopHandler: - discard - - FinallyInfo* = object - routes: seq[PathIndex] - numExits: int - ## number of exits the finally has. Pre-computed for efficiency - numErr: int - ## number of exceptional jump paths going through this finalizer - numNormal: int - ## number of non-exception jump paths going through this finalizer - - discriminator: uint32 - ## ID of the discriminator variable to use for the dispatcher - errBackupId: uint32 - ## only valid if the finally is entered by exceptional control-flow - - PathKind = enum - pkError - pkNormal - PathIndex = uint32 - ## Index of a ``PathItem`` within the item storage. - PathItemTarget = object - label: CLabelId - isCleanup: bool - PathItem = object - ## Represents a step in a jump path. A jump path is a chain of finally - ## sections plus final target an intercepted goto visits. - ## - ## An item is part of two intrusive linked-lists: one doubly-linked-list - ## representing a single chain, and one singly-linked-list for the - ## adjacent chains. The "none" value for a pointer is represented by it - ## pointing to the node itself. - prev, next: PathIndex - sibling: PathIndex - - target: PathItemTarget - ## the identifier of the jump target - kinds: set[PathKind] - ## the kinds of control-flow (exception or normal) reaching the path - ## item - - Paths = seq[PathItem] - ## The data storage for multiple jump paths, with the items layed out - ## "tail first", meaning that the final target of a jump chain comes - ## *before* the others. The idea is to uniquely identify jump paths - ## within a body while merging common trailing paths. - ## - ## Consider the two jump paths E->D->C->B->A and G->F->C->B->A. If - ## both are added to the storage, the content would look like this: - ## - ## (0: A) -> (1: B) -> (2: C) -> (3: D) -> (4: E) - ## \ (5: F) -> (6: G) - ## - ## The numbers represent the items' index in the sequence. The `sibling` - ## item of 3 is 5 (all other items have no siblings); the `next` pointer - ## of 5 points to 2. As can be seen, common trailing paths are merged into - ## one. - - Context = object - ## Local state used during the translation bundled into an object for - ## convenience. - paths: Paths - stmtToPath: Table[int, int] - finallys: Table[CLabelId, FinallyInfo] - cleanups: Table[CLabelId, FinallyInfo] - ## cleanup here refers to the exception-related cleanup when - ## exiting a finally or except section - -const - ExitLabel* = CLabelId(0) - ## The label of the procedure exit. - ResumeLabel* = ExitLabel - ## The C label that a ``cnkResume`` targets. - -func `==`*(a, b: CLabelId): bool {.borrow.} - -func toCLabel*(n: CgNode): CLabelId = - ## Returns the ID of the C label the label-like node `n` represents. - case n.kind - of cnkResume: - ResumeLabel - of cnkLabel: - CLabelId(ord(n.label) + 2) - of cnkLeave: - toCLabel(n[0]) - else: - unreachable(n.kind) - -func toCLabel*(n: CgNode, specifier: Option[CLabelSpecifier] - ): CLabel {.inline.} = - (toCLabel(n), specifier) - -func toBlockId*(id: CLabelId): BlockId = - ## If `id` was converted to from a valid CGIR label, converts it back to - ## the CGIR label. - BlockId(ord(id) - 2) - -func rawAdd(p: var Paths, x: openArray[PathItemTarget]): PathIndex = - ## Appends the chain `x` to `p` without any deduplication or - ## linking with the existing items. Returns the index of the - ## tail item. - result = p.len.PathIndex - for i in countdown(x.high, 0): - let pos = p.len.PathIndex - p.add PathItem(prev: (if i > 0: pos + 1 else: pos), - next: (if i < x.high: pos - 1 else: pos), - sibling: pos, - target: x[i]) - -func add(p: var Paths, path: openArray[PathItemTarget]): PathIndex = - ## Adds `path` to the `p`. Only the sub-path of `path` not yet present in - ## `p` is added. The index of the *head* item of the added (or existing) - ## path is returned. - if p.len == 0: - discard rawAdd(p, path) - p[0].next = 0'u32 - return p.high.PathIndex - - var pos = 0'u32 ## the current search position - for i in countdown(path.len-1, 0): - # search the sibling list for a matching item: - while p[pos].target != path[i] and pos != p[pos].sibling: - pos = p[pos].sibling - - if p[pos].target != path[i]: - # no item was found, meaning that this is the end of the common paths. - # Add the remaining items to the storage. - let next = rawAdd(p, path.toOpenArray(0, i)) - p[pos].sibling = next - # only set the next pointer if there was a common sub-path (otherwise - # there's no next item): - if i != path.high: - p[next].next = p[pos].next - return p.high.PathIndex - - # it's a match! continue down the chain - if i > 0: - if p[pos].prev == pos: - # there's no next item, append the remaining new targets to the - # pre-existing path - let next = rawAdd(p, path.toOpenArray(0, i-1)) - p[pos].prev = next - p[next].next = pos - return p.high.PathIndex - else: - pos = p[pos].prev - - # the chain `path` already exists in `p` - result = pos - -func incl(p: var Paths, at: PathIndex, kind: PathKind) = - ## Marks all items following and including `at` with `kind`. - var i = at - while p[i].next != i: - p[i].kinds.incl kind - i = p[i].next - p[i].kinds.incl kind - -func needsDispatcher(f: FinallyInfo): bool = - # a dispatcher is required if re are more than one exits. An exception is - # the case where one exit is only taken when in error mode and the other is - # not. If a dispatcher is required, the finally has sub-labels. - f.numExits > 1 and - not(f.routes.len == 2 and f.numErr == 1 and f.numNormal == 1) - -func needsSpecifier(c: Context, target: PathItemTarget): bool = - # cleanup sections don't have a unique label themselves, so using a - # specifier is required - target.isCleanup or - ((target.label in c.finallys) and - needsDispatcher(c.finallys[target.label])) - -proc append(targets: var seq[PathItemTarget], - redirects: Table[BlockId, CgNode], - exits: PackedSet[BlockId], n: CgNode) = - ## Appends all jump targets `n` represents to `targets`, following - ## `redirects` and turning all labels part of `exits` into the - ## "before return" label. - template addTarget(t: CLabelId; cleanup = false) = - targets.add PathItemTarget(label: t, isCleanup: cleanup) - - case n.kind - of cnkLabel: - if n.label in redirects: - append(targets, redirects, exits, redirects[n.label]) - elif n.label in exits: - addTarget ExitLabel - else: - addTarget toCLabel(n) - of cnkTargetList: - # only the final target could possibly be redirected - let hasRedir = n[^1].kind == cnkLabel and n[^1].label in redirects - for i in 0.. 1 or targets[0].label in c.finallys: - let id = c.paths.add(targets) - if isErr: incl(c.paths, id, pkError) - else: incl(c.paths, id, pkNormal) - - # remember the path associated with the statement for later: - c.stmtToPath[i] = id.int - - case it.kind - of cnkDef, cnkAsgn, cnkFastAsgn: - if it[1].kind == cnkCheckedCall: - exit(it[1][^1], true) - of cnkRaiseStmt, cnkCheckedCall: - exit(it[^1], true) - of cnkGotoStmt: - exit(it[0]) - of cnkCaseStmt: - for j in 1.. 1: - exit(it[^1], true) - else: - discard - - # register every path item with the finally section it targets, and compute - # some statistics that are used during the later code generation: - for i, it in c.paths.pairs: - func setup(f: var FinallyInfo, it: PathItem) = - f.routes.add i.PathIndex - f.numExits += ord(it.next.int != i) - f.numErr += ord(pkError in it.kinds) - f.numNormal += ord(pkNormal in it.kinds) - - if it.target.isCleanup: - setup(c.cleanups.mgetOrPut(it.target.label, FinallyInfo()), it) - elif it.target.label in c.finallys: - setup(c.finallys[it.target.label], it) - - # construction of the instruction list follows - - proc label(code: var seq[CInstr], id: CLabelId; - spec = none(CLabelSpecifier)) {.nimcall.} = - # a label must always be preceded by some code, so no length guard is - # required - if code[^1].op in {opJump, opErrJump} and code[^1].label.id == id and - code[^1].label.specifier == spec: - # optimization: remove the preceding jump if it targets the label - code.setLen(code.len - 1) - code.add CInstr(op: opLabel, label: (id, spec)) - - proc jump(code: var seq[CInstr], target: CLabelId) {.nimcall.} = - code.add CInstr(op: opJump, label: (target, none CLabelSpecifier)) - - proc jump(code: var seq[CInstr], op: JumpOp, c: Context, - path: PathIndex) {.nimcall.} = - let target = c.paths[path].target - if needsSpecifier(c, target): - code.add CInstr(op: op, label: (target.label, some path)) - else: - code.add CInstr(op: op, label: (target.label, none CLabelSpecifier)) - - proc stmt(code: var seq[CInstr], c: Context, pos: int) {.nimcall.} = - if (let path = c.stmtToPath.getOrDefault(pos, -1); path != -1 and - needsSpecifier(c, c.paths[path].target)): - # a label specifier, and thus a separate instruction, is needed - code.add CInstr(op: opStmt, stmt: pos, specifier: CLabelSpecifier path) - elif code.len > 0 and code[^1].op == opStmts and - code[^1].stmts.b == pos + 1: - # append to the sequence - inc code[^1].stmts.b - else: - # start a new sequence - code.add CInstr(op: opStmts, stmts: pos..pos) - - var - code: seq[CInstr] - nextDispId = 0'u32 - nextRecoverID = 0'u32 - - for i, it in stmts.pairs: - case it.kind - of cnkFinally: - stmt code, c, i - let - clabel = toCLabel(it[0]) - f = addr c.finallys[clabel] - - # allocate and set the ID for the discriminator variable: - f.discriminator = nextDispId - inc nextDispId - - # emit the entry-point(s); one for each route - if needsDispatcher(f[]): - # an entry point looks like this: - # L1_1_: - # Target = ... - # goto L1_ - for i, entry in f.routes.pairs: - label code, clabel, some(entry) - code.add CInstr(op: opSetTarget, discr: f.discriminator, value: i) - # jump to the main code: - jump code, clabel - - # the body follows: - label code, clabel - if f.numErr > 0: - # backing up the error state is only needed when the finally is - # entered by exceptional control-flow - f.errBackupId = nextRecoverID - code.add CInstr(op: opBackup, local: nextRecoverID) - inc nextRecoverID - - of cnkContinueStmt: - let - clabel = toCLabel(it[0]) - f {.cursor.} = c.finallys[clabel] - - # no need to restore the error state if control-flow never reaches the - # end of the finally anyway - if f.numErr > 0 and f.numExits > 0: - code.add CInstr(op: opRestore, local: f.errBackupId) - - if f.numExits == 0: - discard "the end is never reached; nothing to do" - elif not needsDispatcher(f) and f.routes.len == 2: - # optimization: if two paths go through a finally, with one of them - # an exceptional jump path and the other one not, instead of using a - # full dispatcher we emit: - # if err: goto error_exit - # goto normal_exit - let exit = if c.paths[f.routes[0]].kinds == {pkError}: 0 else: 1 - jump code, opErrJump, c, c.paths[f.routes[exit]].next - jump code, opJump, c, c.paths[f.routes[1 - exit]].next - else: - assert f.routes.len == f.numExits - # a dispatcher is only required if there is more than one exit - let op = if f.numExits > 1: opDispJump - else: opJump - - if op == opDispJump: - code.add CInstr(op: opDispatcher, discr: f.discriminator, - value: f.numExits) - - for it in f.routes.items: - jump code, op, c, c.paths[it].next - - # emit the exception-related cleanup after the dispatcher: - if clabel in c.cleanups: - let cleanup {.cursor.} = c.cleanups[clabel] - # a dispatcher is not worth the overhead, emit an abort instruction - # for each route - for entry in cleanup.routes.items: - label code, clabel, some(entry) - # TODO: omit the cleanup logic as a whole, if the finally section is - # never entered via an exception - if f.numErr > 0: - code.add CInstr(op: opAbort, local: f.errBackupId) - jump code, opJump, c, PathIndex c.paths[entry].next - - stmt code, c, i - - of cnkJoinStmt: - # XXX: labels that were redirected cannot be eliminated yet, as case - # statements (which are handled outside of ccgflow) might still - # target them - label code, toCLabel(it[0]) - of cnkExcept: - # an except section is a label followed by the filter logic - label code, toCLabel(it[0]) - stmt code, c, i - of cnkEnd: - let clabel = toCLabel(it[0]) - # emit the cleanup for except sections: - if clabel in c.cleanups: - let cleanup {.cursor.} = c.cleanups[clabel] - # a dispatcher is not worth the overhead, emit a pop instruction - # for each route - for entry in cleanup.routes.items: - label code, clabel, some(entry) - code.add CInstr(op: opPopHandler) - jump code, opJump, c, PathIndex c.paths[entry].next - - stmt code, c, i - - of cnkGotoStmt: - let target = it[0] - if (let path = c.stmtToPath.getOrDefault(i, -1); path != -1): - jump code, opJump, c, PathIndex path - elif target.kind == cnkLabel: - jump code, toCLabel(target) - else: - jump code, toCLabel(target[^1]) - of cnkRaiseStmt: - stmt code, c, i # the statement handles the exception setup part - # the goto part is the same as for a normal goto - let target = it[^1] - if target.kind == cnkLabel: - jump code, toCLabel(target) - elif (let path = c.stmtToPath.getOrDefault(i, -1); path != -1): - jump code, opJump, c, PathIndex path - else: - jump code, toCLabel(target[^1]) - - else: - stmt code, c, i - - result = code diff --git a/compiler/backend/ccgstmts.nim b/compiler/backend/ccgstmts.nim index 6e16b590c6a..bf85773eca5 100644 --- a/compiler/backend/ccgstmts.nim +++ b/compiler/backend/ccgstmts.nim @@ -103,25 +103,18 @@ proc exit(n: CgNode): CgNode = of cnkCheckedCall: n[^1] else: nil -proc useLabel(p: BProc, label: CLabel) {.inline.} = - if label.id == ExitLabel: - p.flags.incl beforeRetNeeded - proc raiseInstr(p: BProc, n: CgNode): Rope = if n != nil: case n.kind of cnkLabel: - # easy case, simply goto the target: result = ropecg(p.module, "goto $1;", [n.label]) - of cnkTargetList: - # the first non-leave operand is the initial jump target - let label = toCLabel(n[0], p.specifier) - useLabel(p, label) - result = ropecg(p.module, "goto $1;", [label]) + of cnkResume: + p.flags.incl beforeRetNeeded + result = "goto BeforeRet_;" else: unreachable(n.kind) else: - # absence of an node storing the target means "never exits" + # absence of a node storing the target means "never exits" if hasAssume in CC[p.config.cCompiler].props: result = "__asume(0);" else: @@ -138,27 +131,14 @@ proc raiseExit(p: BProc, n: CgNode) = [raiseInstr(p, n)]) proc genRaiseStmt(p: BProc, t: CgNode) = - if t[0].kind != cnkEmpty: - var a: TLoc - initLocExprSingleUse(p, t[0], a) - var e = rdLoc(a) - discard getTypeDesc(p.module, t[0].typ) - var typ = skipTypes(t[0].typ, abstractPtrs) - genLineDir(p, t) - if isImportedException(typ, p.config): - lineF(p, cpsStmts, "throw $1;$n", [e]) - else: - lineCg(p, cpsStmts, "#raiseException2((#Exception*)$1, $2, $3, $4);$n", - [e, - makeCString(if p.prc != nil: p.prc.name.s else: p.module.module.name.s), - quotedFilename(p.config, t.info), toLinenumber(t.info)]) - + if nimErrorFlagDisabled in p.flags: + # the error flag needs to be set to true, but the ``nimErr_`` local was + # disabled. Query the pointer manually + linefmt(p, cpsStmts, "*#nimErrorFlag() = NIM_TRUE;$n", []) else: - genLineDir(p, t) - # reraise the last exception: - linefmt(p, cpsStmts, "#reraiseException();$n", []) - - # the goto is emitted separately + p.flags.incl nimErrorFlagAccessed + linefmt(p, cpsStmts, "*nimErr_ = NIM_TRUE;$n", []) + linefmt(p, cpsStmts, "$1$n", [raiseInstr(p, t[0])]) template genCaseGenericBranch(p: BProc, b: CgNode, e: TLoc, rangeFormat, eqFormat: FormatStr, labl: BlockId) = @@ -276,6 +256,7 @@ proc bodyCanRaise(p: BProc; n: CgNode): bool = proc genExcept(p: BProc, n: CgNode) = ## Generates and emits the C code for an ``Except`` join point. + linefmt(p, cpsStmts, "$1:$n", [n[0].label]) if n.len > 1: # it's a handler with a filter/matcher @@ -302,10 +283,6 @@ proc genExcept(p: BProc, n: CgNode) = p.flags.incl nimErrorFlagAccessed # exit error mode: lineCg(p, cpsStmts, "*nimErr_ = NIM_FALSE;$n", []) - # setup the handler frame: - var tmp: TLoc - getTemp(p, p.module.g.graph.getCompilerProc("ExceptionFrame").typ, tmp) - lineCg(p, cpsStmts, "#nimCatchException($1);$n", [addrLoc(p.module, tmp)]) proc genAsmOrEmitStmt(p: BProc, t: CgNode, isAsmStmt=false): Rope = var res = "" @@ -421,78 +398,10 @@ proc genStmt(p: BProc, t: CgNode) = if isPush: popInfoContext(p.config) internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr} -proc gen(p: BProc, code: openArray[CInstr], stmts: CgNode) = - ## Generates and emits the C code for `code` and `stmts`. This is the main - ## driver of C code generation. - var pos = 0 - while pos < code.len: - let it = code[pos] - case it.op - of opLabel: - lineCg(p, cpsStmts, "$1:;$n", [it.label]) - of opJump: - useLabel(p, it.label) - lineCg(p, cpsStmts, "goto $1;$n", [it.label]) - of opDispJump: - # must only be part of a dispatcher - unreachable() - of opSetTarget: - lineCg(p, cpsStmts, "Target$1_ = $2;$n", [$it.discr, it.value]) - of opDispatcher: - lineF(p, cpsLocals, "NU8 Target$1_;$N", [$it.discr]) - lineF(p, cpsStmts, "switch (Target$1_) {$n", [$it.discr]) - for i in 0.. [L0, L1] - # goto [L0, L2] - # L0: # finalizer - # ... - # Continue + # def y = f() -> [L1] + # goto [L2] # L1: # exception handler # ... # L2: ... @@ -255,7 +208,7 @@ proc toStructureList*(stmts: openArray[CgNode]): StructDesc = # handler. A labeled JS block must enclose the `goto`. for i, it in stmts.pairs: template exit(n: CgNode, isError: bool) = - spawnOpens(structs, i, n, isError, finallys, marker) + spawnOpens(structs, i, n, isError, marker) template terminator() = structs.add Structure(kind: stkTerminator, stmt: i) @@ -271,14 +224,7 @@ proc toStructureList*(stmts: openArray[CgNode]): StructDesc = exit(it[^1], isError=true) of cnkGotoStmt: exit(it[0], isError=false) - let target = finalTarget(it[0]) - # if the goto jumps to a finally, there's no label for the break. - # Since this can only happen when structured control-flow never - # leaves the finally, a JavaScript 'return' can be used - if target.label in finallys: - structs.add Structure(kind: stkReturn, stmt: i) - else: - terminator() + terminator() of cnkRaiseStmt: exit(it[^1], isError=true) terminator() @@ -291,8 +237,24 @@ proc toStructureList*(stmts: openArray[CgNode]): StructDesc = struct(stkStructStart, it[0].label) of cnkIfStmt: struct(stkStructStart, it[1].label) - of cnkEnd, cnkContinueStmt, cnkLoopStmt: + of cnkEnd, cnkLoopStmt: struct(stkEnd, it[0].label) + of cnkContinueStmt: + var + depth = 1 + j = structs.high + # look for the first unmatched opening 'finally': + while j >= 0 and (depth != 1 or structs[j].kind != stkFinally): + depth += ord(structs[j].kind == stkEnd) + depth -= ord(structs[j].kind in + {stkCatch, stkFinally, stkTry, stkStructStart, stkBlock}) + dec j + + assert structs[j].kind == stkFinally + exit(it[0], isError=true) + terminator() + # attach the end to the next statement: + structs.add Structure(kind: stkEnd, stmt: i + 1, label: structs[j].label) of cnkJoinStmt: assert it[0].label in marker struct(stkEnd, it[0].label) @@ -352,7 +314,7 @@ proc toStructureList*(stmts: openArray[CgNode]): StructDesc = of cnkGotoStmt: # we don't inline the target at bare gotos. Mark the block the goto # targets as ineligible by incrementing the counter by two - inline.mgetOrPut(finalTarget(n[0]).label, 0) += 2 + inline.mgetOrPut(n[0].label, 0) += 2 else: discard "only gotos are interesting" of stkEnd: @@ -384,4 +346,4 @@ proc toStructureList*(stmts: openArray[CgNode]): StructDesc = # structured control-flow would take same route # * a chain of exception could be merged into a single JavaScript catch - result = (structs, finallys, inline) + result = (structs, inline) diff --git a/compiler/backend/jsgen.nim b/compiler/backend/jsgen.nim index a30ba00aef8..971c35cb51e 100644 --- a/compiler/backend/jsgen.nim +++ b/compiler/backend/jsgen.nim @@ -145,13 +145,10 @@ type BlockKind = enum bkBlock - bkTryFinally ## try with attached 'finally' - bkTryCatch ## try with attached 'catch' - bkFinally + bkTry ## try with attached 'catch' bkCatch BlockFlag = enum - needsRecover needsEnableFlag BlockInfo = object @@ -168,9 +165,6 @@ type options: TOptions module: BModule g: PGlobals - lastErrorBackupNeeded: bool - ## signals whether the value of ``lastJSError`` needs to be captured - ## on procedure entry unique: int # for temp identifier generation blocks: seq[BlockInfo] ## enclosing exception handlers, finallys, and labeled blocks. Used @@ -244,12 +238,6 @@ proc indentLine(p: PProc, r: Rope): Rope = result.add " " result.add r -template line(p: PProc, added: string) = - p.body.add(indentLine(p, rope(added))) - -template line(p: PProc, added: Rope) = - p.body.add(indentLine(p, added)) - template lineF(p: PProc, frmt: FormatStr, args: varargs[Rope]) = p.body.add(indentLine(p, ropes.`%`(frmt, args))) @@ -697,11 +685,11 @@ proc genLineDir(p: PProc, n: CgNode) = if hasFrameInfo(p): lineF(p, "F.line = $1;$n", [rope(line)]) -proc handleJump(p: PProc, n: CgNode, fromError: bool): seq[BlockId] = - ## Makes sure the control-flow described by jump action description `n` - ## matches the actual JavaScript control-flow. If catch or finally - ## sections would be entered that shouldn't be, they're flagged as - ## requiring an "is enabled" guard and a boolean local is spawned. +proc handleErrorJump(p: PProc, n: CgNode): seq[BlockId] = + ## Makes sure the jump described by target node `n` matches the actual + ## JavaScript control-flow. If catch or finally sections would be entered + ## that shouldn't be, they're marked as requiring an "is enabled" guard + ## and a boolean local is spawned. ## ## Returns the list of sections that need to be disabled. @@ -710,8 +698,8 @@ proc handleJump(p: PProc, n: CgNode, fromError: bool): seq[BlockId] = yield (i, s[i]) template onMiss(idx: int, b: var BlockInfo) = - if b.kind == bkTryFinally or (b.kind == bkTryCatch and fromError): - # JavaScript control-flow enters a 'finally' or 'catch' it shouldn't. + if b.kind == bkTry: + # JavaScript control-flow enters a 'catch' it shouldn't. # The section needs to be disabled; a boolean flag is used for this. # Thanks to `var`, the local can be spawned without regards to # scoping. @@ -731,37 +719,11 @@ proc handleJump(p: PProc, n: CgNode, fromError: bool): seq[BlockId] = break # found the target else: onMiss(i, b) - - of cnkTargetList: - # marking blocks as having to restore the current exceptions is also done - # here - var - t = 0 - wasLeave = fromError - # ^^ exceptional control-flow could come from within a procedure call, where - # an exception handler boundary crossed - for i, b in mreverse(p.blocks): - wasLeave = wasLeave or n[t].kind == cnkLeave - # skip leave actions: - while n[t].kind == cnkLeave: - inc t - - if n[t].kind == cnkLabel and n[t].label == b.label: - inc t - if wasLeave: - b.flags.incl needsRecover - # the next jump target doesn't need to recover the exception, - # unless there's another leave action in-between - wasLeave = false - # stop searching when we reach the end - if t >= n.len: - break - else: - onMiss(i, b) - + of cnkResume: + discard "nothing to do" of cnkCheckedCall: # to reduce conditionals at the callsite - result = handleJump(p, n[^1], fromError) + result = handleErrorJump(p, n[^1]) else: unreachable() @@ -798,7 +760,7 @@ proc genExcept(p: PProc, n: CgNode) = [$id, orExpr]) p.nested: # disable the necessary sections before throwing - setEnabled(p, handleJump(p, n[^1], fromError=true), "false") + setEnabled(p, handleErrorJump(p, n[^1]), "false") lineF(p, "throw Exception$1_;\L", [$id]) lineF(p, "}\L") @@ -821,18 +783,8 @@ proc genExcept(p: PProc, n: CgNode) = proc genRaiseStmt(p: PProc, n: CgNode) = # disable the necessary sections before throwing: - setEnabled(p, handleJump(p, n[^1], fromError=true), "false") - if n[0].kind != cnkEmpty: - var a: TCompRes - gen(p, n[0], a) - genLineDir(p, n) - useMagic(p, "raiseException") - lineF(p, "raiseException($1);$n", - [a.rdLoc]) - else: - genLineDir(p, n) - useMagic(p, "reraiseException") - line(p, "reraiseException();\L") + setEnabled(p, handleErrorJump(p, n[^1]), "false") + lineF(p, "throw lastJSError;$n", []) func intLiteral(v: Int128, typ: PType): string = if typ.kind == tyBool: @@ -894,7 +846,6 @@ proc genCaseJS(p: PProc, desc: StructDesc, stmts: openArray[CgNode], n: CgNode) gen(p, desc, stmts, desc.inline[target]) else: # cannnot inline; a jump is needed - setEnabled(p, handleJump(p, it[^1], fromError=false), "false") lineF(p, "break Label$1;$n", [$target]) lineF(p, "}$n", []) @@ -2096,9 +2047,6 @@ proc genProcBody(p: PProc, prc: PSym): Rope = else: result = "" - if p.lastErrorBackupNeeded: - result.add(p.indentLine("var Exception0_ = lastJSError;$n" % [])) - result.add(p.body) if prc.typ.callConv == ccSysCall: result = ("try {$n$1} catch (e) {$n" & @@ -2217,15 +2165,6 @@ proc finishProc*(p: PProc): string = #if gVerbosity >= 3: # echo "END generated code for: " & prc.name.s -proc handleRecover(p: PProc, b: BlockInfo) = - if needsRecover in b.flags: - let nesting = p.numHandlers - lineF(p, "lastJSError = Exception$1_;$n", [$nesting]) - if nesting == 0: - # there's no enclosing 'catch'; the value of ``lastJSError`` needs to - # be captured on procedure entry - p.lastErrorBackupNeeded = true - proc handleSectionStart(p: PProc) = # wrap the section in an 'if' if it can be disabled at run-time (only the # opening is handled here) @@ -2240,13 +2179,6 @@ proc popBlock(p: PProc) = case blk.kind of bkBlock: endBlock(p) - # restore - handleRecover(p, blk) - of bkFinally: - if needsEnableFlag in blk.flags: - # close the wrapper 'if' and re-enable the section - endBlock(p, "} else { Enabled$1_ = true; }$n", $blk.label) - endBlock(p) of bkCatch: # the counterpart to the opening logic if needsEnableFlag in blk.flags: @@ -2256,7 +2188,7 @@ proc popBlock(p: PProc) = endBlock(p) # release the name: dec p.numHandlers - of bkTryCatch, bkTryFinally: + of bkTry: discard "nothing to do when exiting these" proc gen(p: PProc, desc: StructDesc, stmts: openArray[CgNode], start: int) = @@ -2293,9 +2225,7 @@ proc gen(p: PProc, desc: StructDesc, stmts: openArray[CgNode], start: int) = case it.kind of stkTry: p.blocks.add BlockInfo(label: it.label) - p.blocks[^1].kind = - if it.label in desc.finallys: bkTryFinally - else: bkTryCatch + p.blocks[^1].kind = bkTry startBlock(p, "try {$n", []) gen(it.stmt, structs[i+1].stmt) inc depth @@ -2310,20 +2240,14 @@ proc gen(p: PProc, desc: StructDesc, stmts: openArray[CgNode], start: int) = # indentation is managed by ``genStmt`` here gen(it.stmt, structs[i+1].stmt) inc depth - of stkCatch: + of stkCatch, stkFinally: + # MIR finally sections are too translated to JS 'catch' clauses endBlock(p) p.blocks[^1].kind = bkCatch # replace the try block inc p.numHandlers startBlock(p, "catch(Exception$1_) {$n", [$p.numHandlers]) handleSectionStart(p) gen(it.stmt, structs[i+1].stmt) - of stkFinally: - endBlock(p) - startBlock(p, "finally {$n", []) - p.blocks[^1].kind = bkFinally # replace the try block - handleSectionStart(p) - handleRecover(p, p.blocks[^1]) - gen(it.stmt, structs[i+1].stmt) of stkTerminator: let n = stmts[it.stmt] if n.kind == cnkCaseStmt: @@ -2416,7 +2340,7 @@ proc genStmt(p: PProc, n: CgNode) = if n.kind == cnkCheckedCall or (n.kind in {cnkAsgn, cnkFastAsgn, cnkDef} and n[1].kind == cnkCheckedCall): # XXX: somewhat hacky way to handle checked calls - let sections = handleJump(p, n[^1], fromError=true) + let sections = handleErrorJump(p, n[^1]) setEnabled(p, sections, "false") gen(p, n, r) if r.res != "": lineF(p, "$#;$n", [r.res]) @@ -2617,10 +2541,7 @@ proc gen(p: PProc, n: CgNode, r: var TCompRes) = of cnkType: r.res = genTypeInfo(p, n.typ) of cnkDef: genDef(p, n) of cnkGotoStmt: - setEnabled(p, handleJump(p, n[0], fromError=false), "false") - # jump directly to the final target. Placement of 'try' blocks made - # sure that finally sections are visited correctly - lineF(p, "break Label$1;$n", [$finalTarget(n[0]).label]) + lineF(p, "break Label$1;$n", [$n[0].label]) of cnkLoopJoinStmt: startBlock(p, "while (true) {$n") of cnkExcept: @@ -2650,9 +2571,12 @@ proc gen(p: PProc, n: CgNode, r: var TCompRes) = lineF(p, "($1);$n", [a.res]) of cnkAsmStmt, cnkEmitStmt: genAsmOrEmitStmt(p, n) of cnkRaiseStmt: genRaiseStmt(p, n) - of cnkJoinStmt, cnkEnd, cnkLoopStmt, cnkContinueStmt: + of cnkContinueStmt: + # end of finally section. Re-raise the caught exception + lineF(p, "throw Exception$1_;$n", [$p.numHandlers]) + of cnkJoinStmt, cnkEnd, cnkLoopStmt: discard "terminators or endings for which no special handling is needed" - of cnkInvalid, cnkMagic, cnkRange, cnkBinding, cnkLeave, cnkTargetList, + of cnkInvalid, cnkMagic, cnkRange, cnkBinding, cnkResume, cnkBranch, cnkAstLit, cnkLabel, cnkStmtList, cnkCaseStmt, cnkField: internalError(p.config, n.info, "gen: unknown node type: " & $n.kind) diff --git a/compiler/front/msgs.nim b/compiler/front/msgs.nim index 79b785b28da..721604386be 100644 --- a/compiler/front/msgs.nim +++ b/compiler/front/msgs.nim @@ -1016,6 +1016,16 @@ proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope = else: result = conf[i.fileIndex].quotedName +proc unquotedFilename*(conf: ConfigRef, i: TLineInfo): Rope = + ## Returns the unqouted filename for `i`, for the purpose of being used for + ## run-time stacktraces. + if i.fileIndex.int32 < 0: + result = "???" + elif optExcessiveStackTrace in conf.globalOptions: + result = conf[i.fileIndex].fullPath.string + else: + result = conf[i.fileIndex].shortName + proc listWarnings*(conf: ConfigRef) = conf.localReport(InternalReport( kind: rintListWarnings, diff --git a/compiler/mir/mirgen.nim b/compiler/mir/mirgen.nim index a071314d883..0367f555648 100644 --- a/compiler/mir/mirgen.nim +++ b/compiler/mir/mirgen.nim @@ -101,6 +101,8 @@ import std/options as std_options when defined(nimCompilerStacktraceHints): import compiler/utils/debugutils +from compiler/front/msgs import unquotedFilename, toLinenumber + type DestFlag = enum ## Extra information about an assignment destination. The flags are used to @@ -164,6 +166,7 @@ type # input: userOptions: set[TOption] graph: ModuleGraph + owner: PSym config: TranslationConfig @@ -1265,37 +1268,51 @@ proc genRaise(c: var TCtx, n: PNode) = # skip the 'materialize' node genx(c, e, e.high - 1, fromMove=true) - # emit the preparation code: + # raising an exception consists of two parts: + # 1. filling in various state for it (stacktrace, name, etc.) and pushing + # it to the exception stack + # 2. unwinding to the next handler + # The first part is done with a call to the relevant exception runtime + # procedure. The second part is done by ``mnkRaise``. let typ = skipTypes(n[0].typ, abstractPtrs) - cp = c.graph.getCompilerProc("prepareException") + cp = c.graph.getCompilerProc("raiseExceptionEx") c.buildStmt mnkVoid: c.builder.buildCall c.env.procedures.add(cp), VoidType: - c.subTree mnkArg: + c.subTree mnkConsume: # lvalue conversion to the base ``Exception`` type: c.buildTree mnkPathConv, c.typeToMir(cp.typ[1]): c.use tmp + # exception name: c.emitByVal strLiteral(c.env, typ.sym.name.s, CstringType) - - # emit the raise statement: - c.buildStmt mnkRaise: - c.use tmp - raiseExit(c) + # procedure name: + if c.owner.isNil: + c.emitByVal strLiteral(c.env, "???", CstringType) + else: + c.emitByVal strLiteral(c.env, c.owner.name.s, CstringType) + # file name: + c.emitByVal strLiteral(c.env, unquotedFilename(c.graph.config, n.info), + CstringType) + # file number: + c.emitByVal intLiteral(c.env, toLinenumber(n.info), + c.env.types.sizeType) else: # a re-raise statement - c.buildStmt mnkRaise: - c.add MirNode(kind: mnkNone) - raiseExit(c) + let cp = c.graph.getCompilerProc("reraiseException") + c.buildStmt mnkVoid: + c.builder.buildCall c.env.procedures.add(cp), VoidType: + discard + c.buildStmt mnkRaise: + raiseExit(c) proc genReturn(c: var TCtx, n: PNode) = assert n.kind == nkReturnStmt if n[0].kind != nkEmpty: gen(c, n[0]) - c.buildStmt mnkGoto: - blockExit(c.blocks, c.builder, 0) + blockExit(c.blocks, c.graph, c.env, c.builder, 0) proc genAsgnSource(c: var TCtx, e: PNode, status: set[DestFlag]) = ## Generates the MIR code for the right-hand side of an assignment. @@ -1560,11 +1577,6 @@ template withBlock(c: var TCtx, k: BlockKind, body: untyped) = body c.closeBlock() -template withBlock(c: var TCtx, k: BlockKind, lbl: LabelId, body: untyped) = - c.blocks.add Block(kind: k, id: some lbl) - body - c.closeBlock() - proc genBlock(c: var TCtx, n: PNode, dest: Destination) = ## Generates and emits the MIR code for a ``block`` expression or statement. ## A block translates to a scope and, optionally, a join. @@ -1588,12 +1600,7 @@ proc genBranch(c: var TCtx, n: PNode, dest: Destination) = proc leaveBlock(c: var TCtx) = ## Emits a goto for jumping to the exit of first enclosing block. - if c.scopeDepth > 0: - # only emit the early scope exit if still within a scope - earlyExit(c.blocks, c.builder) - - c.subTree mnkGoto: - blockExit(c.blocks, c.builder, closest(c.blocks)) + blockExit(c.blocks, c.graph, c.env, c.builder, closest(c.blocks)) proc genScopedBranch(c: var TCtx, n: PNode, dest: Destination, withLeave: bool) = @@ -1738,9 +1745,39 @@ proc genExceptBranch(c: var TCtx, n: PNode, label: LabelId, # continue raising raiseExit(c) + # emit the setup for the handler frame: + let + p = c.graph.getCompilerProc("nimCatchException") + tmp = c.allocTemp(c.typeToMir(p.typ[1][0])) + c.subTree mnkDef: + c.use tmp + c.add MirNode(kind: mnkNone) + + let + typ = c.typeToMir(p.typ[1]) + arg = c.wrapTemp typ: + c.buildTree mnkAddr, typ: + c.use tmp + + c.subTree mnkVoid: + c.builder.buildCall c.env.procedures.add(p), VoidType: + c.emitByVal arg + # generate the body of the except branch: - c.withBlock bkExcept, label: - c.genScopedBranch(n.lastSon, dest, withLeave=true) + c.blocks.add Block(kind: bkExcept) + c.genScopedBranch(n.lastSon, dest, withLeave=true) + let exc = c.blocks.pop() + if exc.id.isSome: + # the handler may be exited via an exception at run-time -> a finally is + # needed + c.subTree mnkFinally: + c.add labelNode(exc.id.unsafeGet) + c.subTree mnkVoid: + let p = c.graph.getCompilerProc("nimLeaveExcept") + c.builder.buildCall c.env.procedures.add(p), VoidType: + discard + c.subTree mnkContinue: + raiseExit(c) c.subTree mnkEndStruct: c.add labelNode(label) @@ -1765,26 +1802,105 @@ proc genExcept(c: var TCtx, n: PNode, len: int, dest: Destination) = c.genExceptBranch(n[i], curr, none LabelId, dest) proc genFinally(c: var TCtx, n: PNode) = - let blk = c.blocks.pop() - if blk.id.isNone: + let + exc = c.blocks.pop() + blk = c.blocks.pop() + + if blk.id.isNone and exc.id.isNone: # the finally is never entered, omit it return c.builder.useSource(c.sp, n) - c.subTree mnkFinally: - c.add labelNode(blk.id.unsafeGet) + + if exc.id.isSome: + # the handler catches the exception and sets the selector for the + # finally's outgoing target accordingly + c.subTree mnkExcept: + c.add labelNode(exc.id.unsafeGet) + if blk.selector.isSome: + c.subTree mnkInit: + c.use blk.selector.unsafeGet + c.use intLiteral(c.env, blk.exits.len, UInt32Type) + c.subTree mnkEndStruct: + c.add labelNode(exc.id.unsafeGet) + + if blk.id.isSome: + # entry point for break/return/normal try exits + c.join blk.id.unsafeGet + + if exc.id.isSome: + # the exception through which the finally was entered might need to be + # aborted + c.blocks.add Block(kind: bkFinally, + selectorVar: blk.selector.unsafeGet, + excState: blk.exits.len) # translate the body: - c.withBlock bkFinally, blk.id.unsafeGet: - c.scope(not blk.doesntExit): - c.gen(n[^1]) + c.scope(doesReturn(n[^1])): + c.gen(n[^1]) + + if exc.id.isSome: + let err = c.blocks.pop() + # emit the abort handler if needed. When an exception is raised from the + # finally clause, the previously in-flight exception needs to be aborted + if err.id.isSome: + var next: LabelId + if doesReturn(n[^1]): + next = c.allocLabel() + c.builder.goto next # jump over the finally - # the continue statement is always necessary, even if the body has no - # structured exit - c.subTree mnkContinue: - c.add labelNode(blk.id.unsafeGet) - for it in blk.exits.items: - c.add labelNode(it) + c.subTree mnkFinally: + c.add labelNode(err.id.unsafeGet) + let val = c.wrapTemp BoolType: + c.buildMagicCall mEqI, BoolType: + c.emitByVal blk.selector.unsafeGet + c.emitByVal intLiteral(c.env, blk.exits.len, UInt32Type) + c.builder.buildIf (;c.use val): + c.subTree mnkVoid: + let p = c.graph.getCompilerProc("nimAbortException") + c.builder.buildCall c.env.procedures.add(p), VoidType: + c.emitByVal intLiteral(c.env, 1, BoolType) + c.subTree mnkContinue: + raiseExit(c) + + if doesReturn(n[^1]): + c.join next + + if doesReturn(n[^1]): + # emit the dispatcher. The finally body not being noreturn implies the + # existence of a selector + var labels = newSeq[LabelId](blk.exits.len + 1) + c.subTree mnkCase: + c.use blk.selector.unsafeGet + for i, it in blk.exits.pairs: + c.subTree mnkBranch: + c.use intLiteral(c.env, i, UInt32Type) + labels[i] = c.builder.allocLabel() + c.add labelNode(labels[i]) + + if exc.id.isSome: + labels[^1] = c.builder.allocLabel() + c.subTree mnkBranch: + c.use intLiteral(c.env, labels.high, UInt32Type) + c.add labelNode(labels[^1]) + + # emit the branch bodies. The dispatcher cannot jump directly to target + # blocks, since there may be leave actions that need to take place + for i, it in blk.exits.pairs: + c.join labels[i] + blockExit(c.blocks, c.graph, c.env, c.builder, it) + + if exc.id.isSome: + # resume raising the in-flight exception. Using a re-raise would be + # wrong, because the exception wasn't (technically) caught yet + c.join labels[^1] + c.subTree mnkRaise: + raiseExit(c) + + elif exc.id.isSome: + # always resume raising the exception + c.subTree mnkRaise: + raiseExit(c) proc genTry(c: var TCtx, n: PNode, dest: Destination) = let @@ -1795,10 +1911,17 @@ proc genTry(c: var TCtx, n: PNode, dest: Destination) = c.blocks.add Block(kind: bkBlock) if hasFinally: - # the finally clause also applies to the except clauses, so it's - # pushed first - c.blocks.add Block(kind: bkTryFinally, - doesntExit: not doesReturn(n[^1][0])) + # a selector is needed for both the exception aborting and dispatcher. We + # know whether a dispatcher is needed already, but not whether there can + # be an exception. Therefore a selector is always generated + let selector = c.allocTemp(UInt32Type) + c.buildStmt mnkDef: + c.use selector + c.add MirNode(kind: mnkNone) + c.blocks.add Block(kind: bkTryFinally, selector: some(selector)) + + # for translation of 'finally's, an additional exception handler is needed + c.blocks.add Block(kind: bkTryExcept) if hasExcept: c.blocks.add Block(kind: bkTryExcept) @@ -2140,8 +2263,8 @@ proc gen(c: var TCtx, n: PNode) = of nkPragmaBlock: gen(c, n.lastSon) of nkBreakStmt: - c.buildStmt mnkGoto: - blockExit(c.blocks, c.builder, findBlock(c.blocks, n[0].sym)) + blockExit(c.blocks, c.graph, c.env, c.builder, + findBlock(c.blocks, n[0].sym)) of nkVarSection, nkLetSection: genVarSection(c, n) of nkAsgn: @@ -2247,7 +2370,7 @@ proc genWithDest(c: var TCtx, n: PNode; dest: Destination) = proc initCtx(graph: ModuleGraph, config: TranslationConfig, owner: PSym, env: sink MirEnv): TCtx = - result = TCtx(graph: graph, config: config, env: move env) + result = TCtx(graph: graph, config: config, owner: owner, env: move env) if owner != nil: result.userOptions = owner.options result.injectDestructors = @@ -2334,7 +2457,7 @@ proc generateCode*(graph: ModuleGraph, env: var MirEnv, owner: PSym, if needsCleanup: # the result variable only needs to be cleaned up when the procedure # exits via an exception - c.blocks.add Block(kind: bkTryFinally, errorOnly: true) + c.blocks.add Block(kind: bkTryExcept) c.scope(doesReturn): if owner.kind in routineKinds: @@ -2380,7 +2503,7 @@ proc generateCode*(graph: ModuleGraph, env: var MirEnv, owner: PSym, # guaranteed that no one can observe the result location when the # procedure raises c.subTree mnkContinue: - c.add labelNode(b.id.unsafeGet) + raiseExit(c) if needsTerminate and (let b = c.blocks.pop(); b.id.isSome): if doesReturn and isFirst: diff --git a/compiler/mir/mirgen_blocks.nim b/compiler/mir/mirgen_blocks.nim index 8772505049b..58f0b459f1c 100644 --- a/compiler/mir/mirgen_blocks.nim +++ b/compiler/mir/mirgen_blocks.nim @@ -12,10 +12,13 @@ import ], compiler/mir/[ mirconstr, - mirtrees + mirenv, + mirtrees, + mirtypes ], - compiler/utils/[ - idioms + compiler/modules/[ + magicsys, + modulegraphs ] type @@ -40,16 +43,18 @@ type of bkScope: numRegistered: int ## number of entities registered for the scope in the to-destroy list - scopeExits: seq[LabelId] - ## unordered set of follow-up targets of bkTryFinally: - doesntExit*: bool - ## whether structured control-flow doesn't reach the end of the finally - errorOnly*: bool - ## whether only exceptional control-flow is intercepted - exits*: seq[LabelId] - ## unordered set of follow-up targets - of bkTryExcept, bkFinally, bkExcept: + selector*: Option[Value] + ## the variable to store the destination index in + exits*: seq[int] + ## a set of all original target block indices + of bkFinally: + selectorVar*: Value + ## the selector storing the dispatcher's target index + excState*: int + ## the selector value representing the "finally entered via exception" + ## state + of bkTryExcept, bkExcept: discard BlockCtx* = object @@ -81,81 +86,71 @@ proc emitDestroy(bu; val: Value) = bu.subTree mnkDestroy: bu.use val -proc emitFinalizerLabels(c; bu; locs: Slice[int]) = - ## Emits the labels for all scope finalizers required for cleaning up the - ## registered entities in `locs`. - # destruction happens in reverse, so iterate from high to low - for i in countdown(locs.b, locs.a): - if c.toDestroy[i].label.isSome: - bu.add labelNode(c.toDestroy[i].label.unsafeGet) - -proc blockLeaveActions(c; bu; targetBlock: int): bool = - ## Emits the actions for leaving the blocks up until (but not including) - ## `targetBlock`. Returns false when there's an intercepting - ## ``finally`` clause that doesn't exit (meaning that `targetBlock` won't - ## be reached), true otherwise. - proc incl[T](s: var seq[T], it: T) {.inline.} = - if it notin s: - s.add it - - proc inclExit(b: var Block, it: LabelId) {.inline.} = - case b.kind - of bkTryFinally: b.exits.incl it - of bkScope: b.scopeExits.incl it - else: unreachable() +proc isInFinally*(c: BlockCtx): bool = + c.blocks.len > 0 and c.blocks[^1].kind == bkFinally + +proc blockExit*(c; graph: ModuleGraph; env: var MirEnv; bu; targetBlock: int) = + ## Emits a goto jumping to the `targetBlock`, together with the necessary scope + ## and exception cleanup logic. If the jump crosses a try/finally, the + ## finally is jumped to instead. + proc incl[T](s: var seq[T], val: T): int = + for i, it in s.pairs: + if it == val: + return i + + result = s.len + s.add val - var - last = c.toDestroy.high - previous = -1 + var last = c.toDestroy.high for i in countdown(c.blocks.high, targetBlock + 1): let b {.cursor.} = c.blocks[i] case b.kind - of bkBlock, bkTryExcept: - discard "nothing to do" - of bkExcept, bkFinally: - # needs a leave action - bu.add MirNode(kind: mnkLeave, label: b.id.get) of bkScope: - if b.numRegistered > 0: - # there are some locations that require cleanup - if c.toDestroy[last].label.isNone: - c.toDestroy[last].label = some bu.allocLabel() + let start = last - b.numRegistered + var j = last + while j > start: + bu.emitDestroy(c.toDestroy[j].entity) + dec j - if previous != -1: - c.blocks[previous].inclExit c.toDestroy[last].label.unsafeGet - - previous = i - # emit the labels for all scope finalizers that need to be run - emitFinalizerLabels(c, bu, (last-b.numRegistered+1)..last) - - last -= b.numRegistered + last = start of bkTryFinally: - if c.blocks[i].errorOnly and targetBlock >= 0 and - c.blocks[targetBlock].kind != bkTryExcept: - # ignore the finally; it only applies to exceptional control-flow - continue - - let label = bu.requestLabel(c.blocks[i]) - # register as outgoing edge of the preceding finally (if any): - if previous != -1: - c.blocks[previous].inclExit label - - previous = i - - # enter the finally clause: - bu.add labelNode(label) - if b.doesntExit: - # structured control-flow doesn't leave the finally; the finally is - # the final jump target - return false - - if targetBlock >= 0 and previous != -1 and - c.blocks[targetBlock].kind in {bkBlock, bkTryExcept}: - # register the target as the follow-up for the previous finally - c.blocks[previous].inclExit bu.requestLabel(c.blocks[targetBlock]) + if b.selector.isSome: + # add the target as an exit of the try: + let pos = c.blocks[i].exits.incl(targetBlock) + bu.subTree mnkAsgn: + bu.use b.selector.unsafeGet + bu.use literal(mnkIntLit, env.getOrIncl(pos.BiggestInt), UInt32Type) + + # enter to the intercepting finally + bu.subTree mnkGoto: + bu.add labelNode(bu.requestLabel(c.blocks[i])) + return + of bkFinally: + # emit a conditional abort: + let tmp = bu.wrapTemp BoolType: + bu.buildMagicCall mEqI, BoolType: + bu.emitByVal b.selectorVar + bu.emitByVal: + literal(mnkIntLit, env.getOrIncl(b.excState.BiggestInt), + UInt32Type) + + bu.buildIf (;bu.use tmp): + bu.subTree mnkVoid: + let p = graph.getCompilerProc("nimAbortException") + bu.buildCall env.procedures.add(p), VoidType: + bu.emitByVal literal(mnkIntLit, env.getOrIncl(0), BoolType) + of bkExcept: + bu.subTree mnkVoid: + let p = graph.getCompilerProc("nimLeaveExcept") + bu.buildCall env.procedures.add(p), VoidType: + discard + of bkBlock, bkTryExcept: + discard "nothing to do" - result = true + # no intercepting finally exists + bu.subTree mnkGoto: + bu.add labelNode(bu.requestLabel(c.blocks[targetBlock])) template add*(c: var BlockCtx; b: Block) = c.blocks.add b @@ -178,28 +173,32 @@ proc findBlock*(c: BlockCtx, label: PSym): int = assert i >= 0, "no enclosing block?" result = i -proc blockExit*(c; bu; targetBlock: int) = - ## Emits the jump target description for a jump to `targetBlock`. - # XXX: a target list is only necessary if there's more than one jump - # target - bu.subTree mnkTargetList: - if blockLeaveActions(c, bu, targetBlock): - bu.add labelNode(bu.requestLabel(c.blocks[targetBlock])) proc raiseExit*(c; bu) = - ## Emits the jump target description for a jump to the nearest enclosing + ## Emits the jump target for a jump to the nearest enclosing ## exception handler. - var i = c.blocks.high - while i >= 0 and c.blocks[i].kind != bkTryExcept: - dec i + var last = c.toDestroy.high - bu.subTree mnkTargetList: - if blockLeaveActions(c, bu, i): - if i == -1: - # nothing handles the exception within the current procedure - bu.add MirNode(kind: mnkResume) - else: - bu.add labelNode(bu.requestLabel(c.blocks[i])) + for i in countdown(c.blocks.high, 0): + let b {.cursor.} = c.blocks[i] + case b.kind + of bkBlock: + discard "nothing to do" + of bkScope: + if b.numRegistered > 0: + # there are some locations that require cleanup + if c.toDestroy[last].label.isNone: + c.toDestroy[last].label = some bu.allocLabel() + + bu.add labelNode(c.toDestroy[last].label.unsafeGet) + return + of bkTryExcept, bkTryFinally, bkFinally, bkExcept: + # something that intercepts the exceptional control-flow + bu.add labelNode(bu.requestLabel(c.blocks[i])) + return + + # no local exception handler exists + bu.add MirNode(kind: mnkResume) proc closeBlock*(c; bu): bool = ## Finishes the current block. If required for the block (because it is a @@ -224,20 +223,6 @@ proc startScope*(c): int = c.blocks.add Block(kind: bkScope) c.currScope = c.blocks.high -proc earlyExit*(c; bu) = - ## Emits the destroy operations for when structured control-flow reaches the - ## current scope's end. All entities for which a destroy operation is - ## emitted are unregistered already. - let start = c.toDestroy.len - c.blocks[c.currScope].numRegistered - var i = c.toDestroy.high - - while i >= start and c.toDestroy[i].label.isNone: - bu.emitDestroy(c.toDestroy[i].entity) - dec i - - # unregister the entities for which a destroy operation was emitted: - c.blocks[c.currScope].numRegistered = i - start + 1 - c.toDestroy.setLen(i + 1) proc closeScope*(c; bu; nextScope: int, hasStructuredExit: bool) = ## Pops the scope from the stack and emits the scope exit actions. @@ -247,58 +232,62 @@ proc closeScope*(c; bu; nextScope: int, hasStructuredExit: bool) = ## `next` is the index of the scope index returns by the previous ## `startScope <#startScope,BlockCtx>`_ call. # emit all destroy operations that don't need a finally - earlyExit(c, bu) - var scope = c.blocks.pop() assert scope.kind == bkScope let start = c.toDestroy.len - scope.numRegistered - var next = none LabelId - if start < c.toDestroy.len and hasStructuredExit: - # there are destroy operations that need a finally. A goto is required - # for visiting them - next = some bu.allocLabel() - bu.subTree mnkGoto: - bu.subTree mnkTargetList: - emitFinalizerLabels(c, bu, start..c.toDestroy.high) - bu.add labelNode(next.unsafeGet) - - scope.scopeExits.add next.unsafeGet - - # emit all finally sections for the scope. Since not all entities requiring - # destruction necessarily start their existence at the start of the scope, - # multiple sections may be required - var curr = none LabelId - for i in countdown(c.toDestroy.high, start): - # if a to-destroy entry has a label, it marks the start of a new finally - if c.toDestroy[i].label.isSome: - if curr.isSome: - # finish the previous finally by emitting the corresponding 'continue': - bu.subTree MirNode(kind: mnkContinue, len: 2): + if hasStructuredExit: + var i = c.toDestroy.high + while i >= start: + bu.emitDestroy(c.toDestroy[i].entity) + dec i + + # look for the first destroy that needs a 'finally' + var i = c.toDestroy.high + while i >= start and c.toDestroy[i].label.isNone: + dec i + + if i >= start: + # some exceptional exits need cleanup + var next = none LabelId + if hasStructuredExit: + # emit a jump over the finalizers: + next = some bu.allocLabel() + bu.goto next.unsafeGet + + # emit all finally sections for the scope. Since not all entities requiring + # destruction necessarily start their existence at the start of the scope, + # multiple sections may be required + var curr = none LabelId + for i in countdown(i, start): + # if a to-destroy entry has a label, it marks the start of a new finally + if c.toDestroy[i].label.isSome: + if curr.isSome: + # finish the previous finally by emitting the corresponding + # 'continue': + bu.subTree mnkContinue: + bu.add labelNode(c.toDestroy[i].label.unsafeGet) + + curr = c.toDestroy[i].label + bu.subTree mnkFinally: bu.add labelNode(curr.unsafeGet) - # a finally section that's not the last one always continues with - # the next finally - bu.add labelNode(c.toDestroy[i].label.unsafeGet) - - curr = c.toDestroy[i].label - bu.subTree mnkFinally: - bu.add labelNode(curr.unsafeGet) - - bu.emitDestroy(c.toDestroy[i].entity) - - if curr.isSome: - # finish the final finally. `scopeExits` stores all possible follow-up - # targets for the finally - bu.subTree MirNode(kind: mnkContinue, len: uint32(1 + scope.scopeExits.len)): - bu.add labelNode(curr.unsafeGet) - for it in scope.scopeExits.items: - bu.add labelNode(it) - - if next.isSome: - # the join point for the structured scope exit - bu.join next.unsafeGet - - # unregister all entities registered with the scope: - c.toDestroy.setLen(start) + + bu.emitDestroy(c.toDestroy[i].entity) + + # unregister all entities registered with the scope. This needs to happen + # before the ``raiseExitActions`` call below + c.toDestroy.setLen(start) + + if curr.isSome: + # continue raising the exception + bu.subTree mnkContinue: + raiseExit(c, bu) + + if next.isSome: + bu.join next.unsafeGet + else: + # unregister all entities registered with the scope: + c.toDestroy.setLen(start) + c.currScope = nextScope diff --git a/compiler/mir/mirpasses.nim b/compiler/mir/mirpasses.nim index cbb5ab38bf8..7a033ac6a89 100644 --- a/compiler/mir/mirpasses.nim +++ b/compiler/mir/mirpasses.nim @@ -809,8 +809,7 @@ proc splitAssignments(tree: MirTree, changes: var Changeset) = # * if the destination is a local, does the exceptional path enter a # local exception handler? if tree[tree.getRoot(tree.operand(p, 0))].kind notin Locals or - tree[target].kind != mnkTargetList or - tree[tree.last(target)].kind != mnkResume: + tree[target].kind != mnkResume: # future direction: this can be optimized. The assignment only needs to # be split if the assignment destination's value is observed on the # exceptional control-flow path diff --git a/compiler/mir/mirtrees.nim b/compiler/mir/mirtrees.nim index ad27128e6a8..75cb6331ebc 100644 --- a/compiler/mir/mirtrees.nim +++ b/compiler/mir/mirtrees.nim @@ -90,9 +90,6 @@ type mnkResume ## special action in a target list that means "resume ## exception handling in caller" - mnkLeave ## a leave action within a target list - mnkTargetList## describes the actions to perform prior to jumping, as well - ## as the final jump mnkDef ## marks the start of existence of a local, global, procedure, ## or temporary. Supports an optional intial value (except for @@ -175,9 +172,8 @@ type # future direction: the arithmetic operations should also apply to # unsigned integers - mnkRaise ## if the operand is an ``mnkNone`` node, reraises the - ## currently active exception. Otherwise, consumes the operand - ## and sets it as the active exception + mnkRaise ## starts exceptional control-flow and jumps to the specified + ## handler mnkSetConstr ## constructor for set values mnkRange ## range constructor. May only appear in set constructions @@ -276,7 +272,7 @@ type strVal*: StringId of mnkAstLit: ast*: AstId - of mnkLabel, mnkLeave: + of mnkLabel: label*: LabelId of mnkImmediate: imm*: uint32 ## meaning depends on the context @@ -284,7 +280,7 @@ type magic*: TMagic of mnkNone, mnkNilLit, mnkType, mnkResume: discard - of {low(MirNodeKind)..high(MirNodeKind)} - {mnkNone .. mnkLeave}: + of {low(MirNodeKind)..high(MirNodeKind)} - {mnkNone..mnkResume}: len*: uint32 MirTree* = seq[MirNode] @@ -309,7 +305,7 @@ const ## Node kinds that represent definition statements (i.e. something that ## introduces a named entity) - AtomNodes* = {mnkNone..mnkLeave} + AtomNodes* = {mnkNone..mnkResume} ## Nodes that don't support sub nodes. SubTreeNodes* = AllNodeKinds - AtomNodes @@ -317,7 +313,7 @@ const SingleOperandNodes* = {mnkPathNamed, mnkPathPos, mnkPathVariant, mnkPathConv, mnkAddr, mnkDeref, mnkView, mnkDerefView, mnkStdConv, - mnkConv, mnkCast, mnkRaise, mnkArg, + mnkConv, mnkCast, mnkArg, mnkName, mnkConsume, mnkVoid, mnkCopy, mnkMove, mnkSink, mnkDestroy, mnkMutView, mnkToMutSlice} ## Nodes that start sub-trees but that always have a single sub node. @@ -329,7 +325,7 @@ const ## Assignment modifiers. Nodes that can only appear directly in the source ## slot of assignments. - LabelNodes* = {mnkLabel, mnkLeave} + LabelNodes* = {mnkLabel} LiteralDataNodes* = {mnkNilLit, mnkIntLit, mnkUIntLit, mnkFloatLit, mnkStrLit, mnkAstLit} @@ -421,7 +417,7 @@ template `[]`*(tree: MirTree, i: NodePosition | OpValue): untyped = template isAtom(kind: MirNodeKind): bool = # much faster than an `in SubTreeNodes` test - ord(kind) <= ord(mnkLeave) + ord(kind) <= ord(mnkResume) func parent*(tree: MirTree, n: NodePosition): NodePosition = result = n diff --git a/compiler/mir/utils.nim b/compiler/mir/utils.nim index 4a4362feb80..a4d66ed64a9 100644 --- a/compiler/mir/utils.nim +++ b/compiler/mir/utils.nim @@ -53,7 +53,7 @@ func `$`(n: MirNode): string = of mnkMagic: result.add " magic: " result.add $n.magic - of mnkLabel, mnkLeave: + of mnkLabel: result.add " label: " result.addInt n.label.uint32 of mnkImmediate: @@ -241,7 +241,7 @@ proc singleToStr(n: MirNode, result: var string, c: RenderCtx) = result.add "type(" typeToStr(result, n.typ, c.env) result.add ")" - of AllNodeKinds - Atoms - mnkProc + {mnkResume, mnkLeave}: + of AllNodeKinds - Atoms - mnkProc + {mnkResume}: result.error(n) proc singleToStr(tree: MirTree, i: var int, result: var string, c: RenderCtx) = @@ -348,18 +348,11 @@ proc targetToStr(nodes: MirTree, i: var int, result: var string) = var n {.cursor.} = next(nodes, i) case n.kind of mnkLabel: - result.add n.label - of mnkTargetList: result.add "[" - commaSeparated n.len: - n = next(nodes, i) - case n.kind - of mnkLabel: result.add n.label - of mnkLeave: result.add "Leave(L" & $n.label.int & ")" - of mnkResume: result.add "Resume" - else: result.error(n) - + result.add n.label result.add "]" + of mnkResume: + result.add "[Resume]" else: result.error(n) @@ -610,9 +603,7 @@ proc stmtToStr(nodes: MirTree, i: var int, indent: var int, result: var string, exprToStr() result.add "\n" of mnkRaise: - tree "raise ": - valueToStr() - result.add " -> " + tree "raise -> ": targetToStr() result.add "\n" of mnkDestroy: @@ -636,13 +627,8 @@ proc stmtToStr(nodes: MirTree, i: var int, indent: var int, result: var string, result.add ":\n" of mnkContinue: tree "continue ": - inc i # skip the label - result.add "{" - for j in 1.. 1: - result.add ", " - labelToStr(nodes, i, result) - result.add "}\n" + targetToStr(nodes, i, result) + result.add "\n" dec indent of AllNodeKinds - StmtNodes: diff --git a/compiler/sem/injectdestructors.nim b/compiler/sem/injectdestructors.nim index 2edee0e4c2c..781c679fdcb 100644 --- a/compiler/sem/injectdestructors.nim +++ b/compiler/sem/injectdestructors.nim @@ -551,8 +551,6 @@ proc rewriteAssignments(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, of mnkConsume: # we must be processing a call/construction argument consumeArg(tree, ctx, ar, tree.parent(parent), val, i, c) - of mnkRaise: - consumeArg(tree, ctx, ar, NodePosition val, val, i, c) of mnkMove, mnkSink: # assignments are handled separately discard diff --git a/compiler/sem/mirexec.nim b/compiler/sem/mirexec.nim index ff9e68f1b0a..42517d128c0 100644 --- a/compiler/sem/mirexec.nim +++ b/compiler/sem/mirexec.nim @@ -170,19 +170,6 @@ func `[]`(c: DataFlowGraph, pc: SomeInteger): lent Instr {.inline.} = # ---- data-flow graph setup ---- -proc firstTarget(tree: MirTree, n: NodePosition): NodePosition = - ## Returns the first label or resume in the jump target description. - case tree[n].kind - of mnkLabel: - result = n - of mnkTargetList: - for p in subNodes(tree, n): - if tree[p].kind in {mnkResume, mnkLabel}: - return p - unreachable("ill-formed target list") - else: - unreachable(tree[n].kind) - func map(env: var ClosureEnv, id: LabelId): JoinId = if id in env.labelToJoin: result = env.labelToJoin[id] @@ -210,7 +197,6 @@ func getResumeLabel(env: var ClosureEnv): JoinId = func raiseExit(env: var ClosureEnv, opc: Opcode, tree: MirTree, at, target: NodePosition) = - let target = firstTarget(tree, target) # compute the join ID to use, accounting for the special 'resume' action: let join = case tree[target].kind @@ -262,7 +248,7 @@ func emitForArgs(env: var ClosureEnv, tree: MirTree, at, source: NodePosition) = case tree[it].kind of mnkArg, mnkConsume, mnkName: emitForArg(env, tree, at, it) - of mnkMagic, mnkProc, mnkLabel, mnkTargetList, mnkImmediate: + of mnkMagic, mnkProc, mnkLabel, mnkImmediate, mnkResume: discard else: emitLvalueOp(env, opUse, tree, at, OpValue it) @@ -410,9 +396,7 @@ func computeDfg*(tree: MirTree): DataFlowGraph = for i, n in tree.pairs: case n.kind of mnkGoto: - let first = tree.firstTarget(tree.child(i, 0)) - # the node for the target is guaranteed to be a label - goto i, tree[first].label + goto i, tree[i, 0].label of mnkLoop: loop i, tree[i, 0].label of mnkIf: @@ -449,29 +433,8 @@ func computeDfg*(tree: MirTree): DataFlowGraph = raiseExit(env, opFork, tree, i, tree.child(i, n.len - 1)) of mnkFinally: join i, tree[i, 0].label - of mnkContinue: - var j = 0 - # a continue acts much like a dispatcher - for it in subNodes(tree, i): - if j == 0: - discard "label of the associated finally; ignore" - elif j < n.len.int - 1: - fork i, tree[it].label - else: - goto i, tree[it].label - inc j - - if n.len == 1: - # no follow-up targets means that the finally continues exceptional - # control-flow in the caller - let target = env.getResumeLabel() - env.instrs.add Instr(op: opGoto, node: i, dest: target) - of mnkRaise: - # raising an exception consumes it: - if tree[tree.operand(i)].kind != mnkNone: - emitLvalueOp(env, opConsume, tree, i, tree.operand(i)) - - raiseExit(env, opGoto, tree, i, tree.child(i, 1)) + of mnkContinue, mnkRaise: + raiseExit(env, opGoto, tree, i, tree.child(i, 0)) of mnkEndStruct: # emit a join at the end of an 'if' if ifs.len > 0 and tree[i, 0].label == ifs[^1]: diff --git a/compiler/vm/vm.nim b/compiler/vm/vm.nim index 548e5d61372..8fce437c94c 100644 --- a/compiler/vm/vm.nim +++ b/compiler/vm/vm.nim @@ -82,13 +82,6 @@ import std/options as stdoptions from std/math import round, copySign type - VmException = object - ## Internal-only. - refVal: HeapSlotHandle - trace: VmRawStackTrace - # XXX: the trace should be stored in the exception object, which would - # also make it accessible to the guest (via ``getStackTrace``) - VmThread* = object ## This is beginning of splitting up ``TCtx``. A ``VmThread`` is ## meant to encapsulate the state that makes up a single execution. This @@ -101,9 +94,9 @@ type loopIterations: int ## the number of remaining jumps backwards - currentException: HeapSlotHandle - ## the exception ref that's returned when querying the current exception - ehStack: seq[tuple[ex: VmException, pc: uint32]] + exState: ExceptionState + ## the data for the exception runtime + ehStack: seq[uint32] ## the stack of currently executed EH threads. A stack is needed since ## exceptions can be raised while another exception is in flight @@ -150,15 +143,10 @@ type const traceCode = defined(nimVMDebugExecute) - fromEhBit = cast[BiggestInt](0x8000_0000_0000_0000'u64) - ## the presence in a finally's control register signals that the finally - ## was entered as part of exception handling const errIllegalConvFromXtoY = "illegal conversion from '$1' to '$2'" -func `$`(x: VmException) {.error.} - proc createStackTrace*( c: TCtx, thread: VmThread, @@ -223,11 +211,6 @@ template `[]=`(r: Registers, i: SomeInteger, val: TFullReg) = regIndexCheck(r, i) r.data[x] = val -func getReg(t: var VmThread, i: int): var TFullReg {.inline.} = - ## Shortcut for accessing the the `i`-th register belonging to the topmost - ## stack frame. - t.regs[t.sframes[^1].start + i] - func setNodeValue(dest: LocHandle, node: PNode) = assert dest.typ.kind == akPNode deref(dest).nodeVal = node @@ -536,81 +519,50 @@ proc findEh(c: TCtx, t: VmThread, at: PrgCtr, frame: int # no handler exists -proc setCurrentException(t: var VmThread, mem: var VmMemoryManager, - ex: HeapSlotHandle) = - ## Sets `ex` as `t`'s current exception, freeing the previous exception, - ## if necessary. - if ex.isNotNil: - mem.heap.heapIncRef(ex) - if t.currentException.isNotNil: - mem.heap.heapDecRef(mem.allocator, t.currentException) - - t.currentException = ex - -proc decodeControl(x: BiggestInt): tuple[fromEh: bool, val: uint32] = - let x = cast[BiggestUInt](x) - result.fromEh = bool(x shr 63) - result.val = uint32(x) - -proc runEh(t: var VmThread, c: var TCtx): Result[PrgCtr, VmException] = +proc runEh(t: var VmThread, c: var TCtx): Option[PrgCtr] = ## Executes the active EH thread. Returns either the bytecode position to ## resume main execution at, or the uncaught exception. ## ## This implements the VM-in-VM for executing the EH instructions. - template tos: untyped = - # top-of-stack + template pc: untyped = t.ehStack[^1] while true: - let instr = c.ehCode[tos.pc] + let instr = c.ehCode[pc] # already move to the next instruction - inc tos.pc + inc pc - template yieldControl() = - setCurrentException(t, c.memory, tos.ex.refVal) - result.initSuccess(instr.b.PrgCtr) + template yieldControl(pop: static bool) = + when pop: + t.ehStack.shrink(t.ehStack.len - 1) + result = some(instr.b.PrgCtr) return case instr.opcode - of ehoExcept, ehoFinally: + of ehoExcept: # enter exception handler - yieldControl() + yieldControl(true) + of ehoFinally: + yieldControl(false) of ehoExceptWithFilter: let - raised = c.heap.tryDeref(tos.ex.refVal, noneType).value() + raised = c.heap.tryDeref(t.exState.current, noneType).value() if getTypeRel(raised.typ, c.types[instr.a]) in {vtrSub, vtrSame}: # success: the filter matches - yieldControl() + yieldControl(true) else: discard "not handled, try the next instruction" of ehoNext: - tos.pc += instr.b - 1 # account for the ``inc`` above - of ehoLeave: - case instr.a - of 0: - # discard the parent thread - swap(tos, t.ehStack[^2]) - t.ehStack.setLen(t.ehStack.len - 1) - of 1: - # discard the parent thread if it's associated with the provided - # control register - let (fromEh, b) = decodeControl(t.getReg(instr.b.TRegister).intVal) - if fromEh: - vmAssert b.int == t.ehStack.high - 1 - swap(tos, t.ehStack[^2]) - t.ehStack.setLen(t.ehStack.len - 1) - else: - vmUnreachable("illegal operand") + pc += instr.b - 1 # account for the ``inc`` above of ehoEnd: # terminate the thread and return the unhandled exception - result.initFailure(move t.ehStack[^1].ex) + result = none(PrgCtr) t.ehStack.setLen(t.ehStack.len - 1) break -proc resumeEh(c: var TCtx, t: var VmThread, - frame: int): Result[PrgCtr, VmException] = +proc resumeEh(c: var TCtx, t: var VmThread, frame: int): Option[PrgCtr] = ## Continues raising the exception from the top-most EH thread. If exception ## handling code is found, unwinds the stack till where the handler is ## located and returns the program counter where to resume. Otherwise @@ -618,7 +570,7 @@ proc resumeEh(c: var TCtx, t: var VmThread, var frame = frame while true: let r = runEh(t, c) - if r.isOk: + if r.isSome: # an exception handler or finalizer is entered. Unwind to the target # frame: if frame < t.sframes.len - 1: @@ -635,12 +587,11 @@ proc resumeEh(c: var TCtx, t: var VmThread, if pos.isSome: # EH code exists in a frame above. Run it frame = pos.get().frame # update to the frame the EH code is part of - t.ehStack.add (r.takeErr(), pos.get().ehInstr) + t.ehStack.add pos.get().ehInstr else: return r -proc opRaise(c: var TCtx, t: var VmThread, at: PrgCtr, - ex: sink VmException): Result[PrgCtr, VmException] = +proc opRaise(c: var TCtx, t: var VmThread, at: PrgCtr): Option[PrgCtr] = ## Searches for an exception handler for the instruction at `at`. If one is ## found, the stack is unwound till the frame the handler is in and the ## position where to resume is returned. If there no handler is found, `ex` @@ -648,29 +599,22 @@ proc opRaise(c: var TCtx, t: var VmThread, at: PrgCtr, let pos = findEh(c, t, at, t.sframes.high) if pos.isSome: # spawn and run the EH thread: - t.ehStack.add (ex, pos.get().ehInstr) + t.ehStack.add pos.get().ehInstr result = resumeEh(c, t, pos.get().frame) else: # no exception handler exists: - result.initFailure(ex) + result = none(PrgCtr) -proc handle(res: sink Result[PrgCtr, VmException], c: var TCtx, +proc handle(res: sink Option[PrgCtr], c: var TCtx, t: var VmThread): PrgCtr = ## If `res` is an unhandled exception, reports the exception to the ## supervisor. Otherwise returns the position where to continue. - if res.isOk: - result = res.take() - if c.code[result].opcode == opcFinally: - # setup the finally section's control register - let reg = c.code[result].regA - t.getReg(reg).initIntReg(fromEhBit or t.ehStack.high, c.memory) - inc result - + if res.isSome: + result = res.unsafeGet() else: # report to the exception to the supervisor (by raising an event) - let ex = res.takeErr() - reportException(c, ex.trace, - c.heap.tryDeref(ex.refVal, noneType).value()) + reportException(c, t.exState.stack[^1].trace, + c.heap.tryDeref(t.exState.current, noneType).value()) template atomVal(r: TFullReg): untyped = cast[ptr Atom](r.handle.rawPointer)[] @@ -1994,7 +1938,7 @@ proc rawExecute(c: var TCtx, t: var VmThread, pc: var int): YieldReason = c.callbacks[entry.cbOffset]( VmArgs(ra: ra, rb: rb, rc: rc, slots: regs.data, - currentExceptionPtr: addr t.currentException, + exState: addr t.exState, currentLineInfo: c.debug[pc], typeCache: addr c.typeInfoCache, mem: addr c.memory, @@ -2085,77 +2029,20 @@ proc rawExecute(c: var TCtx, t: var VmThread, pc: var int): YieldReason = let instr2 = c.code[pc] let rbx = instr2.regBx - wordExcess - 1 # -1 for the following 'inc pc' inc pc, rbx - of opcEnter: - # enter the finalizer to the target but consider finalizers associated - # with the instruction - let target = pc + c.code[pc].regBx - wordExcess - if c.code[target].opcode == opcFinally: - # remember where to jump back when leaving the finally section - let reg = c.code[target].regA - regs[reg].initIntReg(pc + 1, c.memory) - # jump to the instruction following the 'Finally' - pc = target - else: - vmUnreachable("target is not a 'Finally' instruction") - of opcLeave: - case (instr.regC - byteExcess) - of 0: # exit the EH thread - c.heap.heapDecRef(c.allocator, t.ehStack[^1].ex.refVal) - t.ehStack.setLen(t.ehStack.len - 1) - of 1: # exit the finally section - let (fromEh, b) = decodeControl(regs[ra].intVal) - if fromEh: - # only the topmost EH thread can be aborted - vmAssert t.ehStack.high == int(b) - c.heap.heapDecRef(c.allocator, t.ehStack[^1].ex.refVal) - t.ehStack.setLen(t.ehStack.len - 1) - - # the instruction is a no-op when leaving a finally section that wasn't - # entered through an exception - else: - vmUnreachable("invalid operand") - - setCurrentException(t, c.memory): - if t.ehStack.len > 0: - t.ehStack[^1].ex.refVal - else: - HeapSlotHandle(0) - of opcFinally: - # when entered by normal control-flow, the corresponding exit will jump - # to the target specified on this instruction - decodeBx(rkInt) - regs[ra].intVal = pc + rbx + discard "a no-op" of opcFinallyEnd: # where control-flow resumes depends on how the finally section was # entered - let (isError, target) = decodeControl(regs[ra].intVal) - if isError: - # continue the EH thread - pc = resumeEh(c, t, t.sframes.high).handle(c, t) - 1 - updateRegsAlias() - else: - # not entered through exceptional control-flow; jump to target stored - # in the register - pc = PrgCtr(target) - 1 - + pc = resumeEh(c, t, t.sframes.high).handle(c, t) - 1 + updateRegsAlias() of opcRaise: - decodeBImm() - discard rb # fix the "unused" warning - checkHandle(regs[ra]) - - # `imm == 0` -> raise; `imm == 1` -> reraise current exception - let isReraise = imm == 1 - - var exception: VmException - if isReraise: - # re-raise the current exception - exception = move t.ehStack[^1].ex - # popping the thread is the responsibility of the spawned EH thread - else: - # gather the stack-trace for the exception: - var pc = pc - exception.trace.newSeq(t.sframes.len) + if t.exState.stack.len > 0 and t.exState.stack[^1].trace.len == 0: + # the most-recent exception (which is considered to be the one that + # was just raised) has no stacktrace -> generate one + var + trace = newSeq[(PSym, PrgCtr)](t.sframes.len) + pc = pc for i, it in t.sframes.pairs: let p = @@ -2164,16 +2051,16 @@ proc rawExecute(c: var TCtx, t: var VmThread, pc: var int): YieldReason = else: pc - exception.trace[i] = (it.prc, p) + trace[i] = (it.prc, p) # TODO: store the trace in the exception's `trace` field and move this - # setup logic to the ``prepareException`` implementation + # setup logic to the ``raiseExceptionEx`` implementation + t.exState.stack[^1].trace = trace - exception.refVal = regs[ra].atomVal.refVal - # keep the exception alive during exception handling: - c.heap.heapIncRef(exception.refVal) + # set the current exception to the active one: + asgnRef(t.exState.current, t.exState.stack[^1].refVal, c.memory, true) - pc = opRaise(c, t, pc, exception).handle(c, t) - 1 + pc = opRaise(c, t, pc).handle(c, t) - 1 updateRegsAlias() of opcNew: let typ = c.types[instr.regBx - wordExcess] @@ -2955,8 +2842,11 @@ proc dispose*(c: var TCtx, t: sink VmThread) = ## Cleans up and frees all VM data owned by `t`. c.memory.cleanUpLocations(t.regs, 0) - if t.currentException.isNotNil: - c.heap.heapDecRef(c.allocator, t.currentException) + for it in t.exState.stack.items: + c.heap.heapDecRef(c.allocator, it.refVal) + + if t.exState.current.isNotNil: + c.heap.heapDecRef(c.allocator, t.exState.current) # free heap slots that are pending cleanup cleanUpPending(c.memory) diff --git a/compiler/vm/vm_enums.nim b/compiler/vm/vm_enums.nim index 087c97776b7..eb89a2ac71b 100644 --- a/compiler/vm/vm_enums.nim +++ b/compiler/vm/vm_enums.nim @@ -140,9 +140,6 @@ type opcJmp, # jump Bx opcJmpBack, # jump Bx; resulting from a while loop opcBranch, # branch for 'case' - opcEnter, # jump Bx; target must be a ``opcFinally`` instruction - opcLeave, # if C == 1: abort EH thread associated with finally; - # if C == 0; abort active EH thread opcFinally, opcFinallyEnd, opcNew, @@ -174,4 +171,4 @@ const firstABxInstr* = opcTJmp largeInstrs* = { # instructions which use 2 int32s instead of 1: opcConv, opcObjConv, opcCast, opcNewSeq, opcOf} - relativeJumps* = {opcTJmp, opcFJmp, opcJmp, opcJmpBack, opcEnter, opcFinally} + relativeJumps* = {opcTJmp, opcFJmp, opcJmp, opcJmpBack} diff --git a/compiler/vm/vmdef.nim b/compiler/vm/vmdef.nim index a58de2cd708..6a259c3064c 100644 --- a/compiler/vm/vmdef.nim +++ b/compiler/vm/vmdef.nim @@ -372,7 +372,7 @@ type slots*: ptr UncheckedArray[TFullReg] # TODO: rework either the callback or exception handling (or both) so that # no pointer is required here - currentExceptionPtr*: ptr HeapSlotHandle + exState*: ptr ExceptionState currentLineInfo*: TLineInfo # XXX: These are only here as a temporary measure until callback handling @@ -675,8 +675,6 @@ type ## enter the ``finally`` handler ehoNext ## relative jump to another instruction - ehoLeave - ## abort the parent thread ehoEnd ## ends the thread without treating the exception as handled @@ -761,6 +759,23 @@ type ## maps the symbol of a procedure to the associated data gathered by the ## profiler + VmException* = object + ## Internal-only. Has to be exposed here because ``VmArgs`` needs access + ## to the type. + refVal*: HeapSlotHandle + trace*: VmRawStackTrace + # XXX: the trace should be stored in the exception object, which would + # also make it accessible to the guest (via ``getStackTrace``) + caught*: bool + ## whether the exception was already caught + + ExceptionState* = object + ## Thread-local exception runtime state. + stack*: seq[VmException] + ## previously caught but not yet full handled exceptions + current*: HeapSlotHandle + ## the current exception, which is what ``getCurrentException`` returns + func `<`*(a, b: FieldIndex): bool {.borrow.} func `<=`*(a, b: FieldIndex): bool {.borrow.} func `==`*(a, b: FieldIndex): bool {.borrow.} @@ -968,11 +983,11 @@ template isValid*(handle: LocHandle): bool = template currentException*(a: VmArgs): HeapSlotHandle = ## A temporary workaround for the exception handle being stored as a pointer - a.currentExceptionPtr[] + a.exState.current template `currentException=`*(a: VmArgs, h: HeapSlotHandle) = ## A temporary workaround for the exception handle being stored as a pointer - a.currentExceptionPtr[] = h + a.exState.current = h func unpackedConvDesc*(info: uint16 ): tuple[op: NumericConvKind, dstbytes, srcbytes: int] = diff --git a/compiler/vm/vmgen.nim b/compiler/vm/vmgen.nim index acada37c8ad..83d669e38dc 100644 --- a/compiler/vm/vmgen.nim +++ b/compiler/vm/vmgen.nim @@ -137,8 +137,12 @@ type ## upper bound of allocated registers at the beginning of the block label: BlockId case kind: BlockKind - of bkBlock, bkFinally: + of bkBlock: start: TPosition + of bkFinally: + patchPos: uint32 + ## the ``ehoNext`` instruction that needs to be patched once the + ## follow-up handler is known of bkExcept: discard @@ -166,9 +170,8 @@ type ehExits: seq[tuple[label: BlockId, pos: uint32]] ## EH instructions that need patching once position and type of the ## target EH instruction is known - lastPath: CgNode - ## the path corresponding to the previously emitted EH instruction - ## sequence, or nil. Prevents excessive EH code duplication + ehPatch: seq[tuple[label: BlockId, pos: uint32]] + ## EH table entries that need patching once the handler is generated CodeGenCtx* = object ## Bundles all input, output, and other contextual data needed for the @@ -456,59 +459,17 @@ proc genEhCode(c: var TCtx, n: CgNode) proc registerEh(c: var TCtx, n: CgNode) = ## Emits an exception-handling table entry for the instruction at the head - ## of the instruction list (i.e., the one emitted next). `n` must be either - ## a label or target list. - proc isEqual(a, b: CgNode): bool = - ## Compares two label-like nodes for equality. - if a.kind != b.kind: - return false - - case a.kind - of cnkLeave: a[0].label == b[0].label - of cnkLabel: a.label == b.label - of cnkResume: true - else: - unreachable() - - proc comparePaths(a, b: CgNode): int = - ## Returns the number of actions `a` and `b` share at the end. 0 - ## means that both share no trailing actions. - let (a, b) = - if a.kind == cnkTargetList: (a, b) - else: (b, a) - # because of the above swap, if `a` is not a list of targets, then neither - # is `b` - if a.kind == cnkTargetList: - if b.kind == cnkLabel: - result = if isEqual(a[^1], b): 1 else: 0 - else: - result = min(a.len, b.len) - for i in 1..result: - if not isEqual(a[^i], b[^i]): - return i - 1 - # one target list is a subset of the other - else: - result = if isEqual(a, b): 1 else: 0 - - let pos = uint32(c.code.len - c.prc.baseOffset.int) + ## of the instruction list (i.e., the one emitted next). case n.kind of cnkLabel: - # un-intercepted jump - if c.prc.lastPath == nil or comparePaths(c.prc.lastPath, n) == 0: - genEhCode(c, n) - - c.ehTable.add (pos, uint32(c.ehCode.len - 1)) - of cnkTargetList: - if n.len == 1 and n[0].kind == cnkResume: - # if there's nothing responding to the exception within the current - # procedure, no EH code needs to be associated with the instruction - return - - if c.prc.lastPath == nil or comparePaths(n, c.prc.lastPath) < n.len: - # cannot re-use the previous instruction sequence - genEhCode(c, n) - - c.ehTable.add (pos, uint32(c.ehCode.len - n.len)) + # a local handler or finally exists + c.ehTable.add (uint32(c.code.len - c.prc.baseOffset.int), 0'u32) + # the real EH instruction is associated later + c.prc.ehPatch.add (n.label, c.ehTable.high.uint32) + of cnkResume: + # if there's nothing responding to the exception within the current + # procedure, no EH code needs to be associated with the instruction + discard else: unreachable(n.kind) @@ -719,46 +680,6 @@ proc popBlock(c: var TCtx) = doAssert false, "leaking temporary " & $i & " " & $c.prc.regInfo[i].kind c.prc.regInfo[i] = RegInfo(kind: slotEmpty) -func controlReg(c: TCtx, blk: BlockInfo): TRegister = - c.code[blk.start.int].regA - -proc genGoto(c: var TCtx; n: CgNode) = - ## Generates and emits the code for a ``cnkGoto``. Depending on whether it's - ## an intercepted jump, the goto can translate to more than one instruction. - let - target = n[0] - info = n.info - case target.kind - of cnkLabel: - c.prc.exits.add (target.label, c.xjmp(n, opcJmp)) - of cnkTargetList: - # there are some leave actions - for i in 0.. 0 and - c.prc.ehExits[^1] == (n[0].label, c.ehCode.high.uint32): + c.prc.ehExits[^1] == (label, c.ehCode.high.uint32): c.ehCode.setLen(c.ehCode.len - 1) c.prc.ehExits.setLen(c.prc.ehExits.len - 1) # patch all EH instructions targeting the handler: - for it in take(c.prc.ehExits, n[0].label): + for it in take(c.prc.ehExits, label): c.ehCode[it.pos] = (ehoNext, 0'u16, c.ehCode.len.uint32 - it.pos) + # patch all EH mappings targeting the handler: + for it in take(c.prc.ehPatch, label): + c.ehTable[it.pos].instr = c.ehCode.len.uint32 + +proc genExcept(c: var TCtx, n: CgNode) = + ## Emits the EH code for a ``cnkExcept``. + patchEh(c, n[0].label) + pushBlock(c): BlockInfo(kind: bkExcept, label: n[0].label) let pc = uint32 c.genLabel() @@ -1054,33 +955,10 @@ proc genExcept(c: var TCtx, n: CgNode) = else: # catch-all exception handler c.ehCode.add (ehoExcept, 0'u16, pc) - # new EH code was emitted, invalidating the cached path: - c.prc.lastPath = nil - -proc genFinally(c: var TCtx, n: CgNode) = - let pc = c.genLabel() - - # update all EH instructions targeting the finally: - for it in take(c.prc.ehExits, n[0].label): - c.ehCode[it.pos] = (ehoFinally, 0'u16, uint32 pc) - - pushBlock(c): BlockInfo(kind: bkFinally, label: n[0].label, start: pc) - - let control = c.getTemp(slotTempInt) - c.patch(n[0].label) # patch the jumps targeting the finally - c.gABC(n, opcFinally, control) - # the control register is freed at the end of the finally section proc genRaise(c: var TCtx; n: CgNode) = - if n[0].kind != cnkEmpty: - let dest = c.genx(n[0]) - c.registerEh(n[^1]) - c.gABI(n, opcRaise, dest, 0, imm=0) - c.freeTemp(dest) - else: - # reraise - c.registerEh(n[^1]) - c.gABI(n, opcRaise, 0, 0, imm=1) + c.registerEh(n[^1]) + c.gABC(n, opcRaise) proc writeBackResult(c: var TCtx, info: CgNode) = ## If the result value fits into a register but is not stored in one @@ -3096,7 +2974,7 @@ proc gen(c: var TCtx; n: CgNode; dest: var TDest) = of cnkRaiseStmt: genRaise(c, n) of cnkGotoStmt: - genGoto(c, n) + c.prc.exits.add (n[0].label, c.xjmp(n, opcJmp)) of cnkStmtList: # XXX: supported for a transition period (``cgir.merge`` creates nested # statement lists) @@ -3105,17 +2983,6 @@ proc gen(c: var TCtx; n: CgNode; dest: var TDest) = of cnkVoidStmt: unused(c, n, dest) gen(c, n[0]) - of cnkContinueStmt: - # marks the end of a finally section - let - blk {.cursor.} = c.prc.blocks[^1] - control = c.controlReg(blk) - # patch the ``opcFinally`` instruction: - c.patch(blk.start) - c.gABx(n, opcFinallyEnd, control, 0) - # now free the control register - c.freeTemp(control) - popBlock(c) of cnkJoinStmt: c.patch(n[0].label) of cnkLoopJoinStmt: @@ -3128,7 +2995,23 @@ proc gen(c: var TCtx; n: CgNode; dest: var TDest) = of cnkExcept: genExcept(c, n) of cnkFinally: - genFinally(c, n) + patchEh(c, n[0].label) + c.ehCode.add (ehoFinally, 0'u16, uint32 c.genLabel()) + c.ehCode.add (ehoNext, 0'u16, 0'u32) # patched later + + pushBlock(c): BlockInfo(kind: bkFinally, patchPos: c.ehCode.high.uint32) + c.gABC(n, opcFinally) + of cnkContinueStmt: + # patch the ehoNext instruction: + let pos = c.prc.blocks[^1].patchPos + if n[0].kind == cnkResume: + c.ehCode[pos] = (ehoEnd, 0'u16, 0'u32) + else: + # cannot be patched just yet + c.prc.ehExits.add (n[0].label, pos) + + c.gABC(n, opcFinallyEnd) + popBlock(c) # pop the finally block of cnkEnd: if c.prc.blocks[^1].kind == bkBlock: c.patch(c.prc.blocks[^1].start) @@ -3160,7 +3043,7 @@ proc gen(c: var TCtx; n: CgNode; dest: var TDest) = unused(c, n, dest) of cnkInvalid, cnkMagic, cnkRange, cnkBranch, cnkBinding, cnkLabel, cnkField, cnkToSlice, - cnkResume, cnkTargetList, cnkLeave: + cnkResume: unreachable(n.kind) proc initProc(c: TCtx, owner: PSym, body: sink Body): BProc = diff --git a/compiler/vm/vmops.nim b/compiler/vm/vmops.nim index 3d7db173587..755a0073302 100644 --- a/compiler/vm/vmops.nim +++ b/compiler/vm/vmops.nim @@ -156,7 +156,35 @@ proc setCurrentExceptionWrapper(a: VmArgs) {.nimcall.} = asgnRef(a.currentException, deref(a.getHandle(0)).refVal, a.mem[], reset=true) -proc prepareExceptionWrapper(a: VmArgs) {.nimcall.} = +proc updateCurrentExc(a: VmArgs) = + if a.exState.stack.len == 0: + a.exState.current.asgnRef(HeapSlotHandle(0), a.mem[], true) + else: + a.exState.current.asgnRef(a.exState.stack[^1].refVal, a.mem[], true) + +proc nimCatchExceptionWrapper(a: VmArgs) {.nimcall.} = + # ignore the ExceptionFrame pointer; the "caught" stack is managed directly + # by the VM + a.exState.stack[^1].caught = true + +proc popException(a: VmArgs, previous: bool) = + if previous: + a.mem.heap.heapDecRef(a.mem.allocator, a.exState.stack[^2].refVal) + a.exState.stack.delete(a.exState.stack.len - 2) + else: + a.mem.heap.heapDecRef(a.mem.allocator, a.exState.stack[^1].refVal) + a.exState.stack.shrink(a.exState.stack.len - 1) + updateCurrentExc(a) + +proc nimAbortExceptionWrapper(a: VmArgs) {.nimcall.} = + popException(a, a.getInt(0) == 1) + +proc nimLeaveExceptWrapper(a: VmArgs) {.nimcall.} = + # if the except block is left via a raised exception, the topmost stack + # entry is the raise exception and must not be popped + popException(a, not a.exState.stack[^1].caught) + +proc raiseExceptionExWrapper(a: VmArgs) {.nimcall.} = let raised = a.heap[].tryDeref(deref(a.getHandle(0)).refVal, noneType).value() nameField = raised.getFieldHandle(1.fpos) @@ -170,6 +198,17 @@ proc prepareExceptionWrapper(a: VmArgs) {.nimcall.} = deref(a.getHandle(1)).strVal, a.mem.allocator) + # push to the exception stack: + a.mem.heap.heapIncRef(deref(a.getHandle(0)).refVal) + a.exState.stack.add VmException(refVal: deref(a.getHandle(0)).refVal) + +proc reraiseExceptionWrapper(a: VmArgs) {.nimcall.} = + # the following nimLeaveExcept call needs something valid to pop, so the + # caught exception is duplicated + a.exState.stack.add a.exState.stack[^1] + a.mem.heap.heapIncRef(a.exState.stack[^1].refVal) + a.exState.stack[^1].caught = false + proc nimUnhandledExceptionWrapper(a: VmArgs) {.nimcall.} = # setup the exception AST: let @@ -177,9 +216,8 @@ proc nimUnhandledExceptionWrapper(a: VmArgs) {.nimcall.} = ast = toExceptionAst($exc.getFieldHandle(1.fpos).deref().strVal, $exc.getFieldHandle(2.fpos).deref().strVal) # report the unhandled exception: - # XXX: the current stack-trace should be passed along, but we don't - # have access to it here - raiseVmError(VmEvent(kind: vmEvtUnhandledException, exc: ast)) + raiseVmError(VmEvent(kind: vmEvtUnhandledException, + trace: a.exState.stack[^1].trace, exc: ast)) proc prepareMutationWrapper(a: VmArgs) {.nimcall.} = discard "no-op" @@ -257,8 +295,12 @@ iterator basicOps*(): Override = # system operations systemop(getCurrentExceptionMsg) systemop(getCurrentException) - systemop(prepareException) + systemop(raiseExceptionEx) + systemop(reraiseException) systemop(nimUnhandledException) + systemop(nimCatchException) + systemop(nimLeaveExcept) + systemop(nimAbortException) systemop(prepareMutation) override("stdlib.system.closureIterSetupExc", setCurrentExceptionWrapper) diff --git a/doc/mir.rst b/doc/mir.rst index b34a8661273..a4c8a54c4a1 100644 --- a/doc/mir.rst +++ b/doc/mir.rst @@ -48,14 +48,8 @@ Semantics | | LVALUE - INTERMEDIATE_TARGET =