phase11: merge 48 + gtemac lwc2/swc2 + cookbook 159-161 — 585 bodies / 594 regions

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.
This commit is contained in:
Christopher Williams
2026-09-24 11:03:04 -04:00
parent f27691f6c3
commit bec136559a
3 changed files with 66 additions and 1 deletions
+1
View File
@@ -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
1 # Code-region registry: one C region per matched function.
480 0x800F3140
481 0x800F3160
482 0x800F3A00
483 0x800F3E18
484 0x800F3E70
485 0x800F4098
486 0x800F42AC
+35
View File
@@ -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.
+30 -1
View File
@@ -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 ----------------------------------------------------- */