mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 23:24:32 -04:00
docs(phase-30 S42): cookbook §148 — the loop.c hoisting threshold arithmetic, the MIN_EXPR clamp fold, the allocno-priority slider; and the x16 claim corrected
This commit is contained in:
+16
-5
@@ -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 / 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.
|
||||
> `docs/matching-cookbook.md` is ~716 KB / 424 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.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
- **§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 (16)
|
||||
### instruction scheduling (18)
|
||||
|
||||
- **§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>
|
||||
@@ -59,8 +59,10 @@
|
||||
- **§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>
|
||||
- **§148** — The loop.c hoisting THRESHOLD is arithmetic you can compute, and the `?:` clamp that folds to MIN_EXPR (P30 S42, `func_8017C6F4`, 947 ins) <sub>L10098</sub>
|
||||
- **§3-A.** — `move_movables` hoists iff `threshold × savings × lifetime ≥ insn_count` — and you can read it <sub>L10104</sub>
|
||||
|
||||
### register allocation & pins (35)
|
||||
### register allocation & pins (36)
|
||||
|
||||
- **§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>
|
||||
@@ -97,13 +99,14 @@
|
||||
- **§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>
|
||||
- **§3-C.** — A zero-byte ALLOCNO-PRIORITY slider <sub>L10137</sub>
|
||||
|
||||
### CSE / redundancy / rematerialization (2)
|
||||
|
||||
- **§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>L3306</sub>
|
||||
- **§83d** — CSE's quantity budget is WHOLE-FUNCTION, so a local rewrite cannot fix a local symptom <sub>L6445</sub>
|
||||
|
||||
### loops & induction variables (8)
|
||||
### loops & induction variables (9)
|
||||
|
||||
- **§3-T1** — Loop pointer: top-of-body for `addu` induction, not constant-folded `addiu` <sub>L71</sub>
|
||||
- **§34** — The `func_80138ED0` giant crack: gcc-2.7.2's **3-qty sort bug** + the **zero-byte asm allocation toolkit** + the **giv-init fence** (Phase 24 T5; Opus→close=21, Fable5→MATCH ×134) <sub>L2447</sub>
|
||||
@@ -113,6 +116,7 @@
|
||||
- **§66d-1** — What transfers between giants is the LOOP, not the PIN <sub>L5273</sub>
|
||||
- **§70** — The giv-init base register: walk the PARAMETER, not a copy of it (Phase 29 SESSION-18, `func_801777BC`) <sub>L5612</sub>
|
||||
- **§145** — Three loop/combine levers from the S40 wave-2 drafters (16/16 match_one) <sub>L9917</sub>
|
||||
- **§148** — The loop.c hoisting THRESHOLD is arithmetic you can compute, and the `?:` clamp that folds to MIN_EXPR (P30 S42, `func_8017C6F4`, 947 ins) <sub>L10098</sub>
|
||||
|
||||
### structs, block moves & memcpy (30)
|
||||
|
||||
@@ -563,7 +567,7 @@
|
||||
- **§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)
|
||||
### (unbucketed — title matched no symptom vocabulary) (122)
|
||||
|
||||
- **§3-How** — to use this <sub>L30</sub>
|
||||
- **§1** — Idiom catalog (asm pattern → C that produces it) <sub>L39</sub>
|
||||
@@ -685,6 +689,8 @@
|
||||
- **§144** — THE LITERAL'S SPELLING PICKS THE IMMEDIATE ENCODING (P30 S40 wave 1, `func_801822E0`) <sub>L9878</sub>
|
||||
- **§3-Why** — a correct draft can read as an intrinsic wall <sub>L9978</sub>
|
||||
- **§3-The** — rule <sub>L10013</sub>
|
||||
- **§3-B.** — `(v < 0x40) ? v : 0x3F` is folded to `MIN_EXPR` and expands to the WRONG SHAPE <sub>L10123</sub>
|
||||
- **§3-D.** — Reproduce the original's BUGS verbatim <sub>L10147</sub>
|
||||
|
||||
|
||||
## All sections, in order
|
||||
@@ -1108,3 +1114,8 @@
|
||||
- **§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>
|
||||
- **§148** — The loop.c hoisting THRESHOLD is arithmetic you can compute, and the `?:` clamp that folds to MIN_EXPR (P30 S42, `func_8017C6F4`, 947 ins) <sub>L10098</sub>
|
||||
- **§3-A.** — `move_movables` hoists iff `threshold × savings × lifetime ≥ insn_count` — and you can read it <sub>L10104</sub>
|
||||
- **§3-B.** — `(v < 0x40) ? v : 0x3F` is folded to `MIN_EXPR` and expands to the WRONG SHAPE <sub>L10123</sub>
|
||||
- **§3-C.** — A zero-byte ALLOCNO-PRIORITY slider <sub>L10137</sub>
|
||||
- **§3-D.** — Reproduce the original's BUGS verbatim <sub>L10147</sub>
|
||||
|
||||
@@ -10092,3 +10092,80 @@ 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"**.
|
||||
|
||||
---
|
||||
|
||||
## §148 — The loop.c hoisting THRESHOLD is arithmetic you can compute, and the `?:` clamp that folds to MIN_EXPR (P30 S42, `func_8017C6F4`, 947 ins)
|
||||
|
||||
Serial run #2. Reached **NEAR(63)** of 947 (frame `0x120` exact, `vars=232` exact, all opcodes,
|
||||
immediates, stack offsets and branch targets correct; residual is one register rotation). Did not
|
||||
bank. The levers below are the yield.
|
||||
|
||||
### A. `move_movables` hoists iff `threshold × savings × lifetime ≥ insn_count` — and you can read it
|
||||
|
||||
`loop.c:1631` sets **`threshold = 58`** for this MIPS config and **decrements it by 3 per movable
|
||||
already moved** (`threshold -= 3`, loop.c:1719/1904). This is the first *quantitative* handle we have
|
||||
on gcc-2.7.2 invariant motion.
|
||||
|
||||
Measured here: the draft's prim loop was **513** RTL insns, so `&g.flag` (savings 3, lifetime 3 →
|
||||
`58×3×3 = 522 ≥ 513`) got hoisted into a preheader register that the target recomputes **inline** —
|
||||
costing a callee-saved register and cascading into 2 extra spills and **+96 instructions**.
|
||||
Duplicating the `if (za < g.sz2) za = g.sz2;` tail into **both** arms of the `sz0>sz1` test (which the
|
||||
target's cross-jump reveals) pushed the loop to **523** insns → `522 < 523` → **not hoisted** → the
|
||||
register came back.
|
||||
|
||||
**Symptom → lever:** an address (`&x`) hoisted into a loop-preheader register that the target
|
||||
recomputes inline ⇒ **raise the loop's RTL insn count by ~10, or lower the movable's ref count.**
|
||||
**Read it directly:** `cc1 -dL` writes `<file>.i.loop`, which prints
|
||||
`Loop from A to B: N real insns` and, per movable, `Insn K: regno R (life L), savings S
|
||||
moved/not desirable`. Stop guessing which invariant moved — the dump names it.
|
||||
|
||||
### B. `(v < 0x40) ? v : 0x3F` is folded to `MIN_EXPR` and expands to the WRONG SHAPE
|
||||
|
||||
`fold-const.c:4948` — `A < C1 ? A : C2` with `C1 == C2+1` becomes `MIN(A,C2)`, which expands as
|
||||
**copy-then-conditionally-overwrite** (`move t,v; slti; bnez; li`). `A <= C ? A : C` folds too (the
|
||||
`A op B ? A : B` rule at 4907).
|
||||
|
||||
(v < 0x40) ? v : 0x3F -> MIN_EXPR -> move/slti/bnez/li WRONG
|
||||
(v > 0x3F) ? 0x3F : v -> no fold -> jumpifnot/store/j/store RIGHT
|
||||
|
||||
Same `slti $v0,$v,0x40` is emitted either way — but the second keeps gcc's canonical branchy form.
|
||||
**This single respelling took the draft from close 827 to close 63 and fixed all four clamps
|
||||
instruction-for-instruction.** Corollary: `(v < 0) ? 0 : X` is safe — fold's "swap if arg1 is simpler"
|
||||
rewrites it to `(v >= 0) ? X : 0`, which is exactly the target's `bltz`.
|
||||
|
||||
### C. A zero-byte ALLOCNO-PRIORITY slider
|
||||
|
||||
__asm__ ("" :: "r"(a), "r"(b)); /* emits nothing */
|
||||
|
||||
Priority is `floor_log2(n_refs)·n_refs·size / live_length`, and `REG_N_REFS` is incremented **by loop
|
||||
depth** — so an empty asm with `"r"` inputs inside a loop adds `depth` references per operand while
|
||||
emitting **no code**. Used here it flipped `{rowptr,y}` ↔ `{cx1,cy1}` for the last two callee-saved
|
||||
registers. **This is the counterpart to §47's live-length slider: that one moves the DENOMINATOR,
|
||||
this one moves the NUMERATOR.**
|
||||
|
||||
### D. Reproduce the original's BUGS verbatim
|
||||
|
||||
This variant's F3 arm bbox-tests the packet through **PolyFT3** offsets (pkt+8/+0x10/+0x18, stride 8)
|
||||
while writing F3 xy at stride 4; the FT4 arm reads `tmpxy[3].vx` where `.vy` is meant. Both are
|
||||
original-source copy/paste bugs. Matching means reproducing them.
|
||||
|
||||
### ⚠️ E. A "these are all the same function" claim needs the DRAFT test, not a diff
|
||||
|
||||
The run reported all sixteen 947-ins instances as one identical body (→ "one crack banks 15,152 ins").
|
||||
**Checked and it does not hold:** the draft scores **63** on `ov_SC03_126` but **340** on
|
||||
`func_8017C59C` and `func_8017CF90` — with an *identical* first diff on both, i.e. those two match
|
||||
each other but not the cracked one. That is consistent with the h_norm clustering (947×3, 947×2, plus
|
||||
singletons): **several multi-instance groups, not one group of 16.** A normalized-stream diff can say
|
||||
"same shape"; only *running the actual draft against the sibling's asm* says "same body". **Cheap
|
||||
test, do it before scaling a ×N claim** (`sed s/func_A/func_B/` the draft and `match_one` it).
|
||||
|
||||
### Tooling note
|
||||
|
||||
`permuter_ils` cannot be aimed at this draft: `run_masked` reports *"Function … not found in
|
||||
base.c"* because the `gte_*` `#define` block defeats `make_base_c`. **Demacroize first** — worth doing
|
||||
generally, since any GTE-using draft hits it.
|
||||
|
||||
**Symptom lines for the index:** **"an &address hoisted into a loop preheader"** · **"a clamp expands
|
||||
as move-then-overwrite"** · **"MIN_EXPR"** · **"two callee-saved registers swapped"** · **"permuter
|
||||
says function not found in base.c"**.
|
||||
|
||||
Reference in New Issue
Block a user