diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index ab0f5415f..3640006ec 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 / 995 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 / 996 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. @@ -176,7 +176,7 @@ - **§340** — §194-K COROLLARY: **FLIP THE FALSE EDGE YOU CANNOT DELETE.** A "scheduler" residual can be sched.c's ALIAS ORACLE emitting a FALSE true-dependence; source order chooses its DIRECTION (P31 S67; byte-proven ov_SC03_107/func_8017CF48, 10 -> 0 in one compile, zero bytes) L31209 - **§341** — AN HImode STORE TEMP REWEIGHTS A sched2 TIE-BREAK THAT NO STATEMENT ORDER CAN REACH (P31 S67; byte-proven ov_SC03_006/func_801823B8, last 4 ins) L31243 -### register allocation & pins (120) +### register allocation & pins (121) - **§10** — Closing the regalloc/scheduling hard tail by hand (LZSS, Phase 7 session F — the full close) L854 - **Residual** — A — commutative `|`/`&`/`+` result lands in the wrong source-operand register L875 @@ -298,6 +298,7 @@ - **§334** — A RELOAD SPILL SLOT IS ROUNDED TO `BIGGEST_ALIGNMENT` (8B), SO ONE SPILLED 4-BYTE PSEUDO CAN GROW THE FRAME BY 16 (P31 S67; byte-proven ov_SC05_008/func_8017DF68, 82 -> 53 residual) L31129 - **§342** — A `void *` PARAMETER CAST TO ITS REAL TYPE IN A LOCAL IS **NOT** BYTE-NEUTRAL WHEN A LATER PARAMETER ALSO NEEDS A CALLEE-SAVED REGISTER (P31 S67; byte-proven ov_SC07_002/func_80181394, NEW LAW) L31257 - **§344** — RAISE A BIV'S global_alloc PRIORITY WITH A ZERO-BYTE REFERENCE INSTEAD OF PINNING IT; PINNING THE COUNTER KILLS LSR ENTIRELY (P31 S67; byte-proven ov_SC03_121/func_80180E64, 222 ins) L31296 +- **§347** — LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEVERS, 178 -> 0 (P31 S67; byte-proven ov_SC06_029/func_8017EF34, 243 ins) L31342 ### CSE / redundancy / rematerialization (37) @@ -339,7 +340,7 @@ - **§328** — THE VOLATILE ALIAS MUST BE AN *OBJECT*, NOT A CAST: `*(volatile s32*)&sym` UNFOLDS %lo INTO A SEPARATE `addiu` (P31 S67; byte-proven ov_SC07_007/func_80181B8C, 156 ins, NEW LAW) L31024 - **§345** — A VOLATILE **STORE** EVICTS THE MEM FROM cse AND KEEPS `sh`; A VOLATILE **LOAD** BLOCKS combine AND DEGRADES `lh` INTO `lhu+sll+sra` (P31 S67; byte-proven ov_SC01_084/func_80181A7C) L31312 -### loops & induction variables (36) +### loops & induction variables (37) - **§3-T1** — Loop pointer: top-of-body for `addu` induction, not constant-folded `addiu` L71 - **§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) L2473 @@ -377,6 +378,7 @@ - **§325** — REGALLOC: A SHARED SMALL CONSTANT STORED TWICE IN THE PRE-LOOP BLOCK IS A *local-alloc* $s-OCCUPANT THAT STEALS THE ARGUMENT ALLOCNO'S REGISTER — PIN THE ARGUMENT-DERIVED LOCAL, NOT THE CONSTANT (P31 S67; byte-proven ov_SC03_119/func_80184F18, 153 ins) L30987 - **§344** — RAISE A BIV'S global_alloc PRIORITY WITH A ZERO-BYTE REFERENCE INSTEAD OF PINNING IT; PINNING THE COUNTER KILLS LSR ENTIRELY (P31 S67; byte-proven ov_SC03_121/func_80180E64, 222 ins) L31296 - **§346** — `c ? X : -X` TAKES expand_expr's COND_EXPR **SINGLETON** PATH (copy, then negate IN PLACE) — AN if/else STATEMENT GIVES THE TWO-ARM FORM (P31 S67; byte-proven ov_SC03_102/func_80180C38, closed the last instruction) L31322 +- **§347** — LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEVERS, 178 -> 0 (P31 S67; byte-proven ov_SC06_029/func_8017EF34, 243 ins) L31342 ### structs, block moves & memcpy (80) @@ -553,7 +555,7 @@ - **§342** — A `void *` PARAMETER CAST TO ITS REAL TYPE IN A LOCAL IS **NOT** BYTE-NEUTRAL WHEN A LATER PARAMETER ALSO NEEDS A CALLEE-SAVED REGISTER (P31 S67; byte-proven ov_SC07_002/func_80181394, NEW LAW) L31257 - **§345** — A VOLATILE **STORE** EVICTS THE MEM FROM cse AND KEEPS `sh`; A VOLATILE **LOAD** BLOCKS combine AND DEGRADES `lh` INTO `lhu+sll+sra` (P31 S67; byte-proven ov_SC01_084/func_80181A7C) L31312 -### declarations, prototypes & K&R (105) +### declarations, prototypes & K&R (106) - **§3-T4** — Branch polarity: invert the source condition to flip gcc's chosen branch L90 - **§8c** — Splitting a TU means rebuilding its DECLARATION ENVIRONMENT, not moving text (Phase 26 session 6) L456 @@ -660,6 +662,7 @@ - **§335** — AN `extern u16 A[]` READ AT A VARIABLE SUBSCRIPT ALLOCATES DEAD STACK TEMPS (~8B PER ACCESS) THAT INFLATE THE FRAME WHILE EMITTING ZERO EXTRA INSTRUCTIONS (P31 S67; byte-proven ov_SC06_025/func_8017EA74, closeness 141 -> 20) L31140 - **§342** — A `void *` PARAMETER CAST TO ITS REAL TYPE IN A LOCAL IS **NOT** BYTE-NEUTRAL WHEN A LATER PARAMETER ALSO NEEDS A CALLEE-SAVED REGISTER (P31 S67; byte-proven ov_SC07_002/func_80181394, NEW LAW) L31257 - **§343** — `decl_prior`'s FLEET MAJORITY CAN BE WRONG ABOUT THE TRUE SIGNATURE — READ THE RIVALS, NOT JUST THE WINNER (P31 S67; measured on func_8012BD14 / func_8012D624 / func_80143C74) L31274 +- **§347** — LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEVERS, 178 -> 0 (P31 S67; byte-proven ov_SC06_029/func_8017EF34, 243 ins) L31342 ### jump tables & switches (51) @@ -2529,6 +2532,7 @@ - **§344** — RAISE A BIV'S global_alloc PRIORITY WITH A ZERO-BYTE REFERENCE INSTEAD OF PINNING IT; PINNING THE COUNTER KILLS LSR ENTIRELY (P31 S67; byte-proven ov_SC03_121/func_80180E64, 222 ins) L31296 - **§345** — A VOLATILE **STORE** EVICTS THE MEM FROM cse AND KEEPS `sh`; A VOLATILE **LOAD** BLOCKS combine AND DEGRADES `lh` INTO `lhu+sll+sra` (P31 S67; byte-proven ov_SC01_084/func_80181A7C) L31312 - **§346** — `c ? X : -X` TAKES expand_expr's COND_EXPR **SINGLETON** PATH (copy, then negate IN PLACE) — AN if/else STATEMENT GIVES THE TWO-ARM FORM (P31 S67; byte-proven ov_SC03_102/func_80180C38, closed the last instruction) L31322 +- **§347** — LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEVERS, 178 -> 0 (P31 S67; byte-proven ov_SC06_029/func_8017EF34, 243 ins) L31342 --- @@ -3536,3 +3540,4 @@ Notes routinely quote that as a section id. This table resolves it. Grep bait: ` | L31296 | §344 | RAISE A BIV'S global_alloc PRIORITY WITH A ZERO-BYTE REFERENCE INSTEAD OF PINNING IT; PINN | | L31312 | §345 | A VOLATILE **STORE** EVICTS THE MEM FROM cse AND KEEPS `sh`; A VOLATILE **LOAD** BLOCKS co | | L31322 | §346 | `c ? X : -X` TAKES expand_expr's COND_EXPR **SINGLETON** PATH (copy, then negate IN PLACE) | +| L31342 | §347 | LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEV | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index e8786f921..b54a2f9a9 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -31338,3 +31338,33 @@ Also recorded because it saves a compile: the TU declares `D_800AE620` one way a later declares it as `Blk20_80181260`. A two-body compile confirmed cc1 only WARNS (exit 0) and the bytes are unchanged — there is no `-Werror` in the Makefile or `gate_stage`. A warning is not a blocker; check the exit code before rewriting a declaration to silence one. + +## §347 — LOOP REGISTER ASSIGNMENT IS A **DECLARATION-ORDER + LIVE-RANGE** DIAL: FIVE COMPOSABLE LEVERS, 178 -> 0 (P31 S67; byte-proven ov_SC06_029/func_8017EF34, 243 ins) + +One function exercised five independent loop/branch dials in sequence. Each is reusable on its own, +and the ORDER they were applied in is the ladder: + +1. **DUPLICATE shared tails per-case and let cross-jumping re-merge them (178 -> 53).** Writing a + hand `goto` to a shared label mislays the blocks and leaves the argument setup in the wrong basic + block. Write the tail out in each arm; gcc merges it back correctly. (Cf. §224/§5a — this is the + same law seen from the authoring side.) +2. **Sequential guarded `if`s, NOT `if/else if` (53 -> 34)** — gcc-2.7.2 swaps the else-if chain. +3. **Initialise the POINTER before the counter, and order the body `p++; i++` (34 -> 14).** Loop + register allocation follows LIVE-RANGE-LENGTH priority, so setting `i` first pushes it to `$a1`. +4. **ONE shared counter + a DISTINCT pointer variable per loop (14 -> 6)** — that makes the counter + the top allocno, so it takes `$a0` while still being written first, reproducing the target's + preheader order. +5. **Assign an AND mask to an explicit local between `i = 0` and the pointer init (6 -> 0)** — so + `addiu $a2,-3` precedes the `la` instead of being appended by loop-invariant motion. + +**THE UNIFYING RULE:** gcc-2.7.2 has **no live-range splitting** — one C variable is one hard +register for its whole life. So the register assignment in a loop is chosen by (a) how many distinct +variables you declare, (b) their declaration/initialisation ORDER, and (c) their live-range lengths. +Splitting one reused variable into several, or merging several into one, is the primary register dial +— not pins. Same session, `ov_SC03_105/func_80182DCC`: **three separate `range` locals fixed all 39 +register mismatches at once**, for exactly this reason. + +Companion from that function, worth its own note: **`sp20[1] -= 0x20; sp20[1] -= i*4;` must be +SEPARATE statements on an `s32` temp.** Any single expression lets gcc associate `0x20` into the giv, +and `loop.c` strength-reduces `0x20 + 4*i` into one register, while a bare `4*i` giv sits below the +worth-while threshold.