Repository navigation
Nest a try's pads in the catch funclet around it (Windows) - #360
Merged
Merged
Conversation
A `using` inside a catch clause did not work on Windows:
try { throw 1; } catch (o) {
try { using r = new Res(); ... } catch (e: number) { ... }
}
With no try around the catch, Win32ExceptionPass crashed the compiler
(the JIT at every level). With one, ahead-of-time compilation failed
LLVM's verifier ("Unwind edges out of a catch must have the same unwind
dest as the parent catchswitch").
Win32ExceptionPass scans a function's instructions in order with one
current region, and makes every pad `within none`. A try inside a catch
got by because a throw in it ended the enclosing catch first, so the
nested try ran outside the catch's funclet. A `using` defeats that: its
first call is inside the catch, before any throw. The scan then took the
using's cleanup pad for the end of the catch, left the catch with no
end at all (the crash), and gave the cleanup pad an edge from inside the
catch funclet and one from outside it (the verifier error).
Pads now nest:
- TryOpLowering knows which catch clause a try sits in. On Windows it
tags each landing pad with its catch pad's id and the id of the catch
it is nested in (the nearest try around it that holds it in its catch
clause, through any try bodies between). LandingPadOpLowering emits the
tag as a call to ts.internal.eh_pad right after the landingpad.
- Win32ExceptionPass reads and removes the tag, and scans with a stack
of open regions: a nested region's code sits in the middle of the
enclosing catch's, which carries on when the nested one ends; an end
marker right after a nested catch ends the enclosing one. Nested pads
are created within the enclosing catchpad, and a catchswitch unwinds
to the first invoke that leaves its region, never into its own
descendants.
- A throw inside a try nested in a catch no longer ends that catch on
Windows: the nested try's pad takes it, inside the funclet. The
Itanium path, which has no funclets, keeps ending it as before.
00try_mismatch_in_catch_clause_using.ts now runs on every platform
(compile, jit, and the rc/none corpus), with the no-try-around case
that crashed the compiler added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A
usinginside a catch clause did not work on Windows:mainWin32ExceptionPasscrashed the compiler (the JIT at every level)Win32ExceptionPassscanned a function's instructions in order with one current region, and made every padwithin none. A try inside a catch got by because a throw in it ended the enclosing catch first, so the nested try ran outside the catch's funclet. Ausingbreaks that, because its first call is inside the catch, before any throw. The scan then took the using's cleanup pad for the end of the catch. That left the catch with no end at all (the crash), and gave the cleanup pad one edge from inside the catch funclet and one from outside it (the verifier error).Pads now nest:
TryOpLowering,LandingPadOpLowering).TryOpLoweringknows which catch clause a try sits in. On Windows it tags each landing pad with its catch pad's id and the id of the catch it is nested in: the nearest try around it that holds it in its catch clause, looking through any try bodies in between.LandingPadOpLoweringemits the tag as a call tots.internal.eh_padright after the landingpad.Win32ExceptionPass). The pass reads and removes the tag, and scans with a stack of open regions. A nested region's code sits in the middle of the enclosing catch's code, and the enclosing catch carries on when the nested one ends. An end marker right after a nested catch ends the enclosing one. Nested pads are created within the enclosing catchpad. A catchswitch unwinds to the first invoke that leaves its region, never into its own descendants.The pass could not work out the nesting from the IR alone, so the tag is required. A pad reached from a catch is either nested in it or is that catch's exit, and the IR looks the same in both cases. The old "a throw in a nested try ends the catch first" shortcut is now Linux-only.
This is the Windows gap listed as not fixed in #359.
Verification
00try_mismatch_in_catch_clause_using.tsnow runs on every platform (compile, jit, and the rc/none corpus). Before this PR it was Linux-only. The no-try-around case that crashed the compiler is added to it.🤖 Generated with Claude Code