Eliminate return-accumulator temps in EH/lock by sinking returns#613
Merged
Conversation
fdadf17 to
4ecb574
Compare
When a method's result is computed inside a try, catch, finally-guarded, or
lock body and the ret sits after the region closes, the importer reconstructs
a synthetic accumulator local: try { V = x + 1; } finally { ... } return V;
The original source was try { return x + 1; } finally { ... }.
bare, which recompiles opcode-exact for the simple shapes its DA walk can
prove. It deliberately bails on leave and lock, so the two-return try/finally
(TryFinallyTwoReturns) and the lock body (ClassicLock) keep diverging, and even
the shapes it fixes still render the unnatural temp form rather than the source.
ReturnSinkingPass rewrites the accumulator back to source form: it sinks each
feeding store into a return in place — adjacent StoreLocal V; return V; pairs,
and the terminal fall-through stores of a try-body, catch arm, lock body, or
if/else arm a trailing return V follows. It is sound by construction: it acts
only on a local that is never address-taken and whose every load is a return's
operand, and applies a candidate only when every store and every load is
consumed by the plan (all-or-nothing). Switch is excluded: a switch expression
is lowered through its own result accumulator, so per-arm returns would diverge.
CfgSampleClass compile-back: exact 93 -> 95 (TryFinallyTwoReturns, ClassicLock
now exact); the EH/lock return-accumulators render try { return e; }. Compile-
check arbiter unchanged across 35k+ BCL methods (no new malformed or binding
errors). Gate updated: the five EH/lock shapes pinned exact; KnownDiffs trimmed
to the residual docket.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cross-reference the printer's definite-assignment dead-init drop (#611) as the conservative fallback for accumulators this pass leaves standing, so the two-layer division of labor is discoverable from the code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4ecb574 to
b25f9b0
Compare
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
When a method's result is computed inside a
try,catch, finally-guarded, orlockbody and theretsits after the region closes, the importer reconstructs a synthetic accumulator local:#611 (the definite-assignment dead-init drop) keeps that temp but declares it bare, which recompiles opcode-exact for the simple shapes its DA walk can prove. It deliberately bails on
leaveandlock, so the two-return try/finally (TryFinallyTwoReturns) and the lock body (ClassicLock) keep diverging — and even the shapes it fixes still render the unnatural temp form rather than the source.ReturnSinkingPassrewrites the accumulator back to source form: it sinks each feeding store into areturnin place — adjacentStoreLocal V; return V;pairs, and the terminal fall-through stores of a try body, catch arm, lock body, or if/else arm that a trailingreturn Vfollows.Soundness
All-or-nothing by construction: the pass acts only on a local that is never address-taken and whose every load is a
Returnoperand, and applies a candidate only when every store and every load is consumed by the plan.switchis excluded — a switch expression is lowered through its own result accumulator, so per-arm returns would diverge.Results
TryFinallyTwoReturns,ClassicLocknow exact); all EH/lock return-accumulators render the naturaltry { return e; }.KnownDiffstrimmed to the residual docket.Relationship to #611
Complementary, not a replacement. #611 is the conservative dead-init drop; this restructures the temp away entirely, producing more natural C# and closing the
leave/lockcases #611 leaves behind.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com