nim-works / nimskull

An in development statically typed systems programming language; with sustainability at its core. We, the community of users, maintain it.
https://nim-works.github.io/nimskull/index.html
Other
279 stars 39 forks source link

mirgen: lower `finally` #1468

Closed zerbina closed 1 week ago

zerbina commented 2 weeks ago

Summary

Details

The goals are to:

For the MIR:

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:

Exception Runtime

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.

zerbina commented 2 weeks ago

A MIR Finally is only usable for intercepting exceptional control-flow now, and thus it cannot be used for destructor calls anymore. For implementing scope cleanup, one possible solution would have been to keep the merged destructor blocks and use dispatchers (which is what ccgflow previously did, in the end), but I opted for inlining the destructors directly at the gotos instead.

To visualize what I mean, consider the following:

block L1:
  var x = ...
  if ...: break L1
  var y = ...
  if ...: break L1

Previously, this resulted in MIR code that is equivalent to:

block L1:
  var x = ...
  try:
    if ...: break L1
    var y = ...
    try:
      if ...: break L1
    finally:
      `=destroy`(y)
  finally:
    `=destroy`(x)

Now, the generated MIR code is equivalent to:

block L1:
  var x = ...
  if ...:
    `=destroy`(x)
    break L1
  var y = ...
  if ...:
    `=destroy`(y)
    `=destroy`(x)
    break L1
  `=destroy`(y)
  `=destroy`(x)

While both are functionally equivalent, the inlined version allows for more static elision of =destroy calls and destructive moves (wasMoved), thus possibly reducing run-time overhead.


I made a test with the compiler code-base in order to get some concrete performance numbers. Let A be the compiler from fb4665a518b68c1d329208950520e4ff648b1649 (devel, at the time of writing). Let B be the compiler from this PR.

Using a -d:release version of A that was built by A, the time it takes to compile A is:

Time (mean ± σ):     14.955 s ±  0.156 s

Using a -d:release version of A that was built by B, the time it takes to compile A is:

Time (mean ± σ):     14.641 s ±  0.047 s
zerbina commented 1 week ago

/merge

github-actions[bot] commented 1 week ago

Merge requested by: @zerbina

Contents after the first section break of the PR description has been removed and preserved below:


## Note For Reviewers * reviewing the commits individually is likely going to be easier than reviewing everything together