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: dispatch #2145

Conversation

SingleAccretion
Copy link

@SingleAccretion SingleAccretion commented Jan 1, 2023

This change implements exception handling dispatch for the RyuJit-based LLVM backend.

All funclets will have two LLVM parameters: the shadow stack and the original shadow stack, which is shared among all of the functions belonging to the same logical parent. The shadow stack is used for calls, while the original shadow stack - to access state from the original shadow frame. Once we move to the correct pass order, the frames while dispatch is active will look something like this (note I am using the traditional "stacks grow down" notation here, while physically the shadow stack grows toward higher addresses in our implementation):

; First pass

  ; Shadow frames                            ; Native frames

  [Filtering F0] (with C0 catch)             [Filtering F0]
  [Finally   S0]                                            [Dispatcher]  ^ ; Progression of the native exception
  [Filtering F1]                             [Filtering F1] [F0 frames]   | ; stops once we find a filter which
  [Finally   S1]                                            [Dispatcher]  | ; accepts its managed counterpart.
  [Filtering F2]                             [Filtering F2] [F1 frames]   |
  [Finally   S2]                                            [Dispatcher]  |
  [Throw]                                    [Throw]        [F2 frames]   |
  [Dispatcher(s)]
  [F2 frames] [F1 frames] ... ; Native exception carries the dispatcher's shadow stack

; Second pass                                

  ; Shadow frames                            ; Native frames

  [Filtering F0] <-------------------------| [Filtering F0] <---------------------------------------------|
  [Finally   S0]                           |                [Dispatcher]  ; The handler was found         |
  [Filtering F1]                           |                [S2 frames] [S1 frames] ... [C0 frames]-------|
  [Finally   S1]                           |
  [Filtering F2]                           |
  [Finally   S2]                           |
  [Throw]                                  |
  [Dispatcher]                             |
  [S2 frames] [S1 frames] ... [C0 frames]--| ; Normal "ret" from the dispatcher

So in both passes we will have funclets located above the dispatcher. While it is conceivable to avoid this for the second pass, in the first pass it is not avoidable. The simple solution using the original shadow frame pointer solves both cases nicely. It also solves the issue of reserving space for the exception object: it is passed on the ordinary shadow stack and thus cannot clobber any state from the original shadow frame.

While the above is the high level design of the overall dispatch, the bulk of implementation is concerned with solving a narrower problem: per-frame dispatch. Here a simple approach of encoding the EH tree directly into code with one native handler (landing pad) per one managed clause was chosen, for three reasons:

  1. It is simple.
  2. It transparently handles throws from funclets.
  3. WASM exceptions use similar nesting structure, so it should make the code easier to adapt when moving to them.

Here is the comment from code which describes the specifics of how we lay out the dispatch blocks for one protected region:

// INNER_TRY_REGION:
//   invoke ThrowingCall()
//          unwind to DISPATCH_PAD_INNER;
//
// DISPATCH_PAD_INNER:
//   dispatchData.CppExceptionTuple = landingPadInst
//   dispatchData.DispatcherData = null
//   goto DISPATCH_INNER;
//
// DISPATCH_INNER:
//   dispatchDest = DispatchFunction(FuncletShadowStack(), &dispatchData, &HandlerFunclet, ...)
//                  unwind to DISPATCH_PAD_OUTER
//   if (dispatchDest == 0)
//      goto DISPATCH_OUTER; // For nested regions; top-level ones will use the "switch".
//   goto UNIFIED_DISPATCH;
//
// UNIFIED_DISPATCH:
//   switch (dispatchDest) {
//       case 0: goto RESUME;
//       case 1: goto BB01;
//       case 2: goto BB02;
//       ...
//       default: goto FAIL_FAST;
//   }
//
// RESUME:
//   resume(dispatchData.CppExceptionTuple); // Rethrow the exception and unwind to caller.
//
// FAIL_FAST:
//   FailFast();
//
// What is the possibe set of dispatch destinations (aka why have "UNIFIED_DISPATCH")?
//
// We consider the tree of active protected regions above this one, that are also contained in the same funclet.
// For each region with a (possibly filtered) catch handler, we consider successors of all "catchret" blocks.
// The union of these will form the set of all possible dispatch destinations for the current protected region.
// However, we do not actually emit the "switch" code for each individual region, as it would mean quadratic
// code size growth (number of dispatch destinations X number of protected regions) for deeply nested EH trees.
// Instead, we create one "universal" dispatch block for each funclet, and jump to it from each dispatch. Note
// that thanks to the step blocks inserted by "impImportLeave", we do not need to consider cases where a jump
// from a funclet to its caller would be required.

