Skip to content

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

Open
@SingleAccretion

Description

@SingleAccretion

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-NativeAOT-LLVMLLVM generation for Native AOT compilation (including Web Assembly)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions