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:
Christopher Williams
2026-09-24 09:46:48 -04:00
parent 2c3f5a33a5
commit a1c41771e7
5 changed files with 326 additions and 0 deletions
+3
View File
@@ -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
1 # Code-region registry: one C region per matched function.
38 0x8001644C
39 0x80016DCC
40 0x80016E50
41 0x80016F18
42 0x800170C0
43 0x800171D8
44 0x8001761C
136 0x800301FC
137 0x80030284
138 0x80030358
139 0x80030858
140 0x800308C4
141 0x800319F0
142 0x80031BBC
331 0x800900A0
332 0x80090894
333 0x800908E4
334 0x800909D8
335 0x80090A44
336 0x80090B64
337 0x80090B7C
+26
View File
@@ -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.
+69
View File
@@ -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);
}
}
}
+122
View File
@@ -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));
}
}
+106
View File
@@ -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));
}
}