From bec136559ac097eab2b9800d61e88b4974edd482 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 11:03:04 -0400 Subject: [PATCH] =?UTF-8?q?phase11:=20merge=2048=20+=20gtemac=20lwc2/swc2?= =?UTF-8?q?=20+=20cookbook=20159-161=20=E2=80=94=20585=20bodies=20/=20594?= =?UTF-8?q?=20regions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker D's 0x800F3E18 (88 B) -- THE FIRST GTE/COP2 ROW MATCHED IN THIS PROJECT. 159: lwc2/swc2 move a word straight between MEMORY and COP2, unlike mtc2/mfc2 which move between a GPR and COP2. The row uses lwc2 $9/$10/$11 and swc2 $25/$26/$27, so a row can use the IR/MAC registers WITHOUT the IR/MAC macros. Added gte_lwc2IR1/2/3 and gte_swc2MAC1/2/3. 160: register variables PIN the COP2 operand registers -- worker D's entire residual was that cc1 chose its own cfc2/mfc2 destinations. The GTE analogue of the named-locals family: an inline-asm row's residual is usually the operand REGISTERS, not the sequence. 161 IS A CORRECTION TO THE COORDINATOR'S OWN ADVICE. I broadcast the command-field values with their occurrence counts as if they were a lookup table. They are a DISTRIBUTION, not a per-row answer: worker D wrote 0x178000c (counted 51x) into the row and it came out ONE BYTE wrong; the correct field is 0x170000c. The low bits carry the shift/matrix/vector selectors, so two commands differing only there are different instructions. Read the field off the ORIGINAL WORD. --- config/regions.tsv | 1 + docs/MATCHING_COOKBOOK.md | 35 +++++++++++++++++++++++++++++++++++ include/gtemac.h | 31 ++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/config/regions.tsv b/config/regions.tsv index 0ba4aad..1fa02e7 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -480,6 +480,7 @@ 0x800F3140 0x800F3160 src/func_800F3140.c 0x800F3160 0x800F316C src/func_800F3160.c maspsx=off 0x800F3A00 0x800F3A24 src/func_800F3A00.c +0x800F3E18 0x800F3E70 src/func_800F3E18.c 0x800F3E70 0x800F3E88 src/func_800F3E70.c 0x800F4098 0x800F4100 src/func_800F4098.c maspsx=epilogue 0x800F42AC 0x800F430C src/func_800F42AC.c maspsx=epilogue diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index 8f8ab28..a562e11 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -2597,3 +2597,38 @@ is sign-extended **in the jump delay slot**. > **When one function reads the same field BOTH ways, the two views are deliberate.** Do not > "simplify" them to one type — the mixed `lh`/`lhu` pair over one pointer is the evidence that the > source declared two views. + +### 159. `lwc2`/`swc2` are NOT `mtc2`/`mfc2` — a row can use IR/MAC without the IR/MAC macros (worker D) + +`lwc2`/`swc2` move a word **straight between memory and a COP2 register**; `mtc2`/`mfc2` move between +a **GPR and COP2**. Worker D's `0x800F3E18` — **the first GTE row matched in this project** — uses +`lwc2 $9/$10/$11` and `swc2 $25/$26/$27`, so **the IR/MAC registers can be used without the IR/MAC +macros.** `gte_lwc2IR1/2/3` and `gte_swc2MAC1/2/3` are now in `include/gtemac.h`. + +### 160. Register variables PIN the COP2 operand registers — the GTE analogue of named locals (worker D) + +Worker D's **entire residual** on `0x800F3E18` was that cc1 chose its own `cfc2`/`mfc2` +destinations. `register int r13 __asm__("$13");` with `"=r"(r13)` forces `cfc2 $13,...`, and the same +for `$8/$9/$10` on the matrix loads. Without them the candidate loads `a0[0..2]` in a different order +into `v0/v1/a0` and comes out one instruction long. + +> **An inline-asm row's residual is usually the operand REGISTERS, not the instruction sequence, and +> register variables are the lever.** This is the GTE analogue of the named-locals family. + +### 161. *** READ THE COMMAND FIELD OFF THE ORIGINAL WORD, NOT OFF A TABLE *** + +**A correction to the coordinator's own advice, from worker D.** I broadcast the command-field values +`0x486012`/`0x49E012`/`0x41E012` with their occurrence counts as if they were a lookup table. **They +are a distribution, not a per-row answer.** + +Worker D wrote `0x178000c` — the value I counted **51×** across the binary — into `0x800F3E18`, and +**the row came out exactly ONE BYTE wrong.** The correct field for that row is **`0x170000c`**. + +**The low bits carry the shift / matrix / vector selectors, so two commands that differ only there +are DIFFERENT instructions** — and a one-byte residual is invisible without a raw-word diff. + +> **Read the command field off the ORIGINAL WORD for the row you are working.** A count tells you a +> field is *common*, not that it is *right*. + +The save/restore of `$0/$2/$4` around the command on that row is also real — **the caller's matrix +must survive**, so the `cfc2`/`ctc2` pair is not decorative. diff --git a/include/gtemac.h b/include/gtemac.h index c6d76e2..1ca935e 100644 --- a/include/gtemac.h +++ b/include/gtemac.h @@ -139,6 +139,28 @@ #define gte_ldMAC2(v) __asm__ volatile ("mtc2 %0,$26" : : "r"(v)) #define gte_ldMAC3(v) __asm__ volatile ("mtc2 %0,$27" : : "r"(v)) +/* --- memory <-> COP2 ($9-$11, $25-$27) ----------------------------------- */ + +/* `lwc2`/`swc2` move a word straight between MEMORY and a COP2 register; they are NOT + * `mtc2`/`mfc2`, which move between a GPR and COP2. Worker D's 0x800F3E18 (the first + * GTE row matched in this project) uses `lwc2 $9/$10/$11` and `swc2 $25/$26/$27`, so a + * row can use the IR/MAC registers WITHOUT using the IR/MAC macros above. These take an + * address in a register operand. + * + * Proved by 0x800F3E18. */ +#define gte_lwc2IR1(p) __asm__ volatile ("lwc2 $9, 0(%0)" : : "r"(p)) +#define gte_lwc2IR2(p) __asm__ volatile ("lwc2 $10, 0(%0)" : : "r"(p)) +#define gte_lwc2IR3(p) __asm__ volatile ("lwc2 $11, 0(%0)" : : "r"(p)) +#define gte_swc2MAC1(p) __asm__ volatile ("swc2 $25, 0(%0)" : : "r"(p)) +#define gte_swc2MAC2(p) __asm__ volatile ("swc2 $26, 0(%0)" : : "r"(p)) +#define gte_swc2MAC3(p) __asm__ volatile ("swc2 $27, 0(%0)" : : "r"(p)) + +/* Register variables PIN the COP2 operand registers. Worker D's whole residual on + * 0x800F3E18 was that cc1 chose its own `cfc2`/`mfc2` destinations; binding the + * destination with `register int r13 __asm__("$13");` and passing `"=r"(r13)` forces it. + * This is the GTE analogue of the named-locals family: an inline-asm row's residual is + * usually the operand REGISTERS, not the instruction sequence. */ + /* --- commands ------------------------------------------------------------ */ /* The 25-bit COP2 command field is written as `cop2 0x...`, which GNU as @@ -159,7 +181,14 @@ * 0x41E012 x4 -- worker A's 0x800F3C60 * The low six bits are the GTE command number (0x12 = MVMVA in all three); the * upper bits select the matrix / vector / translation / shift fields. Do NOT - * name them semantically without evidence -- name them by field value. */ + * name them semantically without evidence -- name them by field value. + * + * *** THOSE COUNTS ARE A DISTRIBUTION, NOT A PER-ROW LOOKUP. *** Worker D wrote + * `0x178000c` (the value counted 51x) into 0x800F3E18 and the row came out ONE BYTE + * wrong: the correct field for that row is `0x170000c`. The low bits carry the + * shift / matrix / vector selectors, so two commands that differ only there are + * DIFFERENT instructions and a one-byte residual is invisible without a raw-word + * diff. READ THE COMMAND FIELD OFF THE ORIGINAL WORD, never off this list. */ #define gte_cmd(field) __asm__ volatile ("cop2 " #field) /* --- BIOS call stubs ----------------------------------------------------- */