phase11: merge 19 + cookbook 92-94 — 535 bodies / 544 regions
Worker B's four first-attempt claims (0x800B255C, 0x8004857C, 0x80030858, 0x800909D8). Cookbook 94 is the strategic one: worker B's failures cluster into exactly TWO mechanical classes -- reorg slot-fill choice and rare-epilogue fill -- and neither is a shape problem. Both are the post-pass family, which two workers have now independently arrived at and stopped on. That is the strongest argument yet for writing the post-pass rather than grinding these rows with source spellings.
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
0x8001644C 0x800164EC src/func_8001644C.c
|
||||
0x80016DCC 0x80016E24 src/func_80016DCC.c
|
||||
0x80016E50 0x80016E68 src/func_80016E50.c
|
||||
0x80016F18 0x80016F80 src/func_80016F18.c
|
||||
0x800170C0 0x80017108 src/func_800170C0.c
|
||||
0x800171D8 0x80017200 src/func_800171D8.c
|
||||
0x8001761C 0x80017660 src/func_8001761C.c
|
||||
@@ -135,6 +136,7 @@
|
||||
0x800301FC 0x8003022C src/func_800301FC.c
|
||||
0x80030284 0x800302DC src/func_80030284.c
|
||||
0x80030358 0x80030390 src/func_80030358.c
|
||||
0x80030858 0x800308C4 src/func_80030858.c
|
||||
0x800308C4 0x800309CC src/func_800308C4.c
|
||||
0x800319F0 0x80031A48 src/func_800319F0.c
|
||||
0x80031BBC 0x80031CC0 src/func_80031BBC.c
|
||||
@@ -329,6 +331,7 @@
|
||||
0x800900A0 0x800900CC src/func_800900A0.c
|
||||
0x80090894 0x800908E4 src/func_80090894.c
|
||||
0x800908E4 0x80090990 src/func_800908E4.c
|
||||
0x800909D8 0x80090A44 src/func_800909D8.c
|
||||
0x80090A44 0x80090A70 src/func_80090A44.c
|
||||
0x80090B64 0x80090B7C src/func_80090B64.c
|
||||
0x80090B7C 0x80090BB4 src/func_80090B7C.c
|
||||
|
||||
|
@@ -1440,3 +1440,29 @@ the registry plus the one row. That is the correct technique and worth copying.
|
||||
`0x80044D78` makes ten repeated calls to `func_800460AC` with successive addresses, and **entries 7
|
||||
and 8 are swapped** (0x80116C50 before 0x80116C10). That is a genuine quirk in the original source
|
||||
order, not a transcription error — worth stating because it looks like one.
|
||||
|
||||
### 92. The narrowing IS the type evidence (worker B)
|
||||
|
||||
`0x800B255C`: both parameters are narrowed with `sll`/`sra`, so they are `short` rather than `int` —
|
||||
**an `int` parameter is passed through untouched.** The narrowing instruction is the type evidence,
|
||||
so a parameter's declared type can be read off the prologue without guessing.
|
||||
|
||||
### 93. Store widths are the only evidence for a record's layout (worker B)
|
||||
|
||||
`0x800909D8`: six consecutive symbol stores through `$at` with three different widths
|
||||
(`sw`/`sh`/`sb`). Where a record's fields are written one after another, **the widths of the
|
||||
stores are the layout** — there is no other evidence available for an unregistered struct.
|
||||
|
||||
### 94. The two mechanical failure classes, and why they are the post-pass family (worker B)
|
||||
|
||||
Worker B's batch clustered into exactly two classes, and **neither is a shape problem**:
|
||||
|
||||
1. **Reorg slot-fill choice** (`0x800B1F94`, `0x800F2F08`) — the control flow and every instruction
|
||||
match; one store/load sits in a different delay slot. In `0x800F2F08` the original sinks the last
|
||||
store (`sw v1,28(a2)`) into the following `jal` slot where cc1 leaves the slot empty.
|
||||
2. **Rare-epilogue fill** (`0x800FFBEC`, finding 84) — residual is the last two instructions' order,
|
||||
and `maspsx=off` is worse.
|
||||
|
||||
**Both are the same post-pass family** (finding 40), which is why grinding them with source spellings
|
||||
is the wrong expenditure. This is the strongest argument yet for writing the post-pass: two workers
|
||||
independently arrived at the same two classes and both stopped for the same reason.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* func_80016F18 — 104 bytes at 0x80016F18..0x80016F80
|
||||
*
|
||||
* Byte-identical reconstruction of a leaf accessor: given an object, it walks
|
||||
* two levels of indirection to reach an inner pointer, and — only when both that
|
||||
* pointer and the object's first word are non-zero — publishes the inner
|
||||
* pointer's first word and its successor through the two out-parameters,
|
||||
* otherwise zeroing both.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* beqz a0,0x80016F78 if (a0 == 0) return;
|
||||
* move v1,zero (delay slot) p = 0;
|
||||
* lw v0,16(a0) \ q = *(int **)(a0 + 16);
|
||||
* nop |
|
||||
* lw a3,32(v0) | r = *(int **)(q + 32);
|
||||
* li v0,-1 |
|
||||
* beq a3,v0,0x80016F3C | if (r != (int *)-1)
|
||||
* nop |
|
||||
* lw v1,132(a3) / p = *(int **)(r + 132);
|
||||
* nop
|
||||
* beqz v1,0x80016F58 \ if (p == 0 || *(int *)a0 == 0) {
|
||||
* nop |
|
||||
* lw v0,0(a0) |
|
||||
* nop |
|
||||
* bnez v0,0x80016F64 /
|
||||
* nop
|
||||
* sw zero,0(a1) \ *a1 = 0;
|
||||
* j 0x80016F78 |
|
||||
* sw zero,0(a2) / } (delay slot) *a2 = 0;
|
||||
* lw v0,0(v1) \ else {
|
||||
* nop |
|
||||
* sw v0,0(a1) | *a1 = *p;
|
||||
* addiu v0,v1,4 |
|
||||
* sw v0,0(a2) / *a2 = (int)(p + 1);
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* The guard is written with the **zeroing arm first** (`p == 0 || ...`), which
|
||||
* is what lays the zeroing block out inline with a `j` over it and leaves the
|
||||
* publishing arm last. The natural `if (p && *a0)` spelling puts the zeroing arm
|
||||
* at the end and costs 19 differing bytes. Both conditions are one `||` chain
|
||||
* and the two comparisons use the *same* loaded register order as the original.
|
||||
*
|
||||
* LIMITS: names are address placeholders and every type is inferred from
|
||||
* register usage alone; only the compiled bytes are evidence. The object is
|
||||
* modelled with raw offsets (+16, then +32, then +132) because only those
|
||||
* displacements are observable; the sentinel `-1` is compared against a pointer
|
||||
* because that is what the bytes compare. `*a2` receives `p + 1` as an int, so
|
||||
* whether the original stored a pointer or a small integer there is unproven.
|
||||
*/
|
||||
|
||||
void func_80016F18(char *a0, int *a1, int *a2) {
|
||||
int *p = 0;
|
||||
|
||||
if (a0) {
|
||||
int *q = *(int **)(a0 + 16);
|
||||
int *r = *(int **)((char *)q + 32);
|
||||
|
||||
if (r != (int *)-1)
|
||||
p = *(int **)((char *)r + 132);
|
||||
if (p == 0 || *(int *)a0 == 0) {
|
||||
*a1 = 0;
|
||||
*a2 = 0;
|
||||
} else {
|
||||
*a1 = *p;
|
||||
*a2 = (int)(p + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* func_80036B14 — 656 bytes at 0x80036B14..0x80036DA4
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: the sibling of func_80036DA4. It primes the same
|
||||
* two 4-int vectors and byte flag, and between the first and second update calls it scales
|
||||
* three of the four components by a caller-supplied vector, dividing by 4096.
|
||||
*
|
||||
* Shape (s0 = a0, s1 = a1, s2 = a2, s3 = a3, s5 = the 5th argument, s4 = the 6th):
|
||||
* 27BDFFB0 addiu sp,sp,-80
|
||||
* AFB50044 sw s5,68(sp)
|
||||
* 8FB50060 lw s5,96(sp) ; 5th argument (a pointer, read from the stack)
|
||||
* 93B40064 lbu s4,100(sp) ; 6th argument (unsigned char)
|
||||
* ... ; s0..s3 = the four register arguments
|
||||
* 1220006C beqz s1,0x80036CCC ; if (a1 == 0) skip BOTH the first and second blocks
|
||||
* 8E020000 lw v0,0(s1) ; *(V *)(a0+176) = *a1 (4 loads then 4 stores)
|
||||
* ... AE0500BC sw a1,188(s0)
|
||||
* 8EA20000 lw v0,0(s5) ; *(V *)(a0+196) = *a4
|
||||
* ... AE0500D0 sw a1,208(s0)
|
||||
* 8E0500A4 lw a1,164(s0) ; p = *(int **)(a0 + 164)
|
||||
* 26070168 addiu a3,s0,168
|
||||
* A20400D4 sb s4,212(s0)
|
||||
* 24A00240 addiu a0,a1,576 ; 576/4 = 144
|
||||
* 8CA20148 lw a2,328(a1) ; p[82]
|
||||
* 0C00BEF0 jal 0x8002FBC0
|
||||
* 24A10270 addiu a1,a1,624 ; (delay) 624/4 = 156
|
||||
* 8E220000 lw v0,0(s1) ; t = *a1 (sp+16..sp+28)
|
||||
* ... AFA1001C sw a1,28(sp)
|
||||
* 1240002A beqz s2,0x80036C70 ; if (a2 == 0) skip the scaling
|
||||
* 8FA10010 lw v1,16(sp) ; -- NESTED inside the a1 block
|
||||
* 1840000F blez v1,0x80036C08 ; if (t.v[0] > 0)
|
||||
* 8E420000 lw v0,0(s2)
|
||||
* 00620018 mult v1,v0
|
||||
* 00001012 mflo v0
|
||||
* 04410002 bgez v0,0x80036C00
|
||||
* 24400FFF addiu v0,v0,4095
|
||||
* 00000740 sra v0,v0,0xc ; ... t.v[0] * a2[0] / 4096
|
||||
* AFA20010 sw v0,16(sp)
|
||||
* ... ; the same for v[1] (a2[1]) and v[2] (a2[2])
|
||||
* 8FA20010 lw v0,16(sp) ; 0x80036C70: copy t back and call again
|
||||
* ... AE0500BC sw a1,188(s0)
|
||||
* ... AE0500D0 sw a1,208(s0)
|
||||
* 8E0500A4 lw a1,164(s0)
|
||||
* A20400D4 sb s4,212(s0)
|
||||
* 24A0060C addiu a0,a1,1548 ; 1548/4 = 387
|
||||
* 8CA20514 lw a2,1300(a1) ; p[325]
|
||||
* 0C00BEF0 jal 0x8002FBC0
|
||||
* 24A1063C addiu a1,a1,1596 ; (delay) 1596/4 = 399
|
||||
* 1260002B beqz s3,0x80036D7C ; if (a3 == 0) done
|
||||
* ... ; u = D_8010C124; t.v[0..2] = *a3; copy; call 1224/1272/976
|
||||
* 8FBF0048 lw ra,72(sp) ; (END)
|
||||
* ...
|
||||
*
|
||||
* **THE `if (a2 != 0)` BLOCK IS NESTED INSIDE THE `if (a1 != 0)` BLOCK, AND THE ONLY
|
||||
* DIAGNOSTIC IS TWO BRANCH TARGETS.** Writing the two guards as siblings gives the CORRECT
|
||||
* LENGTH (656) and exactly **2 differing bytes**, both of them branch displacements:
|
||||
* `beqz s1` then jumps to the `if (a2)` test instead of past the whole second block, and
|
||||
* `beqz s2` jumps to the wrong place. Because `t` is only initialised inside the `if (a1)`
|
||||
* block, the nested form is also the only one that is correct — but the *tell* is worth
|
||||
* remembering: **a correct-length candidate whose residual is a handful of branch words
|
||||
* means the block nesting is wrong, not the code inside the blocks.**
|
||||
*
|
||||
* Also byte-required: the vector copies are struct assignments (4 loads then 4 stores, same
|
||||
* lever as 0x8005584C / 0x80036DA4); `t.v[0] = t.v[1] = t.v[2] = *a3;` reads the same
|
||||
* address three times (no CSE) leaving the 4th word as stack garbage; `struct V u =
|
||||
* D_8010C124;` is an initialiser and is hoisted above the `t` stores; the 5th and 6th
|
||||
* arguments arrive on the stack as a word and a byte respectively. The scaling is a
|
||||
* DIVISION by 4096 (`/ 4096`), not `>> 12` — cc1 emits the `addiu v0,v0,4095` sign bias for
|
||||
* the division and would not for a shift.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the struct layouts and every offset are hypotheses
|
||||
* read from the instruction shape; only the bytes are evidence. D_8010C124 and func_8002FBC0
|
||||
* are not registered and are referenced by their address-named spellings. `t.v[3]` is
|
||||
* deliberately never written, because the original copies four words from a three-word
|
||||
* initialisation.
|
||||
*/
|
||||
|
||||
struct V { int v[4]; };
|
||||
|
||||
extern struct V D_8010C124;
|
||||
void func_8002FBC0(int *, int *, int, int *);
|
||||
|
||||
void func_80036B14(char *a0, struct V *a1, int *a2, int *a3, struct V *a4, unsigned char a5)
|
||||
{
|
||||
struct V t;
|
||||
struct V u;
|
||||
int *p;
|
||||
|
||||
if (a1 != 0) {
|
||||
*(struct V *)(a0 + 176) = *a1;
|
||||
*(struct V *)(a0 + 196) = *a4;
|
||||
*(unsigned char *)(a0 + 212) = a5;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 144, p + 156, p[82], (int *)(a0 + 168));
|
||||
t = *a1;
|
||||
|
||||
if (a2 != 0) {
|
||||
if (t.v[0] > 0)
|
||||
t.v[0] = t.v[0] * a2[0] / 4096;
|
||||
if (t.v[1] > 0)
|
||||
t.v[1] = t.v[1] * a2[1] / 4096;
|
||||
if (t.v[2] > 0)
|
||||
t.v[2] = t.v[2] * a2[2] / 4096;
|
||||
}
|
||||
*(struct V *)(a0 + 176) = t;
|
||||
*(struct V *)(a0 + 196) = *a4;
|
||||
*(unsigned char *)(a0 + 212) = a5;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 387, p + 399, p[325], (int *)(a0 + 168));
|
||||
}
|
||||
|
||||
if (a3 != 0) {
|
||||
u = D_8010C124;
|
||||
t.v[0] = *a3;
|
||||
t.v[1] = *a3;
|
||||
t.v[2] = *a3;
|
||||
*(struct V *)(a0 + 176) = t;
|
||||
*(struct V *)(a0 + 196) = u;
|
||||
*(unsigned char *)(a0 + 212) = a5;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 306, p + 318, p[244], (int *)(a0 + 168));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* func_80036DA4 — 460 bytes at 0x80036DA4..0x80036F70
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: primes a sub-object's two 4-int vectors and a
|
||||
* byte flag, then runs one of three update calls. The first two blocks are byte-identical
|
||||
* apart from three constants, and the third differs only in how its vectors are built —
|
||||
* which is why it matched on the FIRST spelling.
|
||||
*
|
||||
* Shape (s1 = a0, s2 = a1, s3 = a2, s5 = a3, s4 = the 5th argument, read as
|
||||
* `lbu s4,96(sp)` because it arrives on the stack):
|
||||
* 27BDFFB0 addiu sp,sp,-80
|
||||
* AFB40040 sw s4,64(sp)
|
||||
* 93B40060 lbu s4,96(sp) ; the 5th argument
|
||||
* ... ; s1..s5 = the four register arguments
|
||||
* 1240003C beqz s2,0x80036E98 ; if (a1 == 0) skip the first two blocks
|
||||
* 8E420000 lw v0,0(s2) ; *(V *)(a0+176) = *a1 -- a 4-word block move
|
||||
* 8E430004 lw v1,4(s2)
|
||||
* 8E440008 lw a0,8(s2)
|
||||
* 8E45000C lw a1,12(s2)
|
||||
* AE2200B0 sw v0,176(s1)
|
||||
* ... AE2500BC sw a1,188(s1)
|
||||
* 8E650000 lw v0,0(s5) ; *(V *)(a0+196) = *a3
|
||||
* ... AE2500D0 sw a1,208(s1)
|
||||
* 26300168 addiu s0,s1,168 ; s0 = a0 + 168
|
||||
* 8E2500A4 lw a1,164(s1) ; p = *(int **)(a0 + 164)
|
||||
* 02003821 move a3,s0
|
||||
* A3B400D4 sb s4,212(s1) ; *(char *)(a0 + 212) = a4
|
||||
* 24A00230 addiu a0,a1,560
|
||||
* 8CA20148 lw a2,328(a1) ; p[82]
|
||||
* 0C00BEF0 jal 0x8002FBC0 ; func_8002FBC0(p+140, p+152, p[82], a0+168)
|
||||
* 24A10260 addiu a1,a1,608 ; (delay)
|
||||
* ... ; the SAME block again with 1532 / 1580 / 1300
|
||||
* 1260004A beqz s3,0x80036F48 ; if (a2 == 0) skip the third block
|
||||
* 3C118011 lui a1,0x8011 ; u = D_8010C124
|
||||
* 2631C124 addiu a1,a1,-16092
|
||||
* ... ; 4 loads then 4 stores to sp+32..sp+44
|
||||
* 8E630000 lw v0,0(s3) ; t.v[0] = t.v[1] = t.v[2] = *a2
|
||||
* AF A20010 sw v0,16(sp) ; -- THE SAME ADDRESS LOADED THREE TIMES
|
||||
* ... (three times into sp+16, sp+20, sp+24)
|
||||
* ... ; t (4 words, the 4th UNINITIALISED) -> a0+176
|
||||
* ... ; u -> a0+196, sb a4,212(a0), call with 1208/1256/976
|
||||
* 8FBF0048 lw ra,72(sp) ; (END)
|
||||
* ...
|
||||
*
|
||||
* FOUR BYTE-REQUIRED SHAPES:
|
||||
*
|
||||
* 1. **The vector copies are STRUCT ASSIGNMENTS** (`*(struct V *)(a0 + 176) = *a1;`), not
|
||||
* four element stores — the same lever proven at 0x8005584C. This gives the original's
|
||||
* 4-loads-then-4-stores block move.
|
||||
* 2. **`t.v[0] = t.v[1] = t.v[2] = *a2;` reads the SAME address three times.** cc1 does not
|
||||
* CSE the three `lw v0,0(s3)` loads, so the source really does read `*a2` three times.
|
||||
* The struct's fourth word is never written and is copied as stack garbage — the
|
||||
* original copies 4 words from a 3-word initialisation, which is only reproducible by
|
||||
* copying a whole struct.
|
||||
* 3. **`struct V u = D_8010C124;`** — an initialiser, so it is hoisted above the `t`
|
||||
* stores exactly as the original emits it (sp+32..44 filled first).
|
||||
* 4. **The 5th argument is `unsigned char`** and arrives on the stack (`lbu s4,96(sp)`,
|
||||
* i.e. the caller's sp+16), then is stored with `sb`.
|
||||
*
|
||||
* The three calls are `func_8002FBC0(p + A, p + A + 12, p[A - 58], a0 + 168)` with
|
||||
* A = 140, 383, 302 — i.e. a pointer field 58 words before two vectors 12 words apart,
|
||||
* which is a struct layout, not three unrelated constants.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the struct layouts (V, the object at a0+164, the
|
||||
* target of p) and every offset are hypotheses read from the instruction shape; only the
|
||||
* bytes are evidence. D_8010C124 is not registered and is referenced by its address-named
|
||||
* spelling. func_8002FBC0 is likewise referenced by address.
|
||||
*/
|
||||
|
||||
struct V { int v[4]; };
|
||||
|
||||
extern struct V D_8010C124;
|
||||
void func_8002FBC0(int *, int *, int, int *);
|
||||
|
||||
void func_80036DA4(char *a0, struct V *a1, int *a2, struct V *a3, unsigned char a4)
|
||||
{
|
||||
int *p;
|
||||
|
||||
if (a1 != 0) {
|
||||
*(struct V *)(a0 + 176) = *a1;
|
||||
*(struct V *)(a0 + 196) = *a3;
|
||||
*(unsigned char *)(a0 + 212) = a4;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 140, p + 152, p[82], (int *)(a0 + 168));
|
||||
|
||||
*(struct V *)(a0 + 176) = *a1;
|
||||
*(struct V *)(a0 + 196) = *a3;
|
||||
*(unsigned char *)(a0 + 212) = a4;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 383, p + 395, p[325], (int *)(a0 + 168));
|
||||
}
|
||||
|
||||
if (a2 != 0) {
|
||||
struct V t;
|
||||
struct V u = D_8010C124;
|
||||
|
||||
t.v[0] = *a2;
|
||||
t.v[1] = *a2;
|
||||
t.v[2] = *a2;
|
||||
*(struct V *)(a0 + 176) = t;
|
||||
*(struct V *)(a0 + 196) = u;
|
||||
*(unsigned char *)(a0 + 212) = a4;
|
||||
p = *(int **)(a0 + 164);
|
||||
func_8002FBC0(p + 302, p + 314, p[244], (int *)(a0 + 168));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user