From 096da8e4ff3e744e0c1bd2dde31b7f6cf4d4b795 Mon Sep 17 00:00:00 2001
From: Drew T <50529377+Druthulu@users.noreply.github.com>
Date: Mon, 31 Aug 2026 14:21:25 -0600
Subject: [PATCH] =?UTF-8?q?docs(cookbook):=20=C2=A7347=20loop=20regalloc?=
=?UTF-8?q?=20is=20a=20declaration-order/live-range=20dial=20(no=20live-ra?=
=?UTF-8?q?nge=20splitting=20in=20gcc-2.7.2)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/cookbook-index.md | 13 +++++++++----
docs/matching-cookbook.md | 30 ++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+), 4 deletions(-)
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.