phase11: merge 49 + cookbook 162 — 586 bodies / 595 regions
Worker A's final row 0x8010A6C4 (132 B, first attempt, maspsx=epilogue) -- its ninth epilogue row and its 46th claim. 162: a callee called with DIFFERENT argument counts needs a NON-PROTOTYPE declaration -- func_8010A444(1) / (2, x) / (3, s1, s0) is only expressible as 'void func_8010A444();', the C89 empty-parameter form, not '(void)'. Same constraint that cost worker A a compile on 0x8002DD14. Worker A's final totals: 46 claims (33 first-attempt), 95 evidence rows, 39 levers, 3 deferred rows with derivations, 1 blocked row, 9 rows carrying maspsx=epilogue.
This commit is contained in:
+1015
-1017
File diff suppressed because it is too large
Load Diff
@@ -598,6 +598,7 @@
|
||||
0x80109778 0x80109790 src/func_80109778.c
|
||||
0x80109848 0x80109868 src/func_80109848.c maspsx=off
|
||||
0x80109F38 0x80109F48 src/func_80109F38.c
|
||||
0x8010A6C4 0x8010A748 src/func_8010A6C4.c maspsx=epilogue
|
||||
0x8010A748 0x8010A78C src/func_8010A748.c
|
||||
0x8010A888 0x8010A8B0 src/func_8010A888.c
|
||||
0x8010A8B0 0x8010A8D8 src/func_8010A8B0.c
|
||||
|
||||
|
@@ -2632,3 +2632,19 @@ are DIFFERENT instructions** — and a one-byte residual is invisible without a
|
||||
|
||||
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.
|
||||
|
||||
### 162. A callee called with DIFFERENT argument counts needs a NON-PROTOTYPE declaration (worker A)
|
||||
|
||||
Worker A's `0x8010A6C4`: the **same callee is called with one, two and three arguments** —
|
||||
`func_8010A444(1)`, `func_8010A444(2, x)`, `func_8010A444(3, s1, s0)`. **That is only expressible with
|
||||
a non-prototype declaration:**
|
||||
|
||||
void func_8010A444(); /* C89 empty-parameter form, NOT (void) */
|
||||
|
||||
> **If a callee appears with different argument counts, the declaration must be the empty-parameter
|
||||
> form.** Same constraint that cost worker A a compile on `0x8002DD14`, and worker A's
|
||||
> `0x800697FC` deferred row records it too.
|
||||
|
||||
The row also confirmed **157** a fourth time (the first call passes only `a1`/`a2`, keeping its own
|
||||
live `a0`), and added: **a shift count can be a global read straight into the argument register, with
|
||||
the shift scheduled into the preceding call's delay slot.**
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* func_80107DE8 — 128 bytes at 0x80107DE8..0x80107E68
|
||||
*
|
||||
* Walk a linked list, call a handler for each matching entry, guarded by a global re-entry flag.
|
||||
*
|
||||
* addiu sp,sp,-0x20 / sw s1,20 / move s1,a0 / sw s2,24 / move s2,a1 / sw ra,28
|
||||
* Both parameters are kept: a0 is compared against a field
|
||||
* and a1 against another, both after the call.
|
||||
* beqz s1,END / sw s0,16(sp) if (a0 == 0) return; -- the s0 save is the branch's delay
|
||||
* slot (reorg).
|
||||
* lw s0,2188(gp) node = head (gp + 2188 = 0x801221C4)
|
||||
* li v0,1 / sw v0,2192(gp) flag = 1 (gp + 2192 = 0x801221C8)
|
||||
* beqz s0,CLEAR / move a0,s0 loop entry guard + `cur = node`
|
||||
* lw v0,8(a0) / lw s0,4(s0) cur[2] and node[1]
|
||||
* bne v0,s1,SAME if (cur[2] != a0) continue
|
||||
* lw v0,12(a0) / bne v0,s2,SAME if (cur[3] != a1) continue
|
||||
* jal 0x80107C5C / nop func_80107C5C(cur) -- see below
|
||||
* bnez s0,LOOP / move a0,s0 while (node != 0)
|
||||
* sw zero,2192(gp) flag = 0
|
||||
* epilogue frame release in the jump slot (rare form)
|
||||
*
|
||||
* THREE THINGS ARE BYTE-REQUIRED, AND EACH WAS FOUND BY A DIFFERENT DIAGNOSTIC:
|
||||
*
|
||||
* 1. `node = (int *)node[1];` MUST COME BEFORE the two comparisons, even though that
|
||||
* advances the walk before the test. This is cookbook 72's mechanism in a new instance:
|
||||
* the intervening redefinition of `node` is what stops cc1 propagating `cur` back into
|
||||
* node's register, so the loop really does carry TWO pointers. With the update at the
|
||||
* END of the body, cc1 coalesces `cur` into `node` and the region comes out with the
|
||||
* wrong operand registers (`lw v0,8(s0)` instead of `lw v0,8(a0)`) and 8 bytes too long.
|
||||
*
|
||||
* 2. THE CALL TAKES `cur` AS ITS ARGUMENT. `cur` is allocated to a0 -- the first argument
|
||||
* register -- and there is NO argument setup at the `jal` (just `jal` + `nop`). cc1's
|
||||
* allocator prefers the argument register for a value that must be there at a call, so
|
||||
* a0 is not a tie-break here: it is EVIDENCE. Writing `func_80107C5C();` leaves a pure
|
||||
* 4-byte residual (the four a0/v1 register fields); writing `func_80107C5C(cur);` is
|
||||
* byte-exact and needs no copy before the call.
|
||||
* **Generalised diagnostic: when the ONLY residual is a value in an argument register
|
||||
* (a0-a3) in one compile and a caller-saved temp in the other, try passing that value as
|
||||
* that argument to the nearby call, rather than re-spelling its assignment.**
|
||||
*
|
||||
* 3. `maspsx=epilogue`. The tail is `lw ra / lw s2 / lw s1 / lw s0 / jr ra` with the frame
|
||||
* release in the jump delay slot; the default toolchain leaves it before the jump
|
||||
* (140 bytes without the token, 136 with it and no other source change from spelling 1).
|
||||
*
|
||||
* The entry guard + bottom test is the loop-inversion shape (cookbook 71/19): the source is a
|
||||
* plain `while (node != 0)`, not a do/while.
|
||||
*
|
||||
* LIMITS: the callee is a cross-reference by address only and its purpose is not established;
|
||||
* `cur`'s field indices (2 and 3) are read off the offsets 8 and 12. The list record's layout
|
||||
* beyond `+4` (next) and `+8`/`+12` is not recovered. `D_801221C4` and `D_801221C8` are the
|
||||
* already-registered gp-marked globals at gp+2188 and gp+2192; the second is used as a
|
||||
* re-entrancy guard here, which is an inference from the shape, not a recovered name.
|
||||
*/
|
||||
|
||||
extern int D_801221C4;
|
||||
extern int D_801221C8;
|
||||
extern void func_80107C5C(int *a0);
|
||||
|
||||
void func_80107DE8(int a0, int a1)
|
||||
{
|
||||
int *node;
|
||||
int *cur;
|
||||
|
||||
if (a0 == 0)
|
||||
return;
|
||||
|
||||
node = (int *)D_801221C4;
|
||||
D_801221C8 = 1;
|
||||
|
||||
while (node != 0) {
|
||||
cur = node;
|
||||
node = (int *)node[1];
|
||||
if (cur[2] == a0 && cur[3] == a1)
|
||||
func_80107C5C(cur);
|
||||
}
|
||||
|
||||
D_801221C8 = 0;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* func_8010A6C4 — 132 bytes at 0x8010A6C4..0x8010A748
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: a two-branch setup/teardown dispatcher — if a flag
|
||||
* global is set it runs one routine, otherwise it programs three values and runs another,
|
||||
* and either way it returns its second argument. Matched on the FIRST spelling; needs the
|
||||
* harness's `maspsx=epilogue` mode.
|
||||
*
|
||||
* Original words:
|
||||
* 3C028012 lui v0,0x8012
|
||||
* 8C421064 lw v0,4196(v0) ; v0 = D_80121064
|
||||
* 27BDFFE0 addiu sp,sp,-32
|
||||
* AFB10014 sw s1,20(sp)
|
||||
* 00808821 move s1,a0
|
||||
* AFB00010 sw s0,16(sp)
|
||||
* 00A08021 move s0,a1
|
||||
* 1440000D bnez v0,0x8010A724 ; if (flag != 0) -> the OTHER branch
|
||||
* AFBF0018 sw ra,24(sp) ; (delay)
|
||||
* 3C028012 lui v0,0x8012
|
||||
* 94421060 lhu v0,4192(v0) ; v0 = D_80121060 (UNSIGNED halfword)
|
||||
* 3C058012 lui a1,0x8012
|
||||
* 8CA51070 lw a1,4208(a1) ; a1 = D_80121070
|
||||
* 24040002 li a0,2
|
||||
* 0C042911 jal 0x8010A444 ; func_8010A444(2, D_80121060 << D_80121070)
|
||||
* 00A21004 sllv a1,v0,a1 ; (delay)
|
||||
* 0C042911 jal 0x8010A444 ; func_8010A444(1)
|
||||
* 24040001 li a0,1 ; (delay)
|
||||
* 24040003 li a0,3
|
||||
* 02202821 move a1,s1
|
||||
* 0C042911 jal 0x8010A444 ; func_8010A444(3, s1, s0)
|
||||
* 02003021 move a2,s0 ; (delay)
|
||||
* 080429CD j 0x8010A734
|
||||
* 02001021 move v0,s0 ; (delay)
|
||||
* 02202021 move a0,s1 ; 0x8010A724:
|
||||
* 0C042872 jal 0x8010A1C8 ; func_8010A1C8(s1, s0)
|
||||
* 02002821 move a1,s0 ; (delay)
|
||||
* 02001021 move v0,s0
|
||||
* 8FBF0018 lw ra,24(sp) ; 0x8010A734: (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
|
||||
*
|
||||
* BYTE-REQUIRED SHAPES:
|
||||
*
|
||||
* 1. **`maspsx=epilogue`** (the claim row carries the token). Shape-B tail with no
|
||||
* load-delay `nop`.
|
||||
* 2. **THE SAME CALLEE IS CALLED WITH ONE, TWO AND THREE ARGUMENTS** (`func_8010A444(1)`,
|
||||
* `(2, x)`, `(3, s1, s0)`). That is only expressible with a **non-prototype declaration**
|
||||
* — `void func_8010A444();` — and it is the same C89 constraint that cost me a compile
|
||||
* on `0x8002DD14`.
|
||||
* 3. **The shift count is a GLOBAL read into `a1`** (`D_80121070`) and the shifted value is
|
||||
* produced in the first call's delay slot (`sllv a1,v0,a1`) — so the source writes
|
||||
* `D_80121060 << D_80121070` as the second argument and lets cc1 schedule the shift into
|
||||
* the slot.
|
||||
* 4. **`D_80121060` is an UNSIGNED halfword** (`lhu`) and the flag is a full word (`lw`).
|
||||
*
|
||||
* LIMITS: the function name, both callees, the three globals and the whole "program three
|
||||
* values or tear down" reading are hypotheses taken from the instruction shape; only the
|
||||
* bytes are evidence. All symbols and callees are referenced by their address-named
|
||||
* spellings.
|
||||
*/
|
||||
|
||||
extern int D_80121064;
|
||||
extern unsigned short D_80121060;
|
||||
extern int D_80121070;
|
||||
void func_8010A444();
|
||||
void func_8010A1C8();
|
||||
|
||||
int func_8010A6C4(int s1, int s0)
|
||||
{
|
||||
if (D_80121064 != 0) {
|
||||
func_8010A1C8(s1, s0);
|
||||
return s0;
|
||||
}
|
||||
func_8010A444(2, D_80121060 << D_80121070);
|
||||
func_8010A444(1);
|
||||
func_8010A444(3, s1, s0);
|
||||
return s0;
|
||||
}
|
||||
Reference in New Issue
Block a user