docs(cookbook): §347 loop regalloc is a declaration-order/live-range dial (no live-range splitting in gcc-2.7.2)

This commit is contained in:
Drew T
2026-08-31 14:21:25 -06:00
parent 675b4702b3
commit 096da8e4ff
2 changed files with 39 additions and 4 deletions
+9 -4
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 / 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) <sub>L31209</sub>
- **§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) <sub>L31243</sub>
### 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) <sub>L854</sub>
- **Residual** — A — commutative `|`/`&`/`+` result lands in the wrong source-operand register <sub>L875</sub>
@@ -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) <sub>L31129</sub>
- **§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) <sub>L31257</sub>
- **§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) <sub>L31296</sub>
- **§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) <sub>L31342</sub>
### 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) <sub>L31024</sub>
- **§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) <sub>L31312</sub>
### loops & induction variables (36)
### loops & induction variables (37)
- **§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>L2473</sub>
@@ -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) <sub>L30987</sub>
- **§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) <sub>L31296</sub>
- **§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) <sub>L31322</sub>
- **§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) <sub>L31342</sub>
### 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) <sub>L31257</sub>
- **§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) <sub>L31312</sub>
### declarations, prototypes & K&R (105)
### declarations, prototypes & K&R (106)
- **§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>L456</sub>
@@ -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) <sub>L31140</sub>
- **§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) <sub>L31257</sub>
- **§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) <sub>L31274</sub>
- **§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) <sub>L31342</sub>
### 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) <sub>L31296</sub>
- **§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) <sub>L31312</sub>
- **§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) <sub>L31322</sub>
- **§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) <sub>L31342</sub>
---
@@ -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 |
+30
View File
@@ -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.