Notable difference from the IL backend: since we're generating one dispatcher for one clause (note we count mutually protecting clauses as one clause), we employ dispatchers specialized for the clause type. This is expected to be a somewhat significant saving, both in code size and dispatch time, since the vast majority (95%+) of clauses are either faults/finallys or single catches. Here are the statistics I gathered using SPMI:

benchmarks.run

Total clauses       : 2514
Finally             : 281    11.2%
Fault               : 1413   56.2%
Single catch        : 724    28.8%
Single filter       : 49      1.9%
Mutually protecting : 47      1.9%
Among them, catches : 108    86.4%
Among them, filters : 17     13.6%

libraries.pmi

Total clauses       : 19568
Finally             : 1937    9.9%
Fault               : 11399  58.3%
Single catch        : 5373   27.5%
Single filter       : 349     1.8%
Mutually protecting : 510     2.6%
Among them, catches : 1240   95.0%
Among them, filters : 65      5.0%

The prevalence of faults here, which are not expressible in C#, is due to finally cloning, which duplicates the code inside the finally so we save on the call overhead for the common path and gain additional optimization opportunities. We may choose to disable finally cloning as a size-increasing optimization in the future for WASM, but for now it is, I suppose, a good demonstration of taking advantage of existing RyuJit optimizations.

It was also chosen that we emit a fail fast in case a catch handler returns an unexpected dispatch location. This is technically unreachable code; the thinking was to leave it there while the EH implementation matures and delete once we are confident in its correctness.

@SingleAccretion SingleAccretion force-pushed the Exception-Handling-Dispatch-Upstream branch 2 times, most recently from aa181f9 to 29d383f Compare January 1, 2023 17:06
@SingleAccretion SingleAccretion marked this pull request as ready for review January 1, 2023 17:07
@SingleAccretion
Copy link
Author

@dotnet/nativeaot-llvm

Please feel free to ask any and all questions about the implementation / design. This is a fairly large and fundamental change.

@SingleAccretion SingleAccretion force-pushed the Exception-Handling-Dispatch-Upstream branch from 29d383f to 36a3304 Compare January 1, 2023 19:11
@SingleAccretion SingleAccretion marked this pull request as draft January 1, 2023 20:52
@SingleAccretion SingleAccretion force-pushed the Exception-Handling-Dispatch-Upstream branch from 61008c5 to 36a3304 Compare January 2, 2023 19:41
@SingleAccretion
Copy link
Author

There is a silent bad codegen problem here, as evidenced by CI. Not the first one (a lot of the fixes present here that are nominally unrelated to EH also manifested themselves as silent bad codegen), but the hardest one to debug so far because it does not reproduce locally. Probably will take a while to sort it out...

@SingleAccretion SingleAccretion force-pushed the Exception-Handling-Dispatch-Upstream branch 7 times, most recently from e4d809c to 56da5dc Compare January 4, 2023 19:33
@yowl
Copy link
Contributor

yowl commented Jan 5, 2023

The tests that are removed, is that because the IL backend is no longer being used for them, and so the cases are not applicable ?

@SingleAccretion
Copy link
Author

All of the commits after and including Testing... are temporary, for debugging the elusive bad codegen issue, and will be reverted.

@yowl
Copy link
Contributor

yowl commented Jan 5, 2023

Is it always in TestFilterNested ?

@SingleAccretion
Copy link
Author

Yes, here's the call stack:

