Lift constructor field initializers ahead of the base call to the field declaration#614
Merged
Merged
Conversation
…ld declaration C# emits field initializers before the base constructor call, so a `this.field = value` store the IL places before the chain call is a field initializer — not a body assignment. The printer rendered it as a body statement, which recompiles to AFTER the base call: an opcode-order diff (the two remaining `.ctor` compile-back docket items, `CfgSampleClass::.ctor` spelling `_shadowed = 1;` and `LockFixtureSamples::.ctor` spelling `_root = new object();`). Generalize the constructor-prologue detection: scan the entry block for the base/this `.ctor` chain call; if every statement before it is a qualifying field-initializer store, lift each to the field declaration (new `DecompilerResult.FieldInitializers`) and let the existing chain lift fire on the call. The compile-back harness threads the initializers through to the target type's field declarations. Soundness: a store only qualifies when its receiver is the under-construction `this` (ldarg.0) AND its value references no place — no `this`, parameter, local, or stack-slot load (`ReferencesPlace`). That is exactly C#'s field-initializer legality rule (initializers cannot read the instance or constructor parameters), so a lifted store is always legal where it lands. Detection requires the whole pre-chain prologue to be such stores, matching the compiler's prologue shape exactly; anything else bails and keeps the current body rendering. Constants, `new T()`, and static reads qualify; a field init that reads a parameter does not and stays in the body. Compile-back fixture exact 151 -> 153 (+2), docket 13 -> 11 (-2), recompile-fail unchanged (527; System.Linq unchanged 395). Decompiler tests 432 -> 434. Pinned `CfgSampleClass::.ctor` exact in the gate. Found via #604. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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.
What
C# emits field initializers before the base constructor call, so a
this.field = valuestore the IL places before the chain call is a field initializer, not a body assignment. The printer rendered it as a body statement, which recompiles to after the base call — an opcode-order diff. These were the two remaining.ctorcompile-back docket items:CfgSampleClass::.ctor(int _shadowed = 1;)LockFixtureSamples::.ctor(readonly object _root = new();)How
Generalize the constructor-prologue detection in
CSharpPrinter: scan the entry block for the base/this.ctorchain call; if every statement before it is a qualifying field-initializer store, lift each to the field declaration (newDecompilerResult.FieldInitializers) and let the existing chain lift fire on the call. The compile-back harness threads the initializers through to the target type's field declarations.Soundness
A store only qualifies when its receiver is the under-construction
this(ldarg.0) and its value references no place — nothis, parameter, local, or stack-slot load (ReferencesPlace). That is exactly C#'s field-initializer legality rule (initializers cannot read the instance or constructor parameters), so a lifted store is always legal where it lands. Detection requires the whole pre-chain prologue to be such stores, matching the compiler's prologue shape exactly; anything else bails and keeps the current body rendering. Constants,new T(), and static reads qualify; a field init that reads a parameter does not and stays in the body.Numbers
CfgSampleClass::.ctoropcode-exact in the compile-back gate.Found via the #604
--compile-backoracle. Companion to #612.