7.5 KiB
§309 — A FRAME-ADDRESS EQUIVALENCE RIDES THROUGH A SKIPPABLE CONDITIONAL: POST-if STORES FOLD ONTO $sp UNLESS THE if SURVIVES jump1 AS A BARRIER-PRECEDED DIAMOND (P31 S63 t5e-t5i; byte-proven func_801812AC)
(The FRAME-ADDRESS analogue of §164-52 (L12946), whose fold law and ">=2-jump-ref label" dial are byte-scoped to a SYMBOL base and do not transfer — a frame address is a PLUS, never CONSTANT_P (regalloc.md [A23]), so none of §164-52/§48-C1's struct/offset escapes exist for it. RESOLVES the probe §162m files at L11525 as "UNTRIED, and it is the next probe" — the balanced-diamond EBB split works, and this is its first byte-proof; it also promotes gcc-2.7.2-map/cse_expr.md §H-1/:387 from a pass-map antidote for global/derived-pointer folds to a banked law for &local. SAME C EDIT as §48-A4 (L3458) for a DIFFERENT pass and a different residual — do not merge them. NOT §193-D (that re-bases a block onto $aN via optimize_reg_copy_1, and its dial is a surviving CODE_LABEL). NOT §195-L (a join whose label has NUSES>=2 always ends the cse block; this law is about the ONE-use join that does not).)
THE TELL. A run of sw <reg>, <imm>($sp) / sw $zero, <imm>($sp) immediately downstream of a jal that itself sits just after an if/else or a ?:, where the target has the same opcodes, same source registers and same field order based on a callee-saved pointer register instead — sw <reg>, <imm2>($sN), with imm2 = imm − frame_offset_of_the_local. Same shape, same values, wrong base register and a correspondingly shifted immediate, instruction count identical. That is not regalloc and not scheduling: nothing in your register file is wrong — the pointer variable was never materialised at all. Do not open §47/§158/§136 or a register __asm__ pin on it.
THE MECHANISM. pr = &prim enters cse's table as pseudo == (plus (reg virtual-stack-vars) K), and find_best_addr/fold_rtx rewrite every downstream (mem (plus pseudo disp)) into (mem (plus fp K+disp)) — a legal MIPS address — so the pointer loses its last user and is deleted. Whether that fold reaches past the if is decided in cse_end_of_basic_block (cse.c:8091-8185), which has two arms, both taken only when LABEL_NUSES (JUMP_LABEL (p)) == 1:
- the branch's target label is preceded by a BARRIER (
:8116,-fcse-follow-jumps) — a real two-armed diamond. The scan resumes at the ELSE label and terminates at the join CODE_LABEL;new_basic_block(:8430) flushes the table,pris re-materialised, and the stores address$sN. This is the target. - the branch's target label is not barrier-preceded (
:8146,-fcse-skip-blocks) — a one-armed conditional branching around a block. The scan jumps to that label and keeps going past it to the next CODE_LABEL, carrying the whole table with it.invalidate_skipped_block(:7843-7866) then invalidates only what the skipped arm SET; thepr == fp+Kequivalence is untouched, so every post-join store folds onto$sp.
⚠ CORRECTS THE ORIGINATING NOTE'S MECHANISM. It is not "cse ends its per-arm scan at each arm's own call". invalidate_skipped_block handles CALL_INSN explicitly (invalidate_for_call() + skipped_writes_memory = everything, :7856-7860) and keeps skipping; a call kills hard regs and MEM entries, never a pseudo's (plus fp K) equivalence (cse_expr.md §1). The call matters only because of what it does to jump1: per §136d-2/§164-57/§164-74, an if/else whose arms are plain REG SETs is collapsed by jump.c:728-760 into t = B; if (c) t = A; with the jump around the set deleted (jump.c:821) — leaving exactly the barrier-free skippable block. A call (or a MEM store) is not a simple SET, the collapse cannot fire, the j+BARRIER survives, and the diamond stands. Duplicating the call is one way to buy the barrier, not the mechanism itself.
THE C SHAPE.
// FOLDS TO $sp (wrong) — the conditional collapses to a skippable block
Prim *pr = &prim;
void *src = (a3 != 0) ? (void *)D_8018633C : a2; /* or if/else assigning one shared var */
func_80017DC4(src, &mtx);
pr->code = 0x50000000; /* emitted sw …($sp) */
pr->c[0] = pr->c[1] = a4;
// MATCHES — duplicate the CALL into both arms so jump1 cannot collapse the diamond
Prim *pr = &prim;
if (a3 != 0) { func_80017DC4((void *)D_8018633C, &mtx); }
else { func_80017DC4(a2, &mtx); }
pr->code = 0x50000000; /* now sw …($s0), matches */
pr->c[0] = pr->c[1] = a4;
jump2's cross_jump merges the two now-identical jal suffixes back into one site, so the length is unchanged — 129 ins both ways; only the store base differs. (In-tree bound on §88a: a repeated call-shaped tail was merged here, as in §224; §88a's "left UNMERGED" is a suffix-equality/§50-B-floor observation, not a call exclusion — find_cross_jump has none, §193-C.)
BYTE EVIDENCE (func_801812AC, wave t5e-t5i, 129 ins). Warm-start draft: near, closeness 10, OPCODE-MIXED, residual [36] afa20060 sw v0,96(sp) vs target ae020030 sw $v0,0x30($s0) … [40] afa00058 sw zero,88(sp) vs ae000028 sw $zero,0x28($s0) — unchanged across a statement reorder (v1.c) and a register pin (v2.c); neither reaches the fold. a1.c (drop the local hdr/src copies) → closeness 5, residual is the [36..40] $sp run alone. b1.c (shared src var written as an explicit if/else) → closeness 30, worse — the §136d-2 collapse puts the skippable block straight back; this is the negative witness that the diamond, not the if keyword, is the lever. b5.c (call duplicated into both arms + TU-authoritative void * signature, inline casts) → MATCH 129/129, residual []. b6.c (same duplicated-call structure, hdr copy reintroduced) → closeness 5, SCHEDULE-REORDER — the prologue-order and $sp-fold defects are independent and both fixes are required.
FLAG ABLATION AS A PASS ORACLE (reusable). Recompiling the folding source with -fno-cse-skip-blocks gives nins 129 129 diffs 5: the entire $sp run disappears and only the unrelated prologue arg-copy cluster remains. One recompile named the pass and separated two co-resident residual classes — cheaper than a -da dump read. Bank the technique, not just the law: when a residual is suspected to be a cse EBB-extension effect, ablate -fcse-skip-blocks / -fcse-follow-jumps first (both are -O2-only, toplev.c:3389-3390).
BOUND. 1. -O2 only — both flags are off at -O1/-O0, so this class cannot occur in an -O0 file (§116/§127). 2. Requires LABEL_NUSES == 1 on the branch target. A join reached by two or more jumps ends the block on its own (§195-L, §164-52) and there is nothing to fix. 3. Scoped to a frame address whose uses are all DEREFERENCES. §164-52 LAW 1a's clause carries over: a use of the pointer VALUE (call argument, compare, store of the pointer itself) has no absolute form to fold into and keeps the base alive on any path. 4. The refund is TAIL-only (§193-C) and has a floor of 2 insns in the merged block (§50-B) — duplicating a call whose arms differ after it buys you nothing and costs the duplicate. 5. Not length-neutral in general; it was here because the duplicated tail merged. Read the count before assuming. 6. Untested: more than two arms, a switch dispatch, and whether the same ride-through folds a frame address across a skipped block that itself contains a call (the source says it should — :7856 skips on — but I did not compile it; R14 applies to that clause).