JS:     at invoke_ii (D:\a\_work\1\s\artifacts\tests\coreclr\Browser.wasm.Debug\nativeaot\SmokeTests\HelloWasm\HelloWasm\native\HelloWasm.js:7279:36)
IL:     at S_P_CoreLib_Internal_Reflection_Extensions_NonPortable_CustomAttributeSearcher_1__GetMatchingCustomAttributesIterator_d__2<System___Canon>__MoveNext (<anonymous>:wasm-function[5360]:0x4e7c34)
IL:     at S_P_CoreLib_System_Attribute__OneOrNull (<anonymous>:wasm-function[4943]:0x462cff)
RyuJit: at S_P_CoreLib_System_Attribute__GetCustomAttribute (<anonymous>:wasm-function[11794]:0xb102e4)
RyuJit: at S_P_CoreLib_System_Reflection_CustomAttributeExtensions__GetCustomAttribute (<anonymous>:wasm-function[11829]:0xb151d9)
IL:     at S_P_CoreLib_System_Reflection_CustomAttributeExtensions__GetCustomAttribute_3<System___Canon> (<anonymous>:wasm-function[1650]:0x12be05)
IL:     at S_P_CoreLib_System_Resources_ManifestBasedResourceGroveler__GetNeutralResourcesLanguage (<anonymous>:wasm-function[6018]:0x5e93b1)
RyuJit: at S_P_CoreLib_System_Resources_ResourceManager__CommonAssemblyInit (<anonymous>:wasm-function[10795]:0xaa010c)
RyuJit: at S_P_CoreLib_System_Resources_ResourceManager___ctor_3 (<anonymous>:wasm-function[12358]:0xb3caed)
RyuJit: at S_P_CoreLib_System_SR__get_ResourceManager (<anonymous>:wasm-function[17090]:0xd46ee4)
JS:     at invoke_vii (D:\a\_work\1\s\artifacts\tests\coreclr\Browser.wasm.Debug\nativeaot\SmokeTests\HelloWasm\HelloWasm\native\HelloWasm.js:7257:29)
IL:     at S_P_CoreLib_System_SR__InternalGetResourceString (<anonymous>:wasm-function[6380]:0x66b883)
JS:     at invoke_vii (D:\a\_work\1\s\artifacts\tests\coreclr\Browser.wasm.Debug\nativeaot\SmokeTests\HelloWasm\HelloWasm\native\HelloWasm.js:7257:29)
RyuJit: at S_P_CoreLib_System_SR__GetResourceString (<anonymous>:wasm-function[8913]:0x99225d)
RyuJit: at S_P_CoreLib_System_SR__get_Arg_ArgumentException (<anonymous>:wasm-function[10960]:0xab0c0c)
RyuJit: at S_P_CoreLib_System_ArgumentException___ctor (<anonymous>:wasm-function[8975]:0x995d7a)
RyuJit: at HelloWasm_Program__TestFilterNested (<anonymous>:wasm-function[12648]:0xb5d517)
RyuJit: at HelloWasm_Program__TestTryCatch (<anonymous>:wasm-function[12855]:0xb75599)
RyuJit: at HelloWasm_Program__Main (<anonymous>:wasm-function[3668]:0x301ef0)
RyuJit: at HelloWasm__Module___MainMethodWrapper (<anonymous>:wasm-function[13246]:0xb9344e)

As one of the commits notes, prime suspect is shadow stack corruption.

@yowl
Copy link
Contributor

yowl commented Jan 5, 2023

Hmm, I wonder if its the same stack corruption with the IL second pass handler. I got that better, but wasn't sure it was totally ok in https://github.com/yowl/runtimelab/tree/uo-loop-test . Nested handlers can corrupt the shadow stack on calls made in the handlers.

Once the shadow stack corrupts its really hard, I feel the pain here. If it doesn't repro locally, somethings you could try to make it repro are running without memory growth and playing with the fixed memory size.

@SingleAccretion
Copy link
Author

SingleAccretion commented Jan 5, 2023

Hmm, I wonder if its the same stack corruption with the IL second pass handler. I got that better, but wasn't sure it was totally ok in https://github.com/yowl/runtimelab/tree/uo-loop-test . Nested handlers can corrupt the shadow stack on calls made in the handlers.

Yes, that is the first-order suspicion... It would be helpful is understanding how does this corruption happen, i. e. where do the shadow stack pointers start to alias.

Once the shadow stack corrupts its really hard, I feel the pain here. If it doesn't repro locally, somethings you could try to make it repro are running without memory growth and playing with the fixed memory size.

Thank you for the advice!

@yowl
Copy link
Contributor

yowl commented Jan 5, 2023

From memory and a glance at the changes in that branch, if you have a stack (top down)

InvokeSecondPassWasm
FinallyFunclet1
.
InvokeSecondPassWasm
FinallyFunclet2
MethodA

Then (from memory), the call to MethodA passes the wrong shadow stack - it doesn't cater for locals used in FinallyFunclet1, I think that's right and is why in the uo-loop-test branch I added another parameter to InvokeSecondPassWasm.

