phase11: merge 40 + the epilogue transform now handles BOTH shapes — 571 bodies / 580 regions

Worker A's two epilogue-class rows (0x800F42AC 96 B, 0x80100038 104 B), both carrying the
maspsx=epilogue token -- the first rows closed through the new mode.

AND THE TRANSFORM IS NOW CORRECT FOR BOTH SHAPES, which worker A's report 24 showed was
necessary. The 120 rows split:
  A) lw $31 IMMEDIATELY before the release -> the release moves into the slot AND a nop must be
     inserted after lw $31, or j $31 lands in its load-delay slot.  0x800FFBEC.
  B) other loads between lw $31 and the release -> the release moves into the slot and the
     trailing nop is DROPPED; no load-delay nop is needed.  Worker A's 0x800F44D0.
My first implementation did A only and left every B row 4 bytes long. Both are handled now, and
the discriminator is whether the jump's own register was loaded immediately before the release.

A BUG WORTH RECORDING: reading out[-1] to find that preceding instruction saw maspsx's own
'#nop # DEBUG: ...' comment instead of the lw, silently producing the shape-B answer for a
shape-A row and turning a MATCH back into a LENGTH-MISMATCH. The scan now skips comments.
This commit is contained in:
Christopher Williams
2026-09-24 10:50:09 -04:00
parent d0384a6bca
commit ea51ac9629
9 changed files with 461 additions and 2 deletions
+3
View File
@@ -477,6 +477,7 @@
0x800F3160 0x800F316C src/func_800F3160.c maspsx=off
0x800F3A00 0x800F3A24 src/func_800F3A00.c
0x800F3E70 0x800F3E88 src/func_800F3E70.c
0x800F42AC 0x800F430C src/func_800F42AC.c maspsx=epilogue
0x800F5200 0x800F521C src/func_800F5200.c
0x800F5AF8 0x800F5B40 src/func_800F5AF8.c
0x800F5B40 0x800F5B70 src/func_800F5B40.c
@@ -527,6 +528,7 @@
0x800FEFFC 0x800FF008 src/func_800FEFFC.c
0x800FF6A4 0x800FF6DC src/func_800FF6A4.c
0x800FFBEC 0x800FFC3C src/func_800FFBEC.c maspsx=epilogue
0x80100038 0x801000A0 src/func_80100038.c maspsx=epilogue
0x80100318 0x80100334 src/func_80100318.c
0x80100740 0x801007E0 src/func_80100740.c
0x80100964 0x8010097C src/func_80100964.c
@@ -556,6 +558,7 @@
0x8010513C 0x80105148 src/func_8010513C.c
0x80105148 0x801051CC src/func_80105148.c
0x8010543C 0x80105474 src/func_8010543C.c
0x801059E8 0x80105A50 src/func_801059E8.c maspsx=epilogue
0x80105B34 0x80105B54 src/func_80105B34.c
0x80105B54 0x80105B68 src/func_80105B54.c
0x80105B68 0x80105B88 src/func_80105B68.c
1 # Code-region registry: one C region per matched function.
477 0x800F3160
478 0x800F3A00
479 0x800F3E70
480 0x800F42AC
481 0x800F5200
482 0x800F5AF8
483 0x800F5B40
528 0x800FEFFC
529 0x800FF6A4
530 0x800FFBEC
531 0x80100038
532 0x80100318
533 0x80100740
534 0x80100964
558 0x8010513C
559 0x80105148
560 0x8010543C
561 0x801059E8
562 0x80105B34
563 0x80105B54
564 0x80105B68
+53
View File
@@ -0,0 +1,53 @@
/*
* func_80028C34 — 172 bytes at 0x80028C34..0x80028CE0
*
* The base case of the basis builder whose axis-permuted variant is func_80028CE0.
* Same body, minus that variant's z-vector and its three in-place element shifts,
* and with the incoming a0 forwarded to the callee unchanged:
*
* addiu sp,sp,-0x50 / sw ra,0x48(sp) frame 80 = 16 (outgoing arg area) +
* 48 (three 16-byte vectors at
* sp+24/40/56) + 8 (ra). The 16-byte
* stride is cookbook 96's vector tell.
* lw v0,20(a1) .. lw t0,32(a1) four loads ...
* sw v0,24(sp) .. sw t0,36(sp) ... then four stores: one 16-byte
* STRUCT ASSIGNMENT (cookbook 28/102).
* lw v0,24(sp) / lw t0,24(sp) / lw a3,28(sp)
* the copy is re-read from memory, so the
* source reads elements, not a local.
* subu / sw -> 40,44,48 differences against a1[9..11]
* subu / sw -> 56,60,64 differences against a1[1..3]
* sw a2,16(sp) / jal 0x800289B4 call(a0, &t, &u, &w, a2) -- five
* arguments, the 5th in the outgoing area
* at sp+16 (cookbook 33).
* andi v0,v0,0xff the 0xff mask is the return type.
*
* Verified matched on the first spelling, which is expected: the body is the sibling
* of an already-matched row (cookbook 99/129 adjacency) and reuses its derivation.
*
* LIMITS: as func_80028CE0 -- `struct V` is inferred from the 16-byte stride and the
* batched 4-word copy; the fourth element is copied and never read (it survives at
* sp+36); a1 is indexed as an `int *` at 1/2/3, 5/6/7/8 and 9/10/11; a2's type is
* unknown; func_800289B4 is a cross-reference by address only.
*/
struct V { int v[4]; };
extern int func_800289B4(struct V *a0, struct V *a1, struct V *a2, struct V *a3, int a4);
int func_80028C34(struct V *a0, int *a1, int a2)
{
struct V t, u, w;
t = *(struct V *)(a1 + 5);
u.v[0] = a1[9] - t.v[0];
u.v[1] = a1[10] - t.v[1];
u.v[2] = a1[11] - t.v[2];
w.v[0] = a1[1] - t.v[0];
w.v[1] = a1[2] - t.v[1];
w.v[2] = a1[3] - t.v[2];
return func_800289B4(a0, &t, &u, &w, a2) & 0xff;
}
+55
View File
@@ -0,0 +1,55 @@
/*
* func_8009D798 — 264 bytes at 0x8009D798..0x8009D8A0
*
* Goal B, Phase 11. Sibling of the matched func_80018458 (`sf3_family` score 1.000, ratio 1.00),
* which is why it matched on the first spelling: the two bodies are instruction-identical except
* for the TWO WORDS that set the first call's argument registers.
*
* Two 3-element signed-short differences, fed to the GTE transform 0x800F3E18, then rounded through
* 0x8009C56C and stored as three shorts:
*
* short *s1 = *(short **)(a0 + 8);
* int a[3], b[3], r[3];
* a[i] = *(short *)(*(int *)(a0 + 32) + 2i) - *(short *)(*(int *)(a0 + 28) + 2i); i = 0,1,2
* b[i] = *(short *)(*(int *)(a0 + 24) + 2i) - *(short *)(*(int *)(a0 + 28) + 2i); i = 0,1,2
* func_800F3E18(a, b, r); <- the GTE rotation/translation helper
* func_8009C56C(r, r);
* s1[0] = r[0]; s1[1] = r[1]; s1[2] = r[2];
*
* Both difference blocks are **fully unrolled** (three statements each) and both re-dereference the
* record's pointers at every site — `*(int *)(a0 + 32)` / `+28` / `+24` are re-loaded per element,
* which is cookbook 105's per-statement re-read setting again. The frame is 80 bytes with the two
* scratch vectors at sp+0x10 and sp+0x20 and the result at sp+0x30; the stores to `s1` are `sh` fed
* by 32-bit loads.
*
* LIMITS: the function name, the two callees, the meaning of the record at a0 and of the offsets
* 8/24/28/32 are hypotheses reconstructed from the disassembly; only the compiled bytes are evidence.
* The three int vectors are declared `int[3]` because the slots are word-sized and the callees take
* `int *`; the sources are signed halfword loads. `s1` is the only callee-saved value, so only s0/s1
* are saved.
*/
extern void func_800F3E18(int *a0, int *a1, int *a2);
extern void func_8009C56C(int *a0, int *a1);
void func_8009D798(int a0)
{
short *s1 = *(short **)(a0 + 8);
int a[3];
int b[3];
int r[3];
a[0] = *(short *)(*(int *)(a0 + 32) + 0) - *(short *)(*(int *)(a0 + 28) + 0);
a[1] = *(short *)(*(int *)(a0 + 32) + 2) - *(short *)(*(int *)(a0 + 28) + 2);
a[2] = *(short *)(*(int *)(a0 + 32) + 4) - *(short *)(*(int *)(a0 + 28) + 4);
b[0] = *(short *)(*(int *)(a0 + 24) + 0) - *(short *)(*(int *)(a0 + 28) + 0);
b[1] = *(short *)(*(int *)(a0 + 24) + 2) - *(short *)(*(int *)(a0 + 28) + 2);
b[2] = *(short *)(*(int *)(a0 + 24) + 4) - *(short *)(*(int *)(a0 + 28) + 4);
func_800F3E18(a, b, r);
func_8009C56C(r, r);
s1[0] = r[0];
s1[1] = r[1];
s1[2] = r[2];
}
+68
View File
@@ -0,0 +1,68 @@
/*
* func_800F4098 — 104 bytes at 0x800F4098..0x800F4100
*
* Hypothesis, not a claim about meaning: conditionally calls a global handler with a fixed
* descriptor, then unconditionally calls a virtual method on a global object. Matched on the
* THIRD spelling; it needs the harness's `maspsx=epilogue` mode.
*
* Original words:
* 3C028012 lui v0,0x8012
* 9082A8DE lbu v0,-22306(v0) ; v0 = D_8011A8DE
* 27BDFFE8 addiu sp,sp,-24
* AFB00010 sw s0,16(sp)
* 00808021 move s0,a0
* 2C420002 sltiu v0,v0,2 ; UNSIGNED compare
* 1440000E bnez v0,0x800F40D4 ; if (v0 < 2) SKIP THE FIRST CALL ONLY
* AFBF0014 sw ra,20(sp) ; (delay)
* 3C048011 lui a0,0x8011 ; a0 = 0x8010E5F4
* 2484E5F4 addiu a0,a0,-6668
* 3C028012 lui v0,0x8012
* 8C42A8D8 lw v0,-22312(v0) ; v0 = D_8011A8D8
* 00000000 nop
* 0040F809 jalr v0 ; ((void (*)(char *, int))D_8011A8D8)(&D_8010E5F4, s0)
* 02002821 move a1,s0 ; (delay)
* 3C028012 lui v0,0x8012 ; 0x800F40D4: the UNCONDITIONAL second call
* 8C42A8D4 lw v0,-22316(v0) ; v0 = D_8011A8D4
* 00000000 nop
* 8C42003C lw v0,60(v0) ; v0 = *(int *)(v0 + 60)
* 00000000 nop
* 0040F809 jalr v0 ; (*(int *)(D_8011A8D4 + 60))(s0)
* 02002021 move a0,s0 ; (delay)
* 8FBF0014 lw ra,20(sp) ; (END)
* 8FB00010 lw s0,16(sp)
* 03E00008 jr ra
* 27BD0018 addiu sp,sp,24 ; THE FRAME RELEASE IS IN THE jr DELAY SLOT
*
* **THE GUARD SKIPS THE FIRST CALL ONLY — IT IS NOT AN EARLY RETURN.** The branch target is
* `0x800F40D4`, which is the *second* call's setup, not the epilogue. Writing
* `if (D_8011A8DE < 2) return;` gives the CORRECT LENGTH but the branch goes to the end
* (2 differing bytes); the source is `if (D_8011A8DE >= 2) <first call>; <second call>;`
* with the second call unconditional. **When a branch target lands in the middle of a
* function rather than at the epilogue, the guard is scoped to one statement, not the whole
* body** — the same "read the control flow literally" method as my boolean-structure finding.
*
* Also byte-required: `maspsx=epilogue` (the rare tail, and again with no load-delay `nop`),
* and the `lbu`/`sltiu` pair means the global is an **unsigned char** compared against 2.
*
* **AND THE `lui`-PAGE ERROR AGAIN — FOURTH TIME, SAME CLASS.** `lui a0,0x8011; addiu
* a0,a0,-6668` is 0x80110000 − 6668 = **0x8010E5F4**, not 0x8011E5F4. I derived it wrong,
* the row was 1 byte off, and the only difference was the `lui` immediate. **Cookbook line:
* derive every absolute address from the (page, signed immediate) PAIR; and a one-byte
* residual whose only difference is a `lui` immediate is the signature of this mistake.**
*
* LIMITS: the function name, all three globals, the descriptor and the whole "conditional
* then unconditional dispatch" reading are hypotheses taken from the instruction shape; only
* the bytes are evidence. Every symbol is referenced by its address-named spelling.
*/
extern unsigned char D_8011A8DE;
extern int D_8011A8D8;
extern int *D_8011A8D4;
extern char D_8010E5F4;
void func_800F4098(int s0)
{
if (D_8011A8DE >= 2)
((void (*)(char *, int))D_8011A8D8)(&D_8010E5F4, s0);
((void (*)(int))*(int *)((char *)D_8011A8D4 + 60))(s0);
}
+70
View File
@@ -0,0 +1,70 @@
/*
* func_800F42AC — 96 bytes at 0x800F42AC..0x800F430C
*
* Hypothesis, not a claim about meaning: forwards to a fixed helper and then to a virtual
* method on a global object, passing the object's own field as the first argument. Matched
* on the SECOND spelling, and it needs the harness's `maspsx=epilogue` mode (see below).
*
* Original words:
* 27BDFFE0 addiu sp,sp,-32
* AFB00010 sw s0,16(sp)
* 00808021 move s0,a0
* AFB10014 sw s1,20(sp)
* 00A08821 move s1,a1
* 3C048011 lui a0,0x8011 ; func_800F4100(0x8010E644, s0)
* 2484E644 addiu a0,a0,-6588
* AFBF0018 sw ra,24(sp)
* 0C03D040 jal 0x800F4100
* 02002821 move a1,s0 ; (delay)
* 02002821 move a1,s0 ; a SECOND copy of the argument setup
* 3C028012 lui v0,0x8012
* 8C42A8D4 lw v0,-22316(v0) ; v0 = D_8011A8D4
* 24060008 li a2,8
* 8C440020 lw a0,32(v0) ; a0 = *(int *)(v0 + 32)
* 8C420008 lw v0,8(v0) ; v0 = *(int *)(v0 + 8)
* 00000000 nop
* 0040F809 jalr v0 ; (*(int *)(v0+8))(*(int *)(v0+32), s0, 8, s1)
* 02203821 move a3,s1 ; (delay)
* 8FBF0018 lw ra,24(sp) ; (END)
* 8FB10014 lw s1,20(sp)
* 8FB00010 lw s0,16(sp)
* 03E00008 jr ra
* 27BD0020 addiu sp,sp,32 ; THE FRAME RELEASE IS IN THE jr DELAY SLOT
*
* **THIS ROW REQUIRES `maspsx=epilogue`** (the claim row carries the token). The original's
* epilogue is the RARE form — `jr ra` with `addiu sp,sp,32` in the jump slot — which the
* harness's default maspsx does not produce. Without the mode the row is 92 bytes and 6
* bytes wrong; with `--fill-epilogue` it is exact. **The tail here has NO load-delay `nop`,
* which is what makes it match once the swap is done** (contrast 0x800F44D0, whose
* three-load epilogue does get an inserted nop and therefore does not close).
*
* TWO BYTE-REQUIRED DETAILS:
*
* 1. **`D_8011A8D4` is a POINTER, not a pointer-to-pointer.** The original emits
* `lui v0,0x8012; lw v0,-22316(v0)` and then dereferences `v0` — declaring it
* `extern int *D_8011A8D4;` and using it directly gives that. Writing
* `*(int **)D_8011A8D4` adds a whole extra `lw v0,0(v0)` and the row is 8 bytes long.
* 2. **THE ADDRESS IS DERIVED FROM THE (lui PAGE, SIGNED IMMEDIATE) PAIR, NOT THE IMMEDIATE
* ALONE.** `lui a0,0x8011; addiu a0,a0,-6588` is 0x80110000 − 6588 = **0x8010E644**. I
* read it as 0x8011E644 and the row was one byte wrong (the `lui` differs, the `addiu`
* does not). This is the third time I have made this class of error — once with a
* gp-relative offset and twice with `lui` — and it deserves a cookbook line:
* *derive every absolute address from the page plus the signed immediate.*
*
* LIMITS: the function name, both callees, the global object and the whole "dispatch a
* virtual method" reading are hypotheses taken from the instruction shape; only the bytes
* are evidence. Both callees and the global are referenced by their address-named spellings.
*/
extern int *D_8011A8D4;
extern char D_8010E644;
int func_800F4100();
void func_800F42AC(int s0, int s1)
{
int *p;
func_800F4100(&D_8010E644, s0);
p = D_8011A8D4;
((void (*)(int, int, int, int))*(int *)((char *)p + 8))(*(int *)((char *)p + 32), s0, 8, s1);
}
+50
View File
@@ -0,0 +1,50 @@
/*
* func_800FAF84 — 104 bytes at 0x800FAF84..0x800FAFEC
*
* Goal B, Phase 11. Four spellings. **Requires the harness mode `maspsx=epilogue`** (region token in
* column 4 of the claim row).
*
* int t[2];
* func_800FB038(a1, t);
* func_800F8B80(2, t, 0);
* func_800FA338(a0, a2, 128);
* return func_800FA4D4(0, 0) == 0;
*
* TWO LEVERS, and the first is the one that made the frame match:
*
* 1. **The local is 8 bytes, not 12.** The saved `s0` sits at sp+24 and the callee writes through
* `t` at sp+16 — so a 12-byte local would overlap the saved register. `int t[3]` and a 12-byte
* struct both give **vars=16 and a 48-byte frame**; `int t[2]` gives **vars=8 and the original's
* 40-byte frame**. The tell is arithmetic: 16 (args) + 12 (three saved words) + vars = 40 ⇒
* vars = 12, and the register slots at 24/28/32 leave only sp+16..sp+23 free. cc1 rounds a
* 12-byte local up to 16, so the source's object must be 8 bytes. **Read the frame arithmetic and
* the saved-register offsets together before choosing a local's size.**
*
* 2. **`maspsx=epilogue` — the only residual after the size fix was two swapped words.** The original
* ends `jr ra` / `addiu sp,sp,40` (the frame release in the jump's delay slot); the candidate ends
* `addiu sp,sp,40` / `jr ra`. That is exactly the transform the new harness mode performs, and
* with it the row reports 104 bytes / 0 differing / MATCH. GNU `as` will not do the fill itself
* (it would put `jr $31` in the `lw $31` load-delay slot), which is why the harness has to.
*
* LIMITS: the function name, the four callees, the meaning of the record and of the 2/0/128
* arguments are hypotheses reconstructed from the disassembly; only the compiled bytes are evidence.
* The local is declared `int t[2]`; the callee writes past it, which is a real property of the
* original — do NOT "fix" it by enlarging the array, because that changes the frame and the row stops
* matching. `func_800FA4D4(0, 0) == 0` is written as an equality, not `< 1`, because the original
* emits `sltiu`.
*/
extern void func_800FB038(int a0, int *a1);
extern void func_800F8B80(int a0, int *a1, int a2);
extern void func_800FA338(int a0, int a1, int a2);
extern int func_800FA4D4(int a0, int a1);
int func_800FAF84(int a0, int a1, int a2)
{
int t[2];
func_800FB038(a1, t);
func_800F8B80(2, t, 0);
func_800FA338(a0, a2, 128);
return func_800FA4D4(0, 0) == 0;
}
+72
View File
@@ -0,0 +1,72 @@
/*
* func_80100038 — 104 bytes at 0x80100038..0x801000A0
*
* Hypothesis, not a claim about meaning: clears a per-index slot, then calls a per-index
* handler with the index, a stored argument and a fixed fourth argument. Matched on the
* SECOND spelling, and it needs the harness's `maspsx=epilogue` mode.
*
* Original words:
* 27BDFFE8 addiu sp,sp,-24
* AFB00010 sw s0,16(sp)
* 00808021 move s0,a0
* 00002021 move a0,zero
* 24050001 li a1,1
* AFBF0014 sw ra,20(sp)
* 0C0421F4 jal 0x801087D0 ; func_801087D0(0, 1 << s0)
* 00A08804 sllv a1,a1,s0 ; (delay)
* 3C018014 lui at,0x8014 ; base = 0x80145AC0
* 24215AC0 addiu at,at,23232
* 001080C0 sll v0,s0,0x3 ; v0 = base + s0 * 72
* 00501021 addu v0,v0,s0
* 000210C0 sll v0,v0,0x3
* 00411021 addu v0,v0,at
* 8C430044 lw v1,68(v0) ; v1 = *(int *)(p + 68)
* 00000000 nop
* 10600004 beqz v1,0x80100090 ; if (v1 != 0)
* AC400000 sw zero,0(v0) ; (delay) *(int *)p = 0 -- runs on BOTH paths
* 02002021 move a0,s0 ; v1(s0, *(int *)(p + 64), 4)
* 8C450040 lw a1,64(v0)
* 0040F809 jalr v1
* 24060004 li a2,4 ; (delay)
* 8FBF0014 lw ra,20(sp) ; 0x80100090: (END)
* 8FB00010 lw s0,16(sp)
* 03E00008 jr ra
* 27BD0018 addiu sp,sp,24 ; THE FRAME RELEASE IS IN THE jr DELAY SLOT
*
* **TWO BYTE-REQUIRED SHAPES:**
*
* 1. **`maspsx=epilogue`** (the claim row carries the token). The original's tail is the
* rare form — `jr ra` with `addiu sp,sp,24` in the jump slot — which the default maspsx
* does not emit. With `--fill-epilogue` the row is exact. Like `0x800F42AC`, this tail
* has NO load-delay `nop`, which is why the swap alone closes it.
* 2. **THE BASE POINTER MUST BE A SEPARATE VARIABLE DECLARED BEFORE THE CALL.** Writing
* `p = &D_80145AC0 + s0 * 72;` lets cc1 schedule the index scaling FIRST and the base
* materialisation second; the original materialises the base (`lui at` / `addiu at`)
* BEFORE the scale. Hoisting the base into its own local declared above the call —
* `char *base = &D_80145AC0;` then `p = base + s0 * 72;` — reproduces the original's
* order and is exact. The residual in the first spelling was 16 bytes, all of it the
* two groups swapped.
*
* Also note `*(int *)p = 0` is UNCONDITIONAL in the source but lands in the branch's delay
* slot, so it runs on both paths — the same shape as `0x800F6D60`.
*
* LIMITS: the function name, the callee, the table base, the 72-byte element stride and the
* whole "clear and dispatch" reading are hypotheses taken from the instruction shape; only
* the bytes are evidence. Both the table and the callee are referenced by their address-named
* spellings.
*/
extern char D_80145AC0;
void func_801087D0();
void func_80100038(int s0)
{
char *base = &D_80145AC0;
char *p;
func_801087D0(0, 1 << s0);
p = base + s0 * 72;
*(int *)p = 0;
if (*(int *)(p + 68) != 0)
((void (*)(int, int, int))*(int *)(p + 68))(s0, *(int *)(p + 64), 4);
}
+82
View File
@@ -0,0 +1,82 @@
/*
* func_801059E8 — 104 bytes at 0x801059E8..0x80105A50
*
* Hypothesis, not a claim about meaning: asks a global predicate about an argument; if it
* refuses, returns 0; otherwise initialises the object's flag, two handler pointers and a
* stored argument, and returns 1. Matched on the SECOND spelling and it needs the harness's
* `maspsx=epilogue` mode.
*
* Original words:
* 27BDFFE0 addiu sp,sp,-32
* AFB00010 sw s0,16(sp)
* 00808021 move s0,a0
* AFB10014 sw s1,20(sp)
* 3C028012 lui v0,0x8012
* 8C42FE2C lw v0,-468(v0) ; v0 = D_8011FE2C (a function pointer)
* AFBF0018 sw ra,24(sp)
* 0040F809 jalr v0 ; if (f(a0, s1) != 0) return 0
* 00A08821 move s1,a1 ; (delay) -- the SECOND argument only; a0 is the
* ; caller's own a0, still live, so the source
* ; passes its parameter directly
* 14400007 bnez v0,0x80105A3C
* 00001021 move v0,zero ; (delay)
* 24020001 li v0,1
* 24030001 li v1,1
* A2030046 sb v1,70(s0) ; *(char *)(s0 + 70) = 1
* 3C018010 lui at,0x8010
* 24215A50 addiu at,at,23120 ; *(int *)(s0 + 20) = (int)func_80105A50
* AC010014 sw at,20(s0)
* 3C018010 lui at,0x8010
* 24215A6C addiu at,at,23148 ; *(int *)(s0 + 24) = (int)func_80105A6C
* AE110020 sw s1,32(s0) ; *(int *)(s0 + 32) = s1
* AC010018 sw at,24(s0)
* 8FBF0018 lw ra,24(sp) ; 0x80105A3C: (END)
* 8FB10014 lw s1,20(sp)
* 8FB00010 lw s0,16(sp)
* 03E00008 jr ra
* 27BD0020 addiu sp,sp,32 ; THE FRAME RELEASE IS IN THE jr DELAY SLOT
*
* **THREE BYTE-REQUIRED SHAPES:**
*
* 1. **`maspsx=epilogue`** (the claim row carries the token). The tail is the rare form and,
* like `0x800F42AC` and `0x80100038`, it has NO load-delay `nop`, so the swap alone is
* sufficient. Three of my four epilogue-class rows have this short tail.
* 2. **THE HANDLER ADDRESSES MUST BE FUNCTION SYMBOLS, NOT LITERALS.** Writing
* `*(int *)(s0 + 20) = 0x80105A50;` makes cc1 treat both addresses as constants, hoist
* them to the top of the function and reorder every store (31 bytes of residual);
* `(int)func_80105A50` emits each `lui/addiu` immediately before its store, in the
* original's order. This is the same lever as `0x80100740`.
* 3. **The predicate call passes only the SECOND argument explicitly.** The emitted sets
* `a1 = s1` in the delay slot and never touches `a0`, so the source must pass its own
* first parameter (still live in `a0`) as the call's first argument — `f(a0, s1)`.
* Binding the parameter to a local first (`s0`) and passing that would emit a `move`.
*
* Also note the returns are `0` on the refusal path and `1` at the end, with `li v0,1` and
* `li v1,1` materialised separately.
*
* LIMITS: the function name, the predicate, both handlers, the object offsets (70, 20, 24,
* 32) and the whole "predicate then initialise" reading are hypotheses taken from the
* instruction shape; only the bytes are evidence. The predicate global and both handlers are
* referenced by their address-named spellings.
*/
extern int D_8011FE2C;
void func_80105A50(void);
void func_80105A6C(void);
int func_801059E8(int a0, int a1)
{
int s0 = a0;
int s1 = a1;
int (*f)();
f = (int (*)())D_8011FE2C;
if (f(a0, s1) != 0)
return 0;
*(char *)(s0 + 70) = 1;
*(int *)(s0 + 20) = (int)func_80105A50;
*(int *)(s0 + 32) = s1;
*(int *)(s0 + 24) = (int)func_80105A6C;
return 1;
}
+8 -2
View File
@@ -217,7 +217,8 @@ _REGION_OPTION_KEYS = ("cc1", "as", "gp", "maspsx")
# left empty while leaving cc1's own `.set noreorder` windows alone.
_MASPSX_MODES = {"off": "--off", "noreordernop": "--no-jump-slot-nop",
"regread": "--nop-on-reg-read", "moves": "moves",
"nopmarker": "--honour-nop-marker"}
"nopmarker": "--honour-nop-marker",
"epilogue": "--fill-epilogue"}
def parse_region_options(text: str, line_number: int) -> tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...], bool, tuple[str, ...], bool]:
@@ -853,6 +854,10 @@ def add_toolchain_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--no-jump-slot-nop", action="store_true",
help="maspsx mode: suppress the unconditional reorder nop after a "
"branch/jump so GNU as can fill the slot (region: maspsx=noreordernop)")
parser.add_argument("--fill-epilogue", action="store_true",
help="maspsx mode: move the frame release INTO the jump's delay slot "
"(region token: maspsx=epilogue). 120 unclaimed rows have this "
"shape in the original; GNU as will not do the fill itself.")
parser.add_argument("--honour-nop-marker", action="store_true",
help="maspsx mode: honour cc1's explicit `#nop` marker even when the "
"following macro expansion would fill the delay slot "
@@ -969,7 +974,8 @@ def resolve_toolchain(args: argparse.Namespace) -> Toolchain:
flag for flag, enabled in (
("--no-jump-slot-nop", getattr(args, "no_jump_slot_nop", False)),
("--nop-on-reg-read", getattr(args, "nop_on_reg_read", False)),
("--honour-nop-marker", getattr(args, "honour_nop_marker", False))) if enabled),
("--honour-nop-marker", getattr(args, "honour_nop_marker", False)),
("--fill-epilogue", getattr(args, "fill_epilogue", False))) if enabled),
aspsx_version=args.aspsx_version,
)