-
Notifications
You must be signed in to change notification settings - Fork 5.3k
JIT: remove anticipated demand for throw helpers #123781
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
base: main
Are you sure you want to change the base?
Conversation
Historically morph would signal to the backend that a throw helper might be needed from a certain block by calling `fgAddCodeRef`. This scheme is less viable now that we have targets like Wasm where null checks must be explicit. Modify the JIT so that throw helper demand and throw helper block/call creation is all done during the stack level setting phase, so there is no need to anticipate if throw helpers will be needed in advance. Also, always minimize the set of common throw helpers needed if not generating debuggable code (where throw helper calls are "in line").
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR moves throw-helper “demand” and throw-helper block/call creation out of morph and into the stack-level setting phase, avoiding the need to anticipate helper usage (notably for targets like Wasm that require explicit null checks).
Changes:
- Removed morph-time “anticipation” of throw helpers by deleting
fgAddCodeRefcalls at morph sites. - Reworked throw-helper infrastructure to create
AddCodeDscentries and blocks on demand during stack-level setting (fgCreateAddCodeDsc,fgCreateThrowHelperBlock, updatedfgFindExcptnTarget). - Ensured throw helper code insertion and unused-helper pruning happens during
StackLevelSetter.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/stacklevelsetter.cpp | Centralizes throw-helper discovery and creation during stack-level setting; updates helper usage tracking/removal. |
| src/coreclr/jit/morph.cpp | Removes morph-time throw-helper “demand” signaling (fgAddCodeRef calls). |
| src/coreclr/jit/lower.cpp | Updates comment to reflect that throw-helper calls may occur (not necessarily known upfront). |
| src/coreclr/jit/flowgraph.cpp | Replaces fgAddCodeRef with descriptor/block creation APIs and adjusts helper-block creation flow. |
| src/coreclr/jit/compiler.h | Updates declarations for new throw-helper APIs and exposes fgRngChkThrowAdded. |
| src/coreclr/jit/compiler.cpp | Removes the dedicated “create throw helpers” phase from the pipeline. |
| src/coreclr/jit/codegencommon.cpp | Updates comment wording around sharing throw helper blocks. |
| bool madeChanges = false; | ||
|
|
||
| comp->compUsesThrowHelper = false; | ||
|
|
||
| if (comp->fgHasAddCodeDscMap()) | ||
| { | ||
| if (comp->opts.OptimizationEnabled()) |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compUsesThrowHelper is reset here and then only set to true when there are used throw-helper blocks in fgAddCodeDscMap. In debuggable code (fgUseThrowHelperBlocks() is false), codegen emits helper calls inline (see CodeGen::genJumpToThrowHlpBlk else-branch), but no AddCodeDsc entries will be created, leaving compUsesThrowHelper false. On FEATURE_FIXED_OUT_ARGS targets, Lowering::FinalizeOutgoingArgSpace uses compUsesThrowHelper to ensure MIN_ARG_AREA_FOR_CALL, so this can lead to missing required outgoing arg space for those inline helper calls. Consider setting compUsesThrowHelper based on the same throw-analysis even when not using throw helper blocks (without creating helper blocks), or otherwise ensuring FinalizeOutgoingArgSpace accounts for inline throw-helper calls in debuggable mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may make sense. Will look into it.
| // However, we do optimize throw-helpers and need to process the blocks for | ||
| // that, but only when optimizing. |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says throw-helper processing is needed "only when optimizing", but the optimization check has been removed and this will now run whenever throwHelperBlocksUsed is true. Update the comment to match the new behavior so readers don't assume this is optimization-only.
| // However, we do optimize throw-helpers and need to process the blocks for | |
| // that, but only when optimizing. | |
| // However, when throw-helper blocks are used we still need to process the | |
| // blocks for throw-helper-related work. |
|
FYI @dotnet/jit-contrib Mainly motivated by Wasm, but reduces coupling overall. There is more cleanup that could be done, since we should no longer need to update ACDs when adding or removing EH regions, but I've left that code in place for now. It will be bypassed at runtime since the ACD map will be empty when phases that modify EH run. A modest number of more or less neutral diffs, some from throw helper blocks being reordered, and some places where we now insert an explicit runtime/src/coreclr/jit/lower.cpp Line 3335 in 062177e
fired when we created throw helper blocks before lower, but now we defer creation until after, and so in more cases now there's just one block (and in the example I look at the throw helper block ended up unused and was removed). And a few diffs where the allocator behaves differently. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
| // descriptor exists. | ||
| // | ||
| Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, BasicBlock* fromBlock) | ||
| Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, BasicBlock* fromBlock, bool createIfNeeded) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit]
Could we either:
- Rename this to
fgGetExcptnTargetor update the comment to indicate we always return a descriptor OR - Move the
createIfNeededlogic to the caller to separate that side effect out?
Historically morph would signal to the backend that a throw helper might be needed from a certain block by calling
fgAddCodeRef. This scheme is less viable now that we have targets like Wasm where null checks must be explicit.Modify the JIT so that throw helper demand and throw helper block/call creation is all done during the stack level setting phase, so there is no need to anticipate if throw helpers will be needed in advance.
Also, always minimize the set of common throw helpers needed if not generating debuggable code (where throw helper calls are "in line").