Plus the other change in that branch, is the removal a shadow stack block for every reverse pinvoke, although I dont remember if that was just a performance benefit, or there was a logical problem with the way it was.

You could take the changes to the IL side and just try it I suppose. If it doesn't help then it can be discounted. Let me know if you want me to do that to this branch.

@SingleAccretion
Copy link
Author

Let me know if you want me to do that to this branch.

That would be greatly appreciated :).

@yowl
Copy link
Contributor

yowl commented Jan 5, 2023

That would be greatly appreciated :).

in #2149. I realised while merging that there is third change here that may be more obvious. In a couple of places I added an RhpCheckedAssignRef, where previously there was just an LLVM store. If this goes anywhere we could see which is helping. Throwing from finally blocks is not that common so that idea is maybe not a strong theory.

@SingleAccretion SingleAccretion marked this pull request as ready for review January 11, 2023 13:45
@SingleAccretion
Copy link
Author

SingleAccretion commented Jan 11, 2023

Alright, while the GC hole is still to be resolved, I believe this change by itself is (finally) ready for review.

Edit: but we'll have to wait until the hole is fixed before it can be merged.

@yowl
Copy link
Contributor

yowl commented Jan 11, 2023

Another big step forward! Do you know if any of the exception based tests are now compiled via the RyuJit backend, I assume some are?

@SingleAccretion
Copy link
Author

Do you know if any of the exception based tests are now compiled via the RyuJit backend, I assume some are?

A good number of HelloWasm EH tests are compiled with RyuJit now:

TestThrowIfNull: WASM EH: exception thrown
Catch not called when no exception test: Ok.
Catch called when exception thrown test: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002676]
caught
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Catch called when exception thrown from call: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002677]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Catch called for exception type and order: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: mutually protecting catches, table at [0x00005F64]
WASM EH/RyuJit [SF: 0x03804394] [1]: Clause: catch, class [EETypePointer:0x00156EA4(System.ArgumentException)]
WASM EH/RyuJit [SF: 0x03804394] [1]: Clause: catch, class [EETypePointer:0x0015839C(System.NullReferenceException)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002700]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Try/Finally calls finally when exception thrown test: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x038043A8] [1]: Handling: catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x038043A8] [2]: Calling catch funclet at [0x00002642]
warning: Source map information is not available, emscripten_log with EM_LOG_C_STACK will be ignored. Build with "--pre-js $EMSCRIPTEN/src/emscripten-source-map.min.js" linker flag to add source map loading to code.
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x038043A8] [2]: Handling: fault
WASM EH/RyuJit [SF: 0x038043A8] [2]: Calling fault funclet at [0x00002643]
WASM EH/RyuJit [SF: 0x038043A8] [2]: Funclet returned: success
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002678]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Try/Finally calls finally once when exception thrown and caught test: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804398] [1]: Handling: catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x03804398] [2]: Calling catch funclet at [0x00002640]
WASM EH/RyuJit [SF: 0x03804398] [2]: Funclet returned: 00000001
Ok.
Inner try finally called before outer catch: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [2]: Handling: fault
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling fault funclet at [0x0000267A]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: success
WASM EH/RyuJit [SF: 0x03804394] [2]: Handling (active): fault
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling fault funclet at [0x0000267B]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: success
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling (active): catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x0000267C]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Test invoke when last instruction in if block: Ok.
Throw exception in catch: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x0000267E]
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x0000267F]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
TestExceptionInGvmCall: WASM EH: exception thrown
WASM EH/IL [SF: 0x0380439C] [1]: Pass start; at: 0000000C, exception: [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/IL [SF: 0x0380439C] [1]: EH#00000000: try [00000001..0000001A) handled by [0x00001AB9], class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/IL [SF: 0x0380439C] [1]: Pass end; handler found at [0x00001AB9]
Ok.
Catch handler can access generic context: WASM EH: exception thrown
WASM EH/IL [SF: 0x0380439C] [1]: Pass start; at: 00000001, exception: [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/IL [SF: 0x0380439C] [1]: EH#00000000: try [00000001..00000008) handled by [0x00001ACA], class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/IL [SF: 0x0380439C] [1]: Pass end; handler found at [0x00001ACA]
Ok.
Filter funclet can access generic context: WASM EH: exception thrown
WASM EH/IL [SF: 0x0380439C] [1]: Pass start; at: 00000001, exception: [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/IL [SF: 0x0380439C] [1]: EH#00000000: try [00000001..00000008) handled by [0x00001ACB], filter at [0x00001ACC]
WASM EH/IL [SF: 0x0380439C] [1]: Calling filter funclet at [0x00001ACC]
WASM EH/IL [SF: 0x0380439C] [1]: Funclet returned: true
WASM EH/IL [SF: 0x0380439C] [1]: Pass end; handler found at [0x00001ACB]
Ok.
TestFilter: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: filtered catch
WASM EH/RyuJit [SF: 0x03804394] [1]: Calling filter funclet at [0x00002681]
WASM EH/RyuJit [SF: 0x03804394] [1]: Funclet returned: true
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002680]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
TestFilterNested: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: filtered catch
WASM EH/RyuJit [SF: 0x03804394] [1]: Calling filter funclet at [0x00002684]
WASM EH/RyuJit [SF: 0x03804394] [1]: Funclet returned: false
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling (active): catch, class [EETypePointer:0x00156EA4(System.ArgumentException)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002685]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000002
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: filtered catch
WASM EH/RyuJit [SF: 0x03804394] [1]: Calling filter funclet at [0x00002684]
WASM EH/RyuJit [SF: 0x03804394] [1]: Funclet returned: false
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling (active): catch, class [EETypePointer:0x00156EA4(System.ArgumentException)]
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling (active): filtered catch
WASM EH/RyuJit [SF: 0x03804394] [1]: Calling filter funclet at [0x00002687]
WASM EH/RyuJit [SF: 0x03804394] [1]: Funclet returned: true
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002686]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: filtered catch
WASM EH/RyuJit [SF: 0x03804394] [1]: Calling filter funclet at [0x00002684]
WASM EH/RyuJit [SF: 0x03804394] [1]: Funclet returned: true
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002683]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000003
In middle catchRunning outer filterIn outer catchRunning inner filterIn inner catch
Ok.
Test catch and throw different exception: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002688]
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x00002689]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.
Test rethrow: WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156750(System.Object)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x0000268A]
WASM EH: exception thrown
WASM EH/RyuJit [SF: 0x03804394] [1]: Handling: catch, class [EETypePointer:0x00156A9C(System.Exception)]
WASM EH/RyuJit [SF: 0x03804394] [2]: Calling catch funclet at [0x0000268B]
WASM EH/RyuJit [SF: 0x03804394] [2]: Funclet returned: 00000001
Ok.

