Skip to content

Conversation

@AndyAyersMS
Copy link
Member

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").

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").
Copilot AI review requested due to automatic review settings January 29, 2026 23:01
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jan 29, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a 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 fgAddCodeRef calls at morph sites.
  • Reworked throw-helper infrastructure to create AddCodeDsc entries and blocks on demand during stack-level setting (fgCreateAddCodeDsc, fgCreateThrowHelperBlock, updated fgFindExcptnTarget).
  • 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.

Comment on lines 51 to 57
bool madeChanges = false;

comp->compUsesThrowHelper = false;

if (comp->fgHasAddCodeDscMap())
{
if (comp->opts.OptimizationEnabled())
Copy link

Copilot AI Jan 29, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

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.

Comment on lines 109 to 110
// However, we do optimize throw-helpers and need to process the blocks for
// that, but only when optimizing.
Copy link

Copilot AI Jan 29, 2026

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.

Suggested change
// 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.

Copilot uses AI. Check for mistakes.
@AndyAyersMS
Copy link
Member Author

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 nop before a NOGC region where we didn't before, because this check in lower

if ((comp->fgBBcount == 1) && !comp->compCurBB->HasFlag(BBF_GC_SAFE_POINT))

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.

Copy link
Contributor

Copilot AI left a 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit]
Could we either:

  1. Rename this to fgGetExcptnTarget or update the comment to indicate we always return a descriptor OR
  2. Move the createIfNeeded logic to the caller to separate that side effect out?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants