Repository navigation
Project: decided storage — burn down the residual-bound stack-slot population #9371
Description
Project: decided storage — burn down the residual-bound stack-slot population to zero or a named floor
Companion to #2095 (the thin writer). #9247 moved the printer's residual slot policy into ResidualSlotBindingPass, so every stack-slot web that SlotMaterializationPass declines is now a counted, provenance-carrying pipeline fact instead of print-time policy. That made the remaining population visible; this project retires it. Design owner: docs/design/value-typed-emission.md (Instance 2 — stack-slot materialization and typing; Residual storage binding). The pass itself is frozen and shrink-only: every slice here lands in materialization or a testimony owner and is measured by the census moving a group to zero.
Why a separate program from #2095
- Project: the thin writer — make CSharpPrinter a total function of a decided, fully-typed IR #2095 (Program A) removes decisions from
CSharpPrinterand must be output-neutral: its gates are Render A/B valid→invalid 0 and noFullmethod rendering worse. - This program (Program B) changes output on purpose by typing more storage: its gates are the
--residual-binding-censusdelta plus changed-method fidelity. - They touch disjoint code (
Ir/CSharpPrinter*vsPasses/*), so one PR per program can be in flight at once. The shared contract is the pass→printer surface (Coercenodes,ResidualSlotBindings, the local declaration plan); neither program widens it without the other's design section changing.
Baseline (main 5a00671a9, 14 assemblies / 89,065 methods, --residual-binding-census)
329 bound webs / 396 locals / 213 methods; 52 split webs; 0 late-decidable; 12 untyped visible failures; 1 web in a nested body. (The opening figures at 8076ab86f were 333 / 402 / 214 / 54; #9358, merged after #9247, removed two ConflictingTypeTestimony split webs and two IncompleteCopyComponent webs — see the re-measure comment.) --slot-residual-census post-materialization residual slots: 358 / 232 methods.
| Webs | Kind | Veto flags | Locals |
|---|---|---|---|
| 114 | Unified | OutsideCoercionDomain | 114 |
| 62 | Unified | OutsideCoercionDomain, IncompleteCopyComponent | 62 |
| 58 | Unified | MissingLoad | 58 |
| 21 | Unified | IncompleteCopyComponent | 21 |
| 21 | Split | ConflictingTypeTestimony | 52 |
| 18 | Split | OutsideCoercionDomain, UnrenderableStoreType | 36 |
| 12 | Unified | UnderivableTypeTestimony | 12 |
| 5 | Unified | MissingLoad, IncompleteCopyComponent | 5 |
| 5 | Split | UnrenderableStoreType | 10 |
| 3 | Unified | BooleanSinkIdentityRecovery | 3 |
| 2 | Split | ConflictingTypeTestimony, IncompleteCopyComponent | 7 |
| 2 | Unified | ConflictingTypeTestimony | 2 |
| 2 | Split | BooleanSinkIdentityRecovery | 4 |
| 2 | Split | BooleanSinkIdentityRecovery, IncompleteCopyComponent | 4 |
| 2 | Split | OutsideCoercionDomain, UnrenderableStoreType, IncompleteCopyComponent | 6 |
After #9390 (96d12bcb1): 317 bound webs / 383 locals / 201 methods; 51 split; 0 late-decidable; 4 untyped visible failures; 0 in nested bodies. Groups that moved: UnderivableTypeTestimony 12 → 0; OutsideCoercionDomain 114 → 115; MissingLoad 58 → 57; OutsideCoercionDomain+UnrenderableStoreType split 18 → 17; BooleanSinkIdentityRecovery 3 → 4 (integer loads passed to bool parameters now count as Boolean sinks).
After #9398 (2e9d47e5d): 204 bound webs / 270 locals / 143 methods; 51 split; 0 late-decidable; 4 untyped visible failures; 0 in nested bodies. Groups that moved: Unified OutsideCoercionDomain 115 → 49; Unified OutsideCoercionDomain + IncompleteCopyComponent 62 → 15.
Reproduce: dotnet run --project tools/DecompilerHarness -c Release -- <14 assemblies from eng/prepare-decompiler-corpus.sh> --residual-binding-census.
Docket (one group per slice; each slice names its real corpus witness, moves its group to zero or an explained floor, and runs changed-method fidelity)
- Untyped split pieces fail visibly at residual storage binding: 21 corpus methods with a reference
??join spilled to a stack slot #9248 — untyped split pieces failing visibly: 12 → 4 in Let argument and comparison sinks testify for untyped slot loads #9390 (merged96d12bcb1): argument parameter types (after MethodSpec substitution, closed) and width-and-signedness-exact comparison siblings testify for untyped loads; the object-carrier coalesce witness types two-reference??carriers. The 4 that remain are Array literal raise blocked by a residual slot-copy chain and a hoisted conditional element (NewtonsoftDynamicProxyMetaObject<T>.GetArgArray) #9249's two copy chains, one slot reuse (GetTypeInfoForNode, ConflictingTypeTestimony), andSubstituteNoPiaLocalType(declined sbyte-backed enum sibling, below). - OutsideCoercionDomain, generated-name reference storage — 113 webs retired in Materialize reference storage whose only spelling defect is a generated name #9398 (merged
2e9d47e5d): named reference storage tolerates compiler-generated type names (display classes, reference state machines, anonymous and collection-expression types); output changes only in declaration form, allPartial → Partial. - OutsideCoercionDomain remainder (83 webs across its combinations) — 47 struct/generic-definition
thisspills blocked on Struct instance this spilled to a slot materializes as a value copy and loses writes (Full-labelled) #9395 (importerthistyping; value storage must not admit them), 15 inSectionPipeline1.Add(empty type-shape map: shape acquisition), 19Coalesce` producers without an assignment type (Split, UnrenderableStoreType), 2 name collisions. - MissingLoad (63) — store-only webs; decide the representation (materialize as a discarded store or retire the store) rather than binding a never-read local.
- ConflictingTypeTestimony (25 webs, 61 locals, all split) — testimony reconciliation so the web unifies or is proven to need two storages.
- Array literal raise blocked by a residual slot-copy chain and a hoisted conditional element (Newtonsoft
DynamicProxyMetaObject<T>.GetArgArray) #9249 — IncompleteCopyComponent (21 webs plus its combinations): the residual slot-copy chain blocking the array-literal raise. - UnderivableTypeTestimony (12 → 0 in Let argument and comparison sinks testify for untyped slot loads #9390) — every load with a sink now derives a type or the web fails visibly.
- Enum comparison siblings —
ComparisonSiblingTypedeclines enum siblings because the derivation does not see enum backing widths; threadEnumUnderlyingTypesinto the derivation so an int-backed enum sibling testifies (restoresSubstituteNoPiaLocalTypeonce its sbyte backing is known to be in range or the stores are checked). - UnrenderableStoreType and BooleanSinkIdentityRecovery (12) — spellability and the Boolean-sink identity rule.
- Nested bodies — keep the nested share at or near zero as outer groups retire (1 → 0 in Let argument and comparison sinks testify for untyped slot loads #9390).
Rules
ResidualSlotBindingPassgains no admission rule; a group retires by materialization or testimony typing the web before the pass runs.- Every slice publishes the census before/after at exact base/head and its changed-method fidelity; a
Full-method fidelity regression is the stop signal. - Late-decidable webs (no veto at the pass position) are materialization's defect, not a binding target; the count stays 0.
- Residual byref-like split pieces of one slot collide in the printer up-front declaration map #9367 (byref-like split pieces colliding in the printer's declaration map) is Program A's, since it is a printer defect.
Generated by Claude Code
Activity
richlander commentedon Oct 5, 2026
Fresh
--residual-binding-censusupdate on exactmain5a00671a924dc45866310fd1b6355af1729c7415, using the fixed 14-assembly,89,065-method corpus:
The group movement is confined to three rows:
IncompleteCopyComponentConflictingTypeTestimonyConflictingTypeTestimony, IncompleteCopyComponentAll other groups are unchanged. In particular, the materialization hand-off
remains sound (
Late-decidablestays zero), and the 12 untyped webs continueto fail visibly rather than becoming success-shaped output.
Reproduction:
The census exits 1 by design for the 12 visible untyped-pass failures.
richlander commentedon Oct 5, 2026
Adopted as the baseline: the body now reads 329 / 396 / 213 / 52 / 0 / 12 / 1 at
5a00671a9, with the three moved rows updated and the8076ab86fopening figures kept as history. The movement is exactly #9358's expected effect (StackSlotLiveRangePasssplitting the loop-contained entry-reset ranges upstream of binding:DiffCommand.BuildAnalysisDiffS_6/S_8leave theConflictingTypeTestimonysplit group), so no admission changed in materialization or binding. I will confirm with my own run at the same head when the current experiment finishes; the 12 untyped webs are the first slice's docket and are unchanged.Generated by Claude Code
richlander commentedon Oct 5, 2026
Slice 1 is up as #9390: argument and comparison sinks testify for untyped slot loads, plus the object-carrier coalesce witness. At its head against the effective base
fd5c6343f(14 assemblies / 89,065 methods): bound webs 329 → 317, locals 396 → 383, methods 213 → 201, split 52 → 51, late-decidable 0 → 0, untyped visible failures 12 → 3, nested webs 1 → 0. TheUnderivableTypeTestimonygroup (12 webs) is gone; the three remaining failures are the twoIncompleteCopyComponentchains (#9249's docket) andGetTypeInfoForNode's slot reuse (theConflictingTypeTestimonydocket). Render A/B against the same base: valid→invalid 0, 3 methods newly rendering, invalid→valid 1. The five enum carriers now bind asRefKind/SpecialTypelocals under the frozen policy because their integer-constant stores keep them out of materialization (UnrenderableStoreType): that is the next group to retire.Generated by Claude Code
Testify only width-exact comparison siblings for untyped slot loads
Let argument and comparison sinks testify for untyped slot loads (#9390)
richlander commentedon Oct 5, 2026
Slice 1 merged: #9390 (
96d12bcb1)Untyped visible failures 12 → 4;
UnderivableTypeTestimony12 → 0; nested-body webs 1 → 0; bound population 329 → 317 webs. Three review rounds (Anthropic seat): round 1 found that narrow comparison siblings truncated (byte S = c ? (byte)e : (byte)x), round 2 that auintsibling on a signedcltflipped the comparison's signedness, round 3 clean. The rule that landed: a comparison sibling testifies only when its static type is exactly the operand type the IL comparison fixes — by family, width, and (ordering kinds) signedness.Docket items from the reviews (none blocking; pre-existing unless noted):
double; say so in the next slice touching that paragraph ofvalue-typed-emission.md.ClassicInverseSinkRules.SinkTypemaps argument and comparison sinks with different filters fromCoercionSinks.LoadSinkTargetType; review for one shared abstraction.??passed straight to anobjectargument (no slot) still renders invalid C#; the witness only applies at a slot carrier.IB S_1for vetoed narrow-reference-sibling webs;(BE)S_1at a byte-backed enum comparison (truncates one step later);byte S_1 = !c ? b : sefor a short/byte join — allResidualSlotBindingPasspolicy outputs, candidates for the UnrenderableStoreType slice.BooleanSinkIdentityRecovery3 → 4: the Boolean-sink identity rule now sees argument sinks (BooleanSlotLoadType→SemanticLoadSinkTargetType); the new web is disclosed in the Let argument and comparison sinks testify for untyped slot loads #9390 body's group table.Next slice candidates, in docket order: OutsideCoercionDomain (coercion-domain admission, 196 webs) or the enum-sibling derivation above as a small follow-on.
Generated by Claude Code
Materialize reference storage whose only spelling defect is a generat…
richlander commentedon Oct 5, 2026
Slice 2 merged: #9398 (
2e9d47e5d)Bound webs 317 → 204, locals 383 → 270, methods with bound webs 201 → 143. The 113 retired webs are reference-typed compiler-generated storage (closure display classes, reference iterator/async state machines, anonymous types inside generics, collection-expression types) whose stores already carried the exact type; the explicit-type name check was the only refusal. Render A/B: 34 changed, all invalid→invalid, declaration form only; render hash: 39 changed, all
Partial → Partial. One review round, clean.Found on the way: #9395 — a struct
thisspilled to a slot materializes as a value copy and loses writes, in 26Full-labelled corpus methods today (for exampleFileLinePositionSpan::.ctordropsthis.Path). It is the most valuable correctness item adjacent to this program and blocks the next 47OutsideCoercionDomainwebs, which must not be admitted as value storage untilthiskeeps reference identity.Remaining docket candidates, by yield: MissingLoad (57 + 5), #9395 then the struct/generic-definition
thiswebs (47), ConflictingTypeTestimony (25 webs / 61 locals), #9249 IncompleteCopyComponent (21 + combinations),SectionPipeline1.Add` shape acquisition (15), the enum comparison-sibling follow-on (1).Generated by Claude Code
richlander commentedon Oct 6, 2026
Program B slice merged in #9427: array literals filled through
dupcopy chains now raise. It closes #9249.ArrayLiteralFromStoresPasstreats the importer's per-dupslot copies as one place. A conditional element spilled to its own slot joins the run, for local and slot arrays alike. Literal elements keep the element-store coercion.docs/design/array-literal-fill-raise.md.HelpOption::.ctor, whose literal feeds anUnsupported.ctormarker).This redirected #9249: the fix belongs to the raise owner, not to storage admission.
Follow-ups filed during review: #9436 (keyword parameter loses its
@at address-taking uses) and #9437 (boxed narrow constants print asint). The unresolved-enum conditional reproduction was added to #3106.Remaining Program B candidates from the survey:
?./??elements or shared carriers.??joins whose renders read an unassigned piece while labelled Full.Generated by Claude Code
richlander commentedon Oct 6, 2026
Checkpoint 2026-10-06: source-closed copy components merged (#9454,
79a0e4fb2).CoercionSinks.RequiresSlotStoreCoercion).SlotMaterializationInvariantre-derives the boundary.Carried into the next slice:
SlotMaterializationPass<summary>, and the pointer-admission line invalue-typed-emission.mdthat still excludes incomplete copy components.MissingLoadresiduals.Generated by Claude Code
richlander commentedon Oct 7, 2026
Published the
MissingLoadslice as PR #9702. The pass retires producer-only slot webs before materialization, preserving effectful and contextual producers as typed discards. On the fixed 89,065-method corpus, residual binding moves 143 webs / 209 locals / 98 methods to 94 / 160 / 67; all 49MissingLoaddecisions disappear, with zero valid-to-invalid transitions in the available Render A/B population.#9702
richlander commentedon Oct 8, 2026
Published the receiver-alias slice as #9770.
The pipeline now retires exact aliases of the owner-issued receiver binder when
storage would either copy value-type
thisor require an invalid open-genericreference local. Ordinary non-generic and spellable generic reference storage
continues through normal materialization.
Fixed 14-assembly / 89,065-method evidence:
OutsideCoercionDomain: 14 → 4;The 13-assembly Render A/B comparison covers 46,945 methods: six structural
changes, two valid→valid, four invalid→valid, and zero valid→invalid.
#9770
richlander commentedon Oct 9, 2026
PR #9799 retires the final two valid unified
OutsideCoercionDomainstoragewebs by moving their residual-equivalent named-reference decisions into
SlotMaterializationPass.Residual binding moves from 84 webs / 150 locals / 57 methods to
82 / 148 / 55. The two remaining outside-domain webs belong to the already
unsupported
SectionPipeline<T>.Addmethod. Exact product output is unchanged.#9799
richlander commentedon Oct 9, 2026
Slice 8 merged: #9799 (
c0d5bc012)The final two valid unified
OutsideCoercionDomainwebs now materialize throughthe typed storage pass. Residual binding is 82 webs / 148 locals / 55 methods;
the two remaining outside-domain webs belong to the already unsupported
SectionPipeline<T>.Addmethod. The squash merge preserves the reviewed patchexactly (
8f096362b3dcff200f4ad593a1d2c695b9ccfc8d).The next slice moves from unified storage admission to the 51 split residual
webs: classify
ConflictingTypeTestimonyand select the smallest testimony rulethat retires a coherent group without widening residual binding.
richlander commentedon Oct 9, 2026
Slice 9 is published as #9813 at exact head
e39cffd766ff9adb483ad023363d249b0bc88a2f.It splits the two importer-proven widening reference-coalesce ranges in Roslyn
accessor-location methods. The fixed corpus moves from 82 to 80 residual webs,
148 to 144 residual locals, 55 to 53 affected methods, and 51 to 49 split webs;
exact pass impact adds only the two named witnesses.
#9813
richlander commentedon Oct 10, 2026
#9813 merged into
mainasa016381cd560d3eb8f6633b6261731ac2a8f1316.The reviewed candidate and squash merge have the identical stable patch ID:
584ba8431980968f7fae22a8e5b3366bb8e72355.This slice retires the two importer-proven widening self-coalesce residual webs.
The next independently mergeable slice is to classify the remaining 49 split
webs and select the next coherent proof-backed group rather than broadening
testimony reconciliation.
#9813
richlander commentedon Oct 10, 2026
Slice 10 is published as #9883 at exact head
06e428763140a92d1c7122ecdb91a88a939529f5.It retires all 19 named-reference coalesce split webs whose operands have exact importer-issued widening evidence to one testified storage target. The fixed corpus moves from 80 to 61 residual webs, 144 to 104 residual locals, 53 to 35 affected methods, and 49 to 30 split webs; exact pass impact adds only the 18 affected methods.
#9883
richlander commentedon Oct 10, 2026
Slice 10 merged: #9883 (
f3e613223)All 19 named-reference coalesce split webs now materialize through exact importer-issued widening proof. Residual binding moves from 80 webs / 144 locals / 53 methods to 61 / 104 / 35, and split webs move from 49 to 30. The reviewed candidate and squash merge have the identical stable patch ID:
7cc87e6e1bbc5e3d75ad521872014dd0b21302c3.The next independently mergeable slice is to classify the five remaining non-reference
UnrenderableStoreTypesplit webs and select the smallest proof-backed family without broadening residual testimony.#9883
richlander commentedon Oct 11, 2026
The next printer-thinning slice is published as #9946 at exact head
0cdb3dbefdb0bfe01c9630a620b4429b89af9fe3.It retires four non-reference
UnrenderableStoreTypesplit webs through twobounded owners: exact integer identity for complete Boolean/
intarithmeticslots, and complete Boolean-I4-family accounting before Boolean
materialization. The fixed corpus moves from 61 to 57 residual webs, 104 to 96
residual locals, 35 to 31 affected methods, and 30 to 26 split webs. The
remaining
UnrenderableStoreTypeweb is the deliberately excludedBoolean-to-
ArgumentRefKindjoin.#9946
richlander commentedon Oct 12, 2026
Slice 11 merged: #9946 (
2d4a2f4c4)Four non-reference
UnrenderableStoreTypesplit webs now retain their boundedI4 identity in the typed pass pipeline. The fixed corpus moves from 61 to 57
residual webs, 104 to 96 residual locals, 35 to 31 affected methods, and 30 to
26 split webs. The reviewed candidate and squash merge have the identical
stable patch ID:
137cfc86a9d332b1c27e5b73ae22ffdfa5864b0e.The checksum-verified Roslyn source comparison exposed the next independent
printer-thinning slice: fully raise
ForEachLoopOperation.ChildOperationsCountby eliminating the remainingsingle-use arithmetic and struct-receiver temporaries. That work is tracked by
#9969. The separate Boolean-to-
ArgumentRefKindenum split remains outsidethat issue.
#9946
#9969
richlander commentedon Oct 12, 2026
Published #9979 for the next printer-thinning slice.
The PR fully raises Roslyn's
ForEachLoopOperation.ChildOperationsCountto one direct four-term expression,removing both the arithmetic stack carrier and value-type receiver temporary.
The late pass changes exactly the intended 1/89,065 fixed-corpus methods, while
the residual-storage census remains unchanged at
57 webs / 96 locals / 31 methods / 26 split webs.
Candidate head:
ce09e35a06a49315c4e58f58cc32d5edd61377ce#9979
richlander commentedon Oct 13, 2026
#9979 merged as
8ab566d908f804b1b00766001033438fc6670402.Its stable patch ID exactly matches reviewed head
25244f0bbb2bb095fb1ad51c4c0e048be1bbe5de.Theme: C# printer thinning
ForEachLoopOperation.ChildOperationsCountnow raises to one directfour-term expression without synthetic arithmetic or struct-receiver
temporaries.
ArgumentRefKindenum-identity split in
TypeRef.FunctionPointerParameterText.erased by IL. Any default-spelling change must consult both facets of the
dotnet/runtime style oracle rather than extending the semantic raise.
richlander commentedon Oct 13, 2026
The final planned printer-thinning slice is published as #10032:
#10032
It preserves the already-decided
ArgumentRefKindresult through Booleanfolding, retiring the fixed corpus's final
UnrenderableStoreTypesplit:Exact
boolean-foldingimpact changes onlyTypeRef.FunctionPointerParameterText.richlander commentedon Oct 13, 2026
#10032 merged:
#10032
e5d174110ab312dbc40c71ae6362d21e6940492d2632ce7de669df8609ab5e27acb9499085576416895ace82b2150888633c7d0bec3329061f70d617This completes the 13th of 13 planned C# printer-thinning slices. The final
slice preserves decided
ArgumentRefKindidentity through Boolean folding andretires the fixed corpus's final
UnrenderableStoreTypesplit:The remaining residual groups are named, supported floors outside the completed
plan rather than deferred work required by this slice.