From 4adb559a29467f2dacdaaedede162f4716e33e65 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:17:09 -0600 Subject: [PATCH] =?UTF-8?q?docs(phase-31):=20cookbook=20=C2=A7177=20?= =?UTF-8?q?=E2=80=94=20the=20epilogue=20return-delay=20slot=20is=20decided?= =?UTF-8?q?=20by=20the=20SAVED-REGISTER=20SET?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eleven wave-Q functions in 800c/800c3 sat at closeness 1-3 with the same epilogue residual, and every agent independently filed it as intrinsic ("epilogue-delay-slot-unfillable", "gcc/maspsx structural wall"). It is neither intrinsic nor a scheduling problem. Source-confirmed at gcc-2.7.2/config/mips/mips.c:5376 -- int mips_epilogue_delay_slots () { if (current_frame_info.total_size == 0) return 1; /* no frame */ if (current_frame_info.mask == RA_MASK && current_frame_info.fmask == 0) return 1; /* only $ra */ return 0; } gcc offers the epilogue a delay slot ONLY when the function allocates no stack, or saves nothing but $ra. Otherwise the slot is never offered to the scheduler and the emitter puts the stack restore there instead (mips.c:5276, the tsize > 0 path). So the lever is the CALLEE-SAVED SET, steerable from C: the first value whose live range spans a jal costs an $s register and flips the switch. To gain a filled slot, hold nothing across a call (recompute or re-read after it); to lose one, hoist a load above the call. Register pins are the WRONG tool here -- §176-C already established a pin cannot schedule across a call. ~600 instructions were three instructions from banked and about to be written off. The meta-lesson (R17): when N independent agents call one residual "structural", read the compiler -- the answer was forty lines of mips.c already sitting in tools/reference/gcc-2.7.2/. --- docs/cookbook-index.md | 56 +++++++++++++++++++++------------------ docs/matching-cookbook.md | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index d2cd890f6..b1b474abc 100644 --- a/docs/cookbook-index.md +++ b/docs/cookbook-index.md @@ -2,7 +2,7 @@ > **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section. > -> `docs/matching-cookbook.md` is ~716 KB / 543 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. +> `docs/matching-cookbook.md` is ~716 KB / 544 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. **How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win. @@ -34,7 +34,7 @@ ## By symptom -### delay slots & branches (7) +### delay slots & branches (8) - **§3-T4** — Branch polarity: invert the source condition to flip gcc's chosen branch L90 - **§5a** — Cross-jump tail-merge — gcc collapses two byte-identical blocks the original kept separate (FIX FOUND) L211 @@ -42,9 +42,10 @@ - **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED L10065 - **§162** — CROSS-JUMP DIRECTION: the surviving copy is always the LATER one, so a BACKWARD `j` into a sibling arm is a source `goto` (P30 S48) L11263 - **§162** — The cross-jump "CALL veto" is a COUNT law, not a CALL law (BOUNDS §88a; P30 S48, `func_80189540`) L11299 -- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17136 +- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING L17119 +- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17182 -### instruction scheduling (22) +### instruction scheduling (23) - **§3-T2** — Source statement order drives instruction scheduling L78 - **§3** — When a diff is pure scheduling → decomp-permuter (harness built, Phase 6) L107 @@ -66,10 +67,11 @@ - **§3-A.** — `move_movables` hoists iff `threshold × savings × lifetime ≥ insn_count` — and you can read it L10148 - **§16Z** — SHARPENS *(sharpens §37 "the /s-DEP LATTICE", §136-13, §136-14, §16Xy, §162q, `gcc-2.7.2-map/sched.md` §64)* L14337 - **§175** — A CALLER-SAVED REGISTER PIN CAN BE A CORRECTNESS BUG, NOT JUST A SCHEDULING CHOICE (P31 wave H, 2026-08-15) L16709 -- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17136 -- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17200 +- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING L17119 +- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17182 +- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17246 -### register allocation & pins (47) +### register allocation & pins (48) - **§10** — Closing the regalloc/scheduling hard tail by hand (LZSS, Phase 7 session F — the full close) L835 - **Residual** — A — commutative `|`/`&`/`+` result lands in the wrong source-operand register L856 @@ -115,16 +117,17 @@ - **§156** — THE PREFERENCE-DONOR MERGE: cross-region variable reuse is what fills a0-a3, and a call-arg use in ONE region steers the fill in ALL of them (P30 S46 tier-3, `func_80186E24`, 611 ins: 236-off "S11 regalloc-order" → MATCH, zero new pins) L10769 - **§175** — A CALLER-SAVED REGISTER PIN CAN BE A CORRECTNESS BUG, NOT JUST A SCHEDULING CHOICE (P31 wave H, 2026-08-15) L16709 - **§176j** — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL (and how much is recoverable) L17093 -- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back L17130 -- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17160 -- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17200 +- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING L17119 +- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back L17176 +- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17206 +- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17246 ### CSE / redundancy / rematerialization (4) - **§46** — The `func_80178D40` crack (890 ins ×134, the heaviest core in the game): four LOOP-STRUCTURE levers cheap-Opus found by reading loop.c/jump.c/cse.c (Phase 26 session 8, 2026-07-13) L3313 - **§83d** — CSE's quantity budget is WHOLE-FUNCTION, so a local rewrite cannot fix a local symptom L6452 - **§153** — THE ADDRESS-REMATERIALISATION LAUNDER: a third zero-emission asm lever (P30 S43, `func_8018D98C`, 710 ins) L10463 -- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) L17226 +- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) L17272 ### loops & induction variables (10) @@ -177,7 +180,7 @@ - **§16Z** — SHARPENS *(sharpens §37 "the /s-DEP LATTICE", §136-13, §136-14, §16Xy, §162q, `gcc-2.7.2-map/sched.md` §64)* L14337 - **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) L14339 - **§176g** — SIZE A WAVE BY INSTRUCTIONS, NOT BY CARDS (P31 S52 — the adopted doctrine) L16927 -- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17160 +- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17206 ### types, signedness & load/store width (35) @@ -572,7 +575,7 @@ - **§3-C2.** — RECONCILE BEFORE THE FIRST GATE — a parked draft gets HARDER to bank, not easier L17020 - **§3-D.** — The measured cost shape, and what to build next L17043 - **§176i** — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE (P31 S52, wave Q) L17061 -- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY L17119 +- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY L17165 ### process, measurement & doctrine (78) @@ -652,8 +655,8 @@ - **§176g** — SIZE A WAVE BY INSTRUCTIONS, NOT BY CARDS (P31 S52 — the adopted doctrine) L16927 - **§176h** — THE BATCH-SUBSTITUTION HAZARD MAP (P31 S52): seven holes, three wrong fixes, one law L16965 - **§3-D.** — The measured cost shape, and what to build next L17043 -- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17200 -- **§176-E** — Two cheap source spellings, both cc1-probed L17252 +- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17246 +- **§176-E** — Two cheap source spellings, both cc1-probed L17298 ### (unbucketed — title matched no symptom vocabulary) (178) @@ -833,8 +836,8 @@ - **§176a** — THE VERIFICATION-LAYER LAWS (P31 overnight, 2026-08-15). What each check can and cannot prove. L16734 - **§176b** — BATCH-GATING MECHANICS (P31): what changes when N drafts land in ONE .c L16774 - **§3-C.** — The limit that remains (recorded, not solved) L17013 -- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying L17274 -- **What** — is NOT banked here L17291 +- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying L17320 +- **What** — is NOT banked here L17337 ## All sections, in order @@ -1373,12 +1376,13 @@ - **§3-D.** — The measured cost shape, and what to build next L17043 - **§176i** — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE (P31 S52, wave Q) L17061 - **§176j** — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL (and how much is recoverable) L17093 -- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY L17119 -- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back L17130 -- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17136 -- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17160 -- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17200 -- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) L17226 -- **§176-E** — Two cheap source spellings, both cc1-probed L17252 -- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying L17274 -- **What** — is NOT banked here L17291 +- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING L17119 +- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY L17165 +- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back L17176 +- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first L17182 +- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation L17206 +- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug L17246 +- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) L17272 +- **§176-E** — Two cheap source spellings, both cc1-probed L17298 +- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying L17320 +- **What** — is NOT banked here L17337 diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index fa266d0eb..1f08c5880 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -17116,6 +17116,52 @@ in-flight draft is near-matching, the tokens are already spent and stopping conv "nearly banked" into "needs a second, cheaper pass" — which is fine, but it is a deferral, not a saving. +## §177 — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING +### (P31 S52 — source-confirmed in `gcc-2.7.2/config/mips/mips.c`; eleven functions were stuck on it) + +**The symptom.** A draft sits at closeness 1–3 with the differing instructions clustered in the +epilogue: the target fills the `jr $ra` delay slot with a real body instruction while your draft +emits `addiu $sp,$sp,N` there (or the reverse). Wave Q's repair pass produced **eleven** of these +in the `800c`/`800c3` regions, and every agent independently filed it as an intrinsic scheduling +wall — "epilogue-delay-slot-unfillable", "gcc/maspsx structural". **It is not a scheduling problem +and it is not a wall. It is a frame-shape problem, and it is steerable from C.** + +**The rule, verbatim from `mips.c:5376`:** + +```c +int mips_epilogue_delay_slots () { + if (current_frame_info.total_size == 0) return 1; /* no frame */ + if (current_frame_info.mask == RA_MASK && current_frame_info.fmask == 0) return 1; /* only $ra */ + return 0; /* otherwise */ +} +``` + +So gcc-2.7.2 offers the epilogue a delay slot **only** when the function either allocates no stack +at all, or saves **nothing but `$ra`** (no callee-saved `$s` registers, no FP registers). In every +other case it returns 0, the slot is not offered to the scheduler, and the emitter puts the stack +restore there instead (`mips.c:5276`, the `tsize > 0` path). + +**Therefore the lever is the CALLEE-SAVED SET:** + +| target does | means | your draft must | +|---|---|---| +| `jr $ra` + a body instruction in the slot | frame is `$ra`-only (or zero) | need **no** value live across a call — no `$s` registers | +| `jr $ra` + `addiu $sp,$sp,N` | frame saves `$s` regs | keep **at least one** value live across a call | + +**How to steer it in C** (cheapest first): +1. **Fewer values live across calls.** Recompute a value after the call instead of holding it; read + it back from the struct/global it came from. Each value whose live range spans a `jal` costs one + `$s` register, and the *first* one flips this switch. +2. **More values live across calls**, for the opposite direction: hoist a load above the call and + use it after, instead of reloading. +3. Only then consider register pins — and remember §176-C: a pin cannot schedule across a call, so + pinning is the wrong tool for this residual entirely. + +**Why this matters beyond the eleven.** They are ~600 instructions sitting *three instructions* from +banked, and they were all about to be written off as intrinsic. **A residual that eleven independent +agents call "structural" is a signal to read the compiler, not to file a wall** (R17): the answer +was forty lines of `mips.c` and it was already sitting in `tools/reference/gcc-2.7.2/`. + ## §176c — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY main's `make extract` runs the EXE-only `psyq_integrate` + `ld_interleave` steps, which **rewrite the