Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NativeAOT-LLVM] Exception handling: outstanding tasks #2169

Open
SingleAccretion opened this issue Jan 14, 2023 · 1 comment
Open

[NativeAOT-LLVM] Exception handling: outstanding tasks #2169

SingleAccretion opened this issue Jan 14, 2023 · 1 comment
Labels
area-NativeAOT-LLVM LLVM generation for Native AOT compilation (including Web Assembly)

Comments

@SingleAccretion
Copy link

SingleAccretion commented Jan 14, 2023

With #2145 we will have the basic runtime mechanism for dispatching exceptions in place, however, much work still remains for the implementation to reach non-prototype level of quality.

Correctness:

  1. Inter-frame dispatch and filter ordering.
    • Capture the shadow stack of the throwing frame inside the native exception.
      • Call dispatchers "on" this shadow stack (will require some dispatch pieces to be moved to native code).
    • Capture the list of pending fault/finally handlers in the first pass and invoke them in the second.
      • Should the managed or native exception carry this list? Can we avoid heap allocations for this case by using the shadow stack somehow?
    • Hard problem: dynamic stackalloc live into handlers.
      • Current plan of action is to implement this with a custom stack-like allocator.
      • We should find/write an extensive set of tests for this scenario.
    • Fixed in [NativeAOT-LLVM] Implement two-pass exception handling #2284.
  2. We need to be able to identify the "top frame", one after which the exception will go unhanded. This implies wrapping RPI methods inside catch-all try/catch (perhaps a native one):
  3. Related: we need to properly handle unhandled exceptions.
  4. I think there are also some first-chance handlers we need to invoke when throwing.
  5. Exceptional exits from runtime imports will fail to restore the shadow stack, leading to shadow stack top corruption. This should be fixed by moving the shadow stack top restore logic into RPI frames.
  6. throw null should be translated into throw new NullReferenceException().
  7. Rethrow currently recaptures the stack trace. It should not.
  8. Throwing filters should return "continue search".
  9. Throwing faults are not handled correctly, see src\tests\JIT\Methodical\eh\nested\general\methodthrowsinfinally_d.csproj.
  10. wasi-wasm needs to be done using "manual" unwind (in both passes). Fortunately, that is feasible because exceptions cannot propagate across any managed<->unmanaged boundaries, even when it comes the the native runtime (in other words, the runtime can only invoke managed code when in a tail position).

Performance / code size:

  1. Utilize WASM EH instead of JS-based Emscripten exceptions. We will still want to retain the C++-based implementation so that porting to non-WASM platforms is possible.
  2. Review the generated WASM code for obvious inefficiencies in code size; adapt codegen and dispatchers as appropriate.
  3. We currently capture the stack trace when throwing the exception. This is expensive. Design a cheaper scheme.
  4. Transition to funclets only for finally/filter clauses.
  5. Insert unhandled exception handlers in the RPI helper.
  6. Coalesce virtual unwind frame pop on method exit with the last NOT_IN_TRY setting.
  7. Do not require pop calls for top-level faults which do not access the unwind index.
  8. Better CQ heuristics for unwind index insertion: do not expand the group if the predecessor is in a different protected region, or is a throw helper block, etc.
  9. Skip exceptional predecessors in unwind index insertion for which we know the edge is dead because the source block never throws.
  10. Possible ideas:
    • Do not pass the shadow stack to the catch helper (somehow).
    • Throw in the catch helper instead of using rethrow (measured perf degradation is ~2.5x).
    • Create specialized catch helpers for common indices (2/3).
    • Define the unwind index on entry to catch handlers via the helper.
  11. Shrink the EH info by not wasting leading bytes on padding. Will require adding support for unaligned relocations to the object writer, using Unsafe.ReadUnaliged at runtime and reworking the format a bit.
  12. Do not force locals not live cross-funclet to memory. See notes in [NativeAOT-LLVM] Do not use funclets for faults #2443.

Debugging:

  1. Generate proper function-level DWARF metadata for funclets.
  2. Implement .NET-level stack trace (with collapsed funclets). Note we are currently emitting the MethodRVA-to-token map, which is not used.
  3. Aborts induced by unhandled exceptions should not print a stack trace - we will have printed our own already.

Documentation:

  1. Write a comprehensive document about the design and implementation of the scheme we'll end up with.

Code cleanup:

  1. Do not include ExceptionHandling.cs i. e. the usual EH dispatch, in the WASM build.
@jkotas jkotas added the area-NativeAOT-LLVM LLVM generation for Native AOT compilation (including Web Assembly) label Jan 15, 2023
@jkotas
Copy link
Member

jkotas commented Jan 15, 2023

Generated (managed) main.
Thread.Start entrypoints.

Both of these should be implemented are RPI. RPI should be the only thing to worry about wrt. unhandled exceptions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-NativeAOT-LLVM LLVM generation for Native AOT compilation (including Web Assembly)
Projects
None yet
Development

No branches or pull requests

2 participants