mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 21:36:06 -04:00
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:
+30
-26
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user