mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
docs(phase-30 S42): cookbook §147 — the three-stratum frame law + four stop-searching verdicts (from the serial func_8017C294 run)
This commit is contained in:
+24
-7
@@ -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 / 412 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 / 419 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,13 +34,14 @@
|
||||
## By symptom
|
||||
|
||||
|
||||
### delay slots & branches (3)
|
||||
### delay slots & branches (4)
|
||||
|
||||
- **§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>
|
||||
- **§3-D.** — THE CROSS-JUMP RATCHET (the sharpest new trap — `func_80131340` L-C) <sub>L3501</sub>
|
||||
- **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED <sub>L10058</sub>
|
||||
|
||||
### instruction scheduling (15)
|
||||
### instruction scheduling (16)
|
||||
|
||||
- **§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>
|
||||
@@ -57,8 +58,9 @@
|
||||
- **§66d-5** — `residual_class`'s "structural ⇒ permuter CPU is waste" is WRONG for schedule permutations (measured, Phase 29 SESSION-18) <sub>L5484</sub>
|
||||
- **§3-The** — attribution primitive (use this before calling anything a scheduling residual) <sub>L6050</sub>
|
||||
- **§3-The** — scheduling rules (refining §135-2 and §135-4) <sub>L8902</sub>
|
||||
- **Consequence** — for the family (a real scheduling decision) <sub>L10085</sub>
|
||||
|
||||
### register allocation & pins (33)
|
||||
### register allocation & pins (35)
|
||||
|
||||
- **§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>
|
||||
@@ -93,6 +95,8 @@
|
||||
- **§3-Two** — further notes worth keeping <sub>L8241</sub>
|
||||
- **§137** — REGALLOC-PERM is a TWO-COMPILE ARITHMETIC PROBLEM, not a permuter job <sub>L9334</sub>
|
||||
- **§3-The** — same swallow, twice more, in the integration spine <sub>L9699</sub>
|
||||
- **§3-B.** — A `?:` on MEMORY operands costs ~16 bytes of invisible frame; on REGISTER operands, zero <sub>L10047</sub>
|
||||
- **§3-D.** — A lone `$t8`/`$t9` in the target is RELOAD SCRATCH — reproduce the spill, don't pin the register <sub>L10063</sub>
|
||||
|
||||
### CSE / redundancy / rematerialization (2)
|
||||
|
||||
@@ -177,7 +181,7 @@
|
||||
- **§143** — `cast_call_sites` read a RETURN STATEMENT as a prototype and deleted it. A 0/39 sweep became 18/39. (P30 S40) <sub>L9824</sub>
|
||||
- **Then** — propagation returned 0/137 TWICE — both times a missing TYPE <sub>L9990</sub>
|
||||
|
||||
### declarations, prototypes & K&R (54)
|
||||
### declarations, prototypes & K&R (55)
|
||||
|
||||
- **§3-T4** — Branch polarity: invert the source condition to flip gcc's chosen branch <sub>L90</sub>
|
||||
- **§8c** — Splitting a TU means rebuilding its DECLARATION ENVIRONMENT, not moving text (Phase 26 session 6) <sub>L437</sub>
|
||||
@@ -233,6 +237,7 @@
|
||||
- **Reconciling** — a gate-refused draft: which way you edit depends on WHERE the TU's decl is <sub>L9555</sub>
|
||||
- **§141** — The §134 class is CLOSED: every line-shape decision now routes through `cdecl._mask` (P30 S39) <sub>L9730</sub>
|
||||
- **§143** — `cast_call_sites` read a RETURN STATEMENT as a prototype and deleted it. A 0/39 sweep became 18/39. (P30 S40) <sub>L9824</sub>
|
||||
- **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED <sub>L10058</sub>
|
||||
|
||||
### jump tables & switches (26)
|
||||
|
||||
@@ -278,7 +283,7 @@
|
||||
- **§127a** — §71 (sibling-first) is the strongest `-O0` lever, and it beats the index <sub>L8370</sub>
|
||||
- **§132** — The `JR-PAIR-IN-ONE-O0-OBJECT` "wall" was TWO instrument defects: a merged-double span the carve could not see, and a truncated object no rule deleted (P30 S29, `func_8013B83C` + `func_8013BD74`) <sub>L8563</sub>
|
||||
|
||||
### family propagation & sweeps (71)
|
||||
### family propagation & sweeps (74)
|
||||
|
||||
- **§8d** — Templating a body INTO a TU must not CHANGE its declaration environment — demote the carried data externs (Phase 26 session 8, byte-proven on `func_8015AE2C` ×133) <sub>L483</sub>
|
||||
- **§11** — Cross-binary dedup & code-sharing (Phase 11 — "one match unlocks many") <sub>L908</sub>
|
||||
@@ -351,6 +356,9 @@
|
||||
- **§142** — An open stub whose `h_exact` class is MATCHED elsewhere is FREE. Propagate the body; do not gate a draft. (P30 S39, +7,710 ins in two commands) <sub>L9768</sub>
|
||||
- **§143** — `cast_call_sites` read a RETURN STATEMENT as a prototype and deleted it. A 0/39 sweep became 18/39. (P30 S40) <sub>L9824</sub>
|
||||
- **Then** — propagation returned 0/137 TWICE — both times a missing TYPE <sub>L9990</sub>
|
||||
- **§3-A.** — The frame has THREE strata, and stratum 3 is unreachable from C <sub>L10031</sub>
|
||||
- **§3-E.** — A `qty_compare` TIE is not spelling-reachable — recognise it and stop <sub>L10073</sub>
|
||||
- **Consequence** — for the family (a real scheduling decision) <sub>L10085</sub>
|
||||
|
||||
### integration / TU plumbing (36)
|
||||
|
||||
@@ -493,7 +501,7 @@
|
||||
- **§142** — An open stub whose `h_exact` class is MATCHED elsewhere is FREE. Propagate the body; do not gate a draft. (P30 S39, +7,710 ins in two commands) <sub>L9768</sub>
|
||||
- **§3-The** — measurement (do this before any wave; it is ~20 lines and needs no builds) <sub>L9780</sub>
|
||||
|
||||
### process, measurement & doctrine (57)
|
||||
### process, measurement & doctrine (59)
|
||||
|
||||
- **§8e** — The jtbl ALIGNMENT LAW + the pad-spec filter — multi-table .rodata spans (Phase 29, byte-proven; `.run/probe_jtbl/verdict.md`) <sub>L530</sub>
|
||||
- **§3-The** — mechanism: game-code dedup is SOURCE-LEVEL, not an object swap (R-D1, the key lesson) <sub>L926</sub>
|
||||
@@ -552,6 +560,8 @@
|
||||
- **§3-The** — measurement (do this before any wave; it is ~20 lines and needs no builds) <sub>L9780</sub>
|
||||
- **§3-And** — the report-vs-bytes lesson attached to it <sub>L9812</sub>
|
||||
- **§146** — RE-MEASURE A WALL BEFORE YOU RESPECT IT. Both "permanent" giants fell to drafts already on disk. (P30 S6, +50,094 ins) <sub>L9967</sub>
|
||||
- **§147** — The three-stratum FRAME LAW, and four "stop searching" verdicts (P30 S42, `func_8017C294`, serial run) <sub>L10026</sub>
|
||||
- **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED <sub>L10058</sub>
|
||||
|
||||
### (unbucketed — title matched no symptom vocabulary) (120)
|
||||
|
||||
@@ -1091,3 +1101,10 @@
|
||||
- **Then** — propagation returned 0/137 TWICE — both times a missing TYPE <sub>L9990</sub>
|
||||
- **§3-Two** — errors of mine, both instructive <sub>L9999</sub>
|
||||
- **§3-The** — rule <sub>L10013</sub>
|
||||
- **§147** — The three-stratum FRAME LAW, and four "stop searching" verdicts (P30 S42, `func_8017C294`, serial run) <sub>L10026</sub>
|
||||
- **§3-A.** — The frame has THREE strata, and stratum 3 is unreachable from C <sub>L10031</sub>
|
||||
- **§3-B.** — A `?:` on MEMORY operands costs ~16 bytes of invisible frame; on REGISTER operands, zero <sub>L10047</sub>
|
||||
- **§3-C.** — Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED <sub>L10058</sub>
|
||||
- **§3-D.** — A lone `$t8`/`$t9` in the target is RELOAD SCRATCH — reproduce the spill, don't pin the register <sub>L10063</sub>
|
||||
- **§3-E.** — A `qty_compare` TIE is not spelling-reachable — recognise it and stop <sub>L10073</sub>
|
||||
- **Consequence** — for the family (a real scheduling decision) <sub>L10085</sub>
|
||||
|
||||
@@ -10020,3 +10020,75 @@ is repaired, the verdicts it produced become **hypotheses again**, not facts.
|
||||
|
||||
**Symptom lines for the index:** **"a documented wall"** · **"an old close= verdict"** ·
|
||||
**"match_one MATCH but the whole binary differs"** · **"a sweep returns 0 of N twice"**.
|
||||
|
||||
---
|
||||
|
||||
## §147 — The three-stratum FRAME LAW, and four "stop searching" verdicts (P30 S42, `func_8017C294`, serial run)
|
||||
|
||||
Five levers from one serial crack. It did **not** bank (NEAR 12/246, zero structural divergence, and
|
||||
`permuter_ils` plateaus at exactly 12) — but the knowledge transfers, which is why the run was serial.
|
||||
|
||||
### A. The frame has THREE strata, and stratum 3 is unreachable from C
|
||||
|
||||
gcc-2.7.2 lays out the frame in order: **(1)** declared locals, in declaration order, ascending from
|
||||
the outgoing-args top · **(2)** reload spill slots · **(3)** a trailing block the function's `?:`
|
||||
chains allocate and **never reference**. §136-6 ("slots assigned in DECLARATION order") describes only
|
||||
stratum 1.
|
||||
|
||||
**Symptom → verdict:** *if the target's mystery slot sits at the TOP of the frame, adjacent to the
|
||||
saved-register area, it is stratum 3 — and NO declaration-order, filler-array, `volatile`, or
|
||||
inner-block edit can reach it.* Stop looking for the missing local.
|
||||
|
||||
**Measurement recipe (30 seconds, deterministic):** delete the min/max tail, recompile, re-read
|
||||
`.frame … # vars=`. **The drop IS stratum 3's size.** (Here: exactly 96 bytes.) Run this BEFORE
|
||||
drafting anything large with a min/max tail — it converts an unbounded "which local am I missing?"
|
||||
hunt into a yes/no.
|
||||
|
||||
### B. A `?:` on MEMORY operands costs ~16 bytes of invisible frame; on REGISTER operands, zero
|
||||
|
||||
minx = MIN(outp[0][0], outp[1][0]); /* memory operands -> +16 bytes of frame */
|
||||
a = outp[0][0]; b = outp[1][0]; minx = a<b?a:b; /* register operands -> 0 bytes */
|
||||
|
||||
They are **not** interchangeable. The memory form is also what emits the target's `lhu`+`lh`
|
||||
double-read of one stack slot (the frame-size counterpart to §136-9). **So when a draft's frame is a
|
||||
multiple of 16 too large around a min/max chain, COUNT THE `?:`s before inventing a dead local**
|
||||
(§83c's trap, seen from the other side). Corollary proven here: a zero-temp tail is unreachable when
|
||||
the target re-reads its operands — 219 ins (if/else) and 234 ins (operands bound to locals) vs 246.
|
||||
|
||||
### C. Inner-block declaration does NOT delay slot allocation — BYTE-REFUTED
|
||||
|
||||
`expand_function_start` walks the whole `BLOCK` tree, so `{ … T x; … }` lands at the same stratum-1
|
||||
offset as a function-scope declaration. **Do not spend a cycle on it.**
|
||||
|
||||
### D. A lone `$t8`/`$t9` in the target is RELOAD SCRATCH — reproduce the spill, don't pin the register
|
||||
|
||||
MIPS defines no `REG_ALLOC_ORDER` (regalloc.md K3), so plain allocation never reaches `$24`/`$25`
|
||||
unless everything below is busy. An inherited draft here pinned `register short *dst2 __asm__("$24")`
|
||||
to force `lw $t8`; the pin then pushed `mfhi` off `$t8` onto `$t9` — **a diff the pin itself created.**
|
||||
The correct lever was structural: delete the `volatile` "out" local and let the `a1` parameter spill
|
||||
naturally — reload picks `$t8` for both store and reload for free, and `mfhi $t8` comes right too.
|
||||
**Generalises §17/§72: before pinning a high register, check whether the target's value is a SPILL and
|
||||
reproduce the spill instead.**
|
||||
|
||||
### E. A `qty_compare` TIE is not spelling-reachable — recognise it and stop
|
||||
|
||||
`QTY_CMP_PRI = log2(nrefs)·nrefs·size / (death − birth)`. When two quantities have equal ref counts
|
||||
and live ranges differing by one insn, the register grant flips with statement order and **both
|
||||
orders cost the same**. Here `(mw; xw; mh; yh)` buys the target's emission order and `mh → $a0` but
|
||||
transposes `w/h`; `(mw; mh; xw; yh)` buys the registers and loses the order — both exactly 5.
|
||||
|
||||
**Tell:** structure exactly right, exactly ONE register PAIR transposed, and the alternative ordering
|
||||
transposes a DIFFERENT pair. Swept here: 72 statement permutations × 4 declaration orders × 7
|
||||
s16/s32 retypings × `?:`-MAX spellings × ref-count shifts × `$v0/$v1` pins, plus the permuter — floor
|
||||
unchanged in every direction. **Worth ~an hour to recognise early.**
|
||||
|
||||
### Consequence for the family (a real scheduling decision)
|
||||
|
||||
The blocker is a **frame-layout fact about the body**, not a per-overlay symbol thing, so all 15
|
||||
siblings will hit it identically: the same draft remapped reaches 12/246 on each and **none will
|
||||
bank**. **Do not spend the 15 until stratum 3 is explained.** When it closes, `family_remap` carries
|
||||
all 16 in one pass.
|
||||
|
||||
**Symptom lines for the index:** **"a mystery stack slot at the top of the frame"** · **"frame is a
|
||||
multiple of 16 too large"** · **"a lone $t8/$t9 in the target"** · **"exactly one register pair
|
||||
transposed"** · **"permuter and hand-search plateau at the same number"**.
|
||||
|
||||
Reference in New Issue
Block a user