4.1 KiB
§172 — THE ORPHAN-SLOT MECHANISM v2 (P30 S50-Max): the complete frame-residue model for gcc-2.7.2 MIPS
(v2 supersedes the S50 original in place: the Max-effort source reading corrected the producer list, the alignment math, and the strength of the impossibility claim. The instrument and the orphan rule are unchanged and re-verified.)
The instrument (tools/cc1_dumps.sh <draft.c> <tag>): run the pinned cc1 with
-dr -ds -dj -dc -dl -dg, then count standalone (insn N P X (use (reg:M R))) insns in the
.combine dump — count them across ALL modes, not just SImode. Each is one never-referenced
reload slot. On func_8017CE58: draft 12, target frame demands the equivalent of 16.
Slot producers in the spill region (complete list, from the compiler source):
- alter_reg pseudo slots (reload1.c): every slotted pseudo gets
assign_stack_local(mode, size, -1)— align −1 means BIGGEST_ALIGNMENT = 8 bytes, size rounded up to 8. This is why every orphan costs 8 bytes even in SImode. Slots are assigned in REGNO order: the a1-param pseudo (lowest) lands first, loop-opt-created pseudos (highest) last — the two ends of the spill region are position-pinned; everything between is order-free. - combine USE-orphans (combine.c:10835): the ashift intermediate of a short-mem→int
promotion triple, orphaned when the death-note walk (backward over plain insns only) hits a
CODE_LABEL or JUMP_INSN. Orphan rule, byte-verified both directions: the HImode load's reg
carries an extra HImode use (a
?:arm copy) → the load survives (or is REWRITTEN as(set (reg:HI) (subreg (reg:SI)))— combine does this rewrite, which is why an orphaning site can still show a singlelh) → the promotion folds separately → orphan. Single-use load → 3-way merge consumes everything → no orphan. Sites before the first label/jump (the function head) can NEVER orphan — the walk reaches insn 0. - caller-save areas (caller-save.c
setup_save_areas;-fcaller-savesis ON at -O2): allocated eagerly — oneassign_stack_local(SImode, 4, 0)= 4-byte slot per call-clobbered hard reg that carries a call-crossing pseudo at ANY reload iteration, whether or not a save/restore insn is ever emitted. Transient iteration-1 allocations that later respill leave never-referenced 4-byte areas. - spill_stack_slot (reload1.c): one reused 8-byte slot per hard reg that pseudos are spilled FROM ("Spilling reg N" in the .greg dump).
The three-layer canonicalization wall (measured, ~120 probe forms + a 200-variant sweep):
manufacturing an extra orphan with zero code drift requires an expression NOVEL to cse yet
PROVABLY sign-extended to combine. Three layers jointly close every reachable spelling:
fold-const normalizes the trees ((x<<16)>>16 arrives in RTL as the same subreg-promotion —
verified in the expand dump), cse1/cse2 canonicalize through REG_EQUAL/quantity classes, and
opacity that defeats cse (asm-laundered values/pointers) equally blinds num_sign_bit_copies
(the survivor emits real shifts or loads: the opaque-pointer probe reproduces the exact
16-orphan target frame at +7 insns). Scalar declaration order (20 permutations) and TU context
(both drafts run through the real whole-binary gate — first time ever for this class) are also
byte-refuted as levers. The wall statement, honest form: the residual is not reachable by
re-spelling the same computation; what remains is structurally different source with
coincidentally identical bytes.
Transferable diagnostics: (1) frame-off-by-8k residual → count combine-dump USEs first, all
modes; (2) slt-count changes in a probe = the ?: branch structure moved — the form is wrong;
(3) which chain operands RE-LOAD in a later pass is pinned by which HI temps the earlier pass
clobbered as ?: accumulators (the FIRST operand of each inner ?:), and the bytes pin that —
a select-chain's elision pattern is byte-determined end to end; (4) the a1-param spill position
pins where stratum-1 (declared locals/temps) ENDS — any dial that grows locals displaces it,
which is exactly the dead[8] 2-off signature.