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:
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user