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