The Exceptions.cs test doesn't use RyuJit yet (probably because it creates a delegate).

@yowl
Copy link
Contributor

yowl commented Jan 11, 2023

Looks impressive, do you expect https://stackoverflow.com/questions/57544142/in-c-sharp-try-finally-how-to-catch-the-original-exception/57544409#57544409 to pass with this (assuming there was no other reason why it fell back to the IL backend) ?

@SingleAccretion
Copy link
Author

SingleAccretion commented Jan 11, 2023

No, not currently. We are still doing per-frame dispatch here because attempting to not do this will run into problem with the IL backend catching in-progress exceptions. But it is certainly the case this design should be capable of doing the whole first pass before the second pass (PR's description has a schematic for how it will work).

For full inter-frame dispatch, the currently missing piece is shadow stack from the throw frame, which is expected to be carried by the native exception. The rest of the system should then "just work".

@yowl
Copy link
Contributor

yowl commented Jan 13, 2023

Some minor comments. I think that the fact the tests are passing, is as good a proof (and clearly better) than me eyeballing the code that this is awesome!

src/coreclr/jit/llvmcodegen.cpp Outdated Show resolved Hide resolved
src/coreclr/jit/llvmcodegen.cpp Outdated Show resolved Hide resolved
src/coreclr/jit/llvmcodegen.cpp Outdated Show resolved Hide resolved
@jkotas
Copy link
Member

jkotas commented Jan 17, 2023

@yowl Is this good to merge?

@yowl
Copy link
Contributor

yowl commented Jan 17, 2023

Yes absolutely

@jkotas jkotas merged commit 5580f4b into dotnet:feature/NativeAOT-LLVM Jan 17, 2023
@jkotas
Copy link
Member

jkotas commented Jan 17, 2023

@SingleAccretion Thank you! This is great work

@SingleAccretion SingleAccretion deleted the Exception-Handling-Dispatch-Upstream branch January 17, 2023 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants