docs(phase-31): cookbook §177 — the epilogue return-delay slot is decided by the SAVED-REGISTER SET

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/.
This commit is contained in:
Drew T
2026-08-16 14:17:09 -06:00
parent c70f6d4e34
commit 4adb559a29
2 changed files with 76 additions and 26 deletions
+30 -26
View File
@@ -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 <sub>L90</sub>
- **§5a** — Cross-jump tail-merge — gcc collapses two byte-identical blocks the original kept separate (FIX FOUND) <sub>L211</sub>
@@ -42,9 +42,10 @@
- **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED <sub>L10065</sub>
- **§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) <sub>L11263</sub>
- **§162** — The cross-jump "CALL veto" is a COUNT law, not a CALL law (BOUNDS §88a; P30 S48, `func_80189540`) <sub>L11299</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17136</sub>
- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING <sub>L17119</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17182</sub>
### instruction scheduling (22)
### instruction scheduling (23)
- **§3-T2** — Source statement order drives instruction scheduling <sub>L78</sub>
- **§3** — When a diff is pure scheduling → decomp-permuter (harness built, Phase 6) <sub>L107</sub>
@@ -66,10 +67,11 @@
- **§3-A.** — `move_movables` hoists iff `threshold × savings × lifetime ≥ insn_count` — and you can read it <sub>L10148</sub>
- **§16Z** — SHARPENS *(sharpens §37 "the /s-DEP LATTICE", §136-13, §136-14, §16Xy, §162q, `gcc-2.7.2-map/sched.md` §64)* <sub>L14337</sub>
- **§175** — A CALLER-SAVED REGISTER PIN CAN BE A CORRECTNESS BUG, NOT JUST A SCHEDULING CHOICE (P31 wave H, 2026-08-15) <sub>L16709</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17136</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17200</sub>
- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING <sub>L17119</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17182</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17246</sub>
### 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) <sub>L835</sub>
- **Residual** — A — commutative `|`/`&`/`+` result lands in the wrong source-operand register <sub>L856</sub>
@@ -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) <sub>L10769</sub>
- **§175** — A CALLER-SAVED REGISTER PIN CAN BE A CORRECTNESS BUG, NOT JUST A SCHEDULING CHOICE (P31 wave H, 2026-08-15) <sub>L16709</sub>
- **§176j** — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL (and how much is recoverable) <sub>L17093</sub>
- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back <sub>L17130</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17160</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17200</sub>
- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING <sub>L17119</sub>
- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back <sub>L17176</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17206</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17246</sub>
### 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) <sub>L3313</sub>
- **§83d** — CSE's quantity budget is WHOLE-FUNCTION, so a local rewrite cannot fix a local symptom <sub>L6452</sub>
- **§153** — THE ADDRESS-REMATERIALISATION LAUNDER: a third zero-emission asm lever (P30 S43, `func_8018D98C`, 710 ins) <sub>L10463</sub>
- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) <sub>L17226</sub>
- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) <sub>L17272</sub>
### 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)* <sub>L14337</sub>
- **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) <sub>L14339</sub>
- **§176g** — SIZE A WAVE BY INSTRUCTIONS, NOT BY CARDS (P31 S52 — the adopted doctrine) <sub>L16927</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17160</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17206</sub>
### 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 <sub>L17020</sub>
- **§3-D.** — The measured cost shape, and what to build next <sub>L17043</sub>
- **§176i** — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE (P31 S52, wave Q) <sub>L17061</sub>
- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY <sub>L17119</sub>
- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY <sub>L17165</sub>
### process, measurement & doctrine (78)
@@ -652,8 +655,8 @@
- **§176g** — SIZE A WAVE BY INSTRUCTIONS, NOT BY CARDS (P31 S52 — the adopted doctrine) <sub>L16927</sub>
- **§176h** — THE BATCH-SUBSTITUTION HAZARD MAP (P31 S52): seven holes, three wrong fixes, one law <sub>L16965</sub>
- **§3-D.** — The measured cost shape, and what to build next <sub>L17043</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17200</sub>
- **§176-E** — Two cheap source spellings, both cc1-probed <sub>L17252</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17246</sub>
- **§176-E** — Two cheap source spellings, both cc1-probed <sub>L17298</sub>
### (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. <sub>L16734</sub>
- **§176b** — BATCH-GATING MECHANICS (P31): what changes when N drafts land in ONE .c <sub>L16774</sub>
- **§3-C.** — The limit that remains (recorded, not solved) <sub>L17013</sub>
- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying <sub>L17274</sub>
- **What** — is NOT banked here <sub>L17291</sub>
- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying <sub>L17320</sub>
- **What** — is NOT banked here <sub>L17337</sub>
## All sections, in order
@@ -1373,12 +1376,13 @@
- **§3-D.** — The measured cost shape, and what to build next <sub>L17043</sub>
- **§176i** — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE (P31 S52, wave Q) <sub>L17061</sub>
- **§176j** — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL (and how much is recoverable) <sub>L17093</sub>
- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY <sub>L17119</sub>
- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back <sub>L17130</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17136</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17160</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17200</sub>
- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) <sub>L17226</sub>
- **§176-E** — Two cheap source spellings, both cc1-probed <sub>L17252</sub>
- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying <sub>L17274</sub>
- **What** — is NOT banked here <sub>L17291</sub>
- **§177** — 🔴 THE EPILOGUE RETURN-DELAY SLOT IS DECIDED BY YOUR SAVED-REGISTER SET, NOT BY SCHEDULING <sub>L17119</sub>
- **§176c** — MAIN (SLUS_007.26) CANNOT BE GATED INCREMENTALLY <sub>L17165</sub>
- **§176** — SEVEN LEVERS FROM THE P31 OVERNIGHT WAVES (2026-08-15): statement order, false regalloc, and the pin that fights back <sub>L17176</sub>
- **§176-A** — "SCHEDULE / DELAY-SLOT / LENGTH-DRIFT ±1" ⇒ check STATEMENT ORDER around the call first <sub>L17182</sub>
- **§176-B** — "REGALLOC-PERM, 1-4 instructions off" ⇒ it is usually NOT register allocation <sub>L17206</sub>
- **§176-C** — 🔴 WALL REFUTATION: a hard-register pin CANNOT schedule around a call, because of a genuine gcc-2.7.2 bug <sub>L17246</sub>
- **§176-D** — CSE-class levers used in reverse (two sharpenings of §153 and cse_expr §2) <sub>L17272</sub>
- **§176-E** — Two cheap source spellings, both cc1-probed <sub>L17298</sub>
- **§176-F** — Misdiagnosis triage: four residual verdicts that were lying <sub>L17320</sub>
- **What** — is NOT banked here <sub>L17337</sub>
+46
View File
@@ -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