JIT: Decouple "DoNotEnregister" from liveness#127932
Merged
Merged
Conversation
jakobbotsch
commented
May 7, 2026
jakobbotsch
commented
May 7, 2026
jakobbotsch
commented
May 7, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refactors CoreCLR JIT local liveness/EH handling so that EH “live-in/out-of-handler” state is recomputed each liveness run and lvDoNotEnregister (DNER) is no longer set by liveness, instead being set by regalloc / lowering.
Changes:
- Recompute and encapsulate EH live-in/out state via
LclVarDsc::IsLiveInOutOfHandler()and reset EH/must-init flags each liveness run. - Move DNER-setting decisions out of liveness into regalloc (
checkForDNER) and lowering (lvSetEHVarsDoNotEnreg), and update heuristics to consult EH-liveness in addition to DNER. - Add
Compiler::IsEHVarARegCandidateto centralize “EH write-thru candidate” logic and use it in multiple optimizations.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/sideeffects.h | Use IsLiveInOutOfHandler() for EH-related aliasing decisions. |
| src/coreclr/jit/regallocwasm.h | Declare checkForDNER for Wasm regalloc. |
| src/coreclr/jit/regallocwasm.cpp | Call checkForDNER and use encapsulated EH-live query. |
| src/coreclr/jit/regalloc.cpp | Add checkForDNER and remove DNER-setting from isRegCandidate. |
| src/coreclr/jit/optimizer.cpp | Switch EH-live checks to IsLiveInOutOfHandler(). |
| src/coreclr/jit/morph.cpp | Stop copying EH-live flag during implicit-byref arg retyping. |
| src/coreclr/jit/lsra.h | Declare checkForDNER for LSRA. |
| src/coreclr/jit/lsra.cpp | Call checkForDNER and use IsLiveInOutOfHandler() in LSRA logic. |
| src/coreclr/jit/lower.cpp | Add lvSetEHVarsDoNotEnreg() call to support containment heuristics in lowering. |
| src/coreclr/jit/liveness.cpp | Reset lvMustInit and EH-live state each liveness run; set EH-live without setting DNER. |
| src/coreclr/jit/lclvars.cpp | Add lvSetEHVarsDoNotEnreg; remove lvaSetVarLiveInOutOfHandler; adjust DNER behavior. |
| src/coreclr/jit/jiteh.cpp | Track when EH clauses are removed post-liveness. |
| src/coreclr/jit/inductionvariableopts.cpp | Update IV widening heuristics to consider EH-liveness independently of DNER. |
| src/coreclr/jit/gschecks.cpp | Remove direct clearing of EH-live flag on shadowing locals. |
| src/coreclr/jit/gentree.cpp | Add Compiler::IsEHVarARegCandidate helper; update EH-live checks. |
| src/coreclr/jit/earlyprop.cpp | Update EH-live checks to IsLiveInOutOfHandler(). |
| src/coreclr/jit/copyprop.cpp | Adjust “profitability” gating to compare expected enregistration status incl. EH-liveness. |
| src/coreclr/jit/compiler.h | Encapsulate EH-live flag, add helper APIs, and add optRemovedEHClausesAfterLiveness. |
| src/coreclr/jit/compiler.cpp | Update enregister stats accounting to include PinningRef unconditionally. |
| src/coreclr/jit/codegenwasm.cpp | Update EH-live checks to IsLiveInOutOfHandler(). |
| src/coreclr/jit/codegencommon.cpp | Update EH-live checks to IsLiveInOutOfHandler(). |
Comments suppressed due to low confidence (1)
src/coreclr/jit/compiler.h:5734
optRemovedEHClausesAfterLivenessis currently only set/reset (in fgRemoveEHTableEntry and liveness) but not consumed anywhere else. If it’s meant to guard heuristics that rely onIsLiveInOutOfHandler()(e.g., avoid setting DNER based on stale EH liveness after EH removal), consider wiring it into the relevant decision points; otherwise it should be removed to avoid accumulating dead state.
bool fgBBVarSetsInited = false;
// Track how many artificial ref counts we've added to fgEntryBB (for OSR)
Member
Author
|
Sorry for the requests, accidentally rebased across a merge here... |
d3d7400 to
15f6d4e
Compare
Member
Author
|
/azp run runtime-coreclr jitstress, runtime-coreclr libraries-jitstress |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Member
Author
|
cc @dotnet/jit-contrib PTAL @AndyAyersMS This will be more important for future work once we start optimizing out IR that allows us to remove the EH we inserted for async. |
AndyAyersMS
approved these changes
May 16, 2026
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.
Liveness was marking locals as DNER in various places, including when it found locals to be live into EH handlers.
DNER is sticky and cannot be unset, and we call liveness multiple times, so that made things hard to reason about and led to unnecessary DNER.
This showed up for runtime async functions. Even when we were able to optimize out the try-fault clauses inserted around the body we would often still end up with
lvaAsyncContinuationArgunnecessarily DNER'd because we had already marked it as DNER during a previous liveness pass, when it was live into the fault handler.This PR:
lvLiveInOutOfHandleron all locals every time we run livenesslvLiveInOutOfHandlerintoLclVarDsc::IsLiveInOutOfHandler()and adds anassert(lvTracked)to this function, so it cannot be misusedIsLiveInOutOfHandler()in addition toDNERDiffs aren't all that great, but I think this is a good clean up on its own.