Skip to content

Commit

Permalink
mirgen: lower finally (#1468)
Browse files Browse the repository at this point in the history
## Summary

* simplify MIR control-flow constructs
* lower `finally` into a `block` + `case` dispatcher in `mirgen`
* emit exception stack management code in `mirgen` already
* inline scope cleanup directly at `break` and `return`

## Details

The goals are to:
* use a unified exception runtime across all backends
* make the MIR simpler
* reduce the complexity of the language reaching the code generators,
  and thus the amount of translation work for the code generators 

For the MIR:
* target lists are removed; jumps only need to specify a single target
* `Finally` can only be used as the target for exceptional jumps;
  unstructured control-flow out of a `Finally` section is disallowed
* only a single exceptional target can be specified for `Continue` 
* `Raise` is decoupled from exception management; it only initiates
  unwinding now

The various MIR processing, rendering, and translation is adjusted
accordingly. The CGIR equivalents to the MIR constructs change in the
same way.

Since `Finally` can no longer be used with non-exceptional control-flow
(`Goto`, `Case`, etc.), translation of both scope cleanup and `finally`
needs to change in `mirgen`. The exception runtime calls for managing
the exception stack also have to be injected in `mirgen` already.

### `finally` Lowering

`finally` is translated into continuation passing. However, instead of
reifying the `finally` into a standalone procedure, all "invocations"
of (read, jumps to) the `finally` record their original destination in
a local variable, which a dispatcher emitted at the end of the
`finally` clause then uses to jump to the target (or another
intercepting `finally`).

### Scope Cleanup

Scope cleanup in response to an exception being raised still uses
`Finally`. For cleanup in response to `break` or `return`, all
necessary cleanup is emitted directly before the `Goto`. This increases
the workload for the move analysis / destructor elision pass, but it
also results in more efficient code (since there are usually less
dynamic invocations of destructors).

### Code Generation

All three code generators are updated to consider the new syntax and
semantics of `Finally`, `Continue`, etc. Notably:

* `ccgflow` is obsolete and thus removed; code generation for
  `Finally`,  `Continue`, etc. is now simple enough to implement
  directly in `cgen`
* `jsgen` now translates `Finally` to a JavaScript `catch` clause (so
  as to not interfere with `break`s), which simplifies code generation
  (no more enabling/disabling of `finally` clauses for breaks) and
  also improves code size / performance in some cases

### Exception Runtime

* `nimAbortException` has a `viaRaise` parameter now, as the C-specific
  error flag cannot be used anymore for detecting what causes the abort
* `prepareException` is merged back into `raiseExceptionAux`

The JavaScript target also using the C exception runtime now fixes a
bug where breaking out of a `finally` didn't clean up the current
exception properly.

### VM

Exception stack management is partially decoupled from the core VM and
moved to `vmops` (which hooks and implements the various exception
runtime procedures). Generating the stacktrace still needs to be done
directly by the VM, which prevents the exception stack management from
being fully decoupled.

---------

Co-authored-by: Saem Ghani <[email protected]>
  • Loading branch information
zerbina and saem authored Nov 15, 2024
1 parent fb4665a commit 42945cd
Show file tree
Hide file tree
Showing 33 changed files with 846 additions and 1,700 deletions.
19 changes: 13 additions & 6 deletions compiler/backend/ccgexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 =
Expand Down
Loading

0 comments on commit 42945cd

Please sign in to comment.