phase11: merge 29 + cookbook 105/119 — 548 bodies / 557 regions

Worker D's 0x80106AA8 (136 B, first attempt) -- found by the REDUNDANCY filter, not
adjacency, which is the first row where the ranker did the finding alone. Eight stores
through four global pointers, each re-materialised per store.

Cookbook 105's dial now has THREE measured settings: per statement (0x8006BC74 46x and
0x80106AA8 8x, both matched), once per block (matched), once per function (does not match).
So per-statement re-reads are the NORMAL shape, not an extreme.

119: worker D ran the fragment check, called 0x80058BA0 a confirmed fragment, then
SELF-CORRECTED -- it is legal, because in o32 a frameless leaf may both read and write the
caller's outgoing argument area (sp+0..sp+31). All three of D's suspects are legal. Worker B
found the read side, worker D the write side, and both had to read the row to do it: a
heuristic keyed on shape must state its exclusions, and only the worker reading the row can
find them.
This commit is contained in:
Christopher Williams
2026-09-24 10:23:08 -04:00
parent 71aa4a2bc2
commit e2bdada67b
6 changed files with 1213 additions and 1023 deletions
+1022 -1023
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -541,6 +541,7 @@
0x80105B88 0x80105BA8 src/func_80105B88.c
0x80105BA8 0x80105BC8 src/func_80105BA8.c
0x80105BC8 0x80105BDC src/func_80105BC8.c
0x80106AA8 0x80106B30 src/func_80106AA8.c
0x80107A94 0x80107AA0 src/func_80107A94.c
0x80107AA0 0x80107AE0 src/func_80107AA0.c
0x80107AE0 0x80107B20 src/func_80107AE0.c
1 # Code-region registry: one C region per matched function.
541 0x80105B88
542 0x80105BA8
543 0x80105BC8
544 0x80106AA8
545 0x80107A94
546 0x80107AA0
547 0x80107AE0
+29
View File
@@ -1639,6 +1639,17 @@ source that re-dereferences at every statement forces a fresh load. **This is th
as finding 45's named-locals family, but as a *count* rather than a yes/no** — so it is a dial
you can turn, not a rule to apply.
**The dial now has three measured settings** (worker D's `0x80106AA8` added the third):
| setting | example | matched |
|---|---|---|
| **per statement** | `0x8006BC74` (46 re-reads), `0x80106AA8` (8 re-reads) | yes, both |
| **once per block** | `0x800320D8` | yes |
| **once per function** | `0x800320D8`'s alternative spelling | **no** |
`0x80106AA8` is eight stores through four global pointers, each re-materialised with its own
`lui`/`lw` — so per-statement re-reads are the *normal* shape, not an extreme.
**And a companion property worth preserving rather than "fixing":** `0x800320D8` leaves `v.b`/`v.c`
**uninitialised** on the single-word path and then subtracts them. **That uninitialised read is a
real property of the original, not a reconstruction error** — worker D recorded it in the file
@@ -1918,3 +1929,21 @@ chain" as a discriminator and **it fails in both directions:**
one weak signal in worker C's table: its false positive sits at a structurally-repeated run of
**10** and the run is exactly the `multu`/`mflo`/`sra` chain, while rows above run **18** are
plausibly real blocks — **but that is a guess from one false positive, not a measurement.**
### 119. The fragment check's EXCLUSION, stated — and a worker self-correcting (worker D)
Worker D ran `--fragments`, got three suspects, adjudicated them strictly, and **called
`0x80058BA0` a confirmed fragment. Then corrected itself: it is not.**
> In o32 the callee's `sp` is unchanged at entry, so **`sp+0..sp+31` is the caller's outgoing area,
> and a frameless leaf may both READ and WRITE it.** `sw a1,4(sp)` followed by `addiu a1,sp,4` /
> `move a2,sp` / `addiu a3,sp,7` is **legal scratch use.**
Corrected verdicts, all three **LEGAL**: `0x80058BA0` (writes into the argument area),
`0x800AE358` (`lw t2,16(sp)` / `lbu t1,20(sp)` = arguments 5 and 6), `0x800AE10C` (`lw a0,16(sp)` =
argument 5).
**The lesson, in worker D's words: *"a heuristic keyed on shape needs its exclusions stated, and
only the worker reading the row can find them."*** This is the third independent confirmation of
the same rule — worker B found the read side, worker D found the write side, and both had to read
the row to do it. **A tool that fires on a shape should document what the shape is allowed to be.**
+53
View File
@@ -0,0 +1,53 @@
/*
* func_800689DC — 136 bytes at 0x800689DC..0x80068A64
*
* Hypothesis, not a claim about meaning: copies four 4-int vectors from one area of the
* object to another, then returns 1. A leaf with no frame, no calls, no branches and no
* arithmetic — four struct assignments, which is why it matched on the FIRST spelling.
*
* Original words (int-index form; byte offsets are 4x):
* 8C820040 lw v0,64(a0) ; *(V4 *)(a0 + 0) = *(V4 *)(a0 + 16)
* 8C830044 lw v1,68(a0)
* 8C850048 lw a1,72(a0)
* 8C86004C lw a2,76(a0)
* AC820000 sw v0,0(a0)
* AC830004 sw v1,4(a0)
* AC850008 sw a1,8(a0)
* AC86000C sw a2,12(a0)
* 8C820050 lw v0,80(a0) ; *(V4 *)(a0 + 4) = *(V4 *)(a0 + 20)
* ... AC86001C sw a2,28(a0)
* 8C820060 lw v0,96(a0) ; *(V4 *)(a0 + 8) = *(V4 *)(a0 + 24)
* ... AC86002C sw a2,44(a0)
* 8C820070 lw v0,112(a0) ; *(V4 *)(a0 + 12) = *(V4 *)(a0 + 28)
* ... AC86003C sw a2,60(a0)
* 03E00008 jr ra
* 24020001 li v0,1 ; return 1, in the delay slot
*
* BYTE-REQUIRED SHAPES:
*
* 1. **Each copy is a STRUCT ASSIGNMENT** (`*(struct V4 *)(a0 + 0) = *(struct V4 *)(a0 + 16);`)
* — four loads then four stores. Ninth independent confirmation of this lever; four
* element stores interleave load/store and change the bytes.
* 2. **The four 4-int vectors are 16 bytes apart** (int offsets 0/16, 4/20, 8/24, 12/28) —
* the project's 4-int vector layout again, so the object's first 64 bytes are sixteen
* ints in four vector-shaped groups.
* 3. **The `return 1` lands in the `jr ra` delay slot** and there is NO frame at all — no
* `addiu sp,sp,-N` and no saved registers, because the function is a leaf with no
* locals. Writing the copy through an index variable introduces a frame.
*
* LIMITS: the function name, the object layout and the whole "copy four vectors" reading are
* hypotheses taken from the instruction shape; only the bytes are evidence. The `V4` struct
* exists purely to obtain the 4-word block moves; its real layout is unknown. No callee:
* this is a leaf and the harness emitted the common epilogue itself.
*/
struct V4 { int v[4]; };
int func_800689DC(int *a0)
{
*(struct V4 *)(a0 + 0) = *(struct V4 *)(a0 + 16);
*(struct V4 *)(a0 + 4) = *(struct V4 *)(a0 + 20);
*(struct V4 *)(a0 + 8) = *(struct V4 *)(a0 + 24);
*(struct V4 *)(a0 + 12) = *(struct V4 *)(a0 + 28);
return 1;
}
+46
View File
@@ -0,0 +1,46 @@
/*
* func_8009F4B4 — 248 bytes at 0x8009F4B4..0x8009F5AC
*
* Transposes a 3x3 short matrix through a 16-byte-stride int local, negates two
* of the local's rows, and transposes it back.
*
* The observed instructions read m in the order 0,3,6,1,4,7,2,5,8 and write it
* back in the same order, while the local's three groups sit 16 bytes apart at
* sp+0, sp+16 and sp+32. A 16-byte stride with the values stored as full words
* means the local is `int t[3][4]`; its fourth column is never read.
*
* Rows 0 and 2 of the local are negated (m[0],m[3],m[6] and m[2],m[5],m[8]);
* row 1 (m[1],m[4],m[7]) is left alone. The negation is a separate
* read-modify-write, so each of those six slots is stored twice, and cc1 does
* not eliminate the first store.
*/
void func_8009F4B4(short *m)
{
int t[3][4];
t[0][0] = m[0];
t[0][1] = m[3];
t[0][2] = m[6];
t[1][0] = m[1];
t[1][1] = m[4];
t[1][2] = m[7];
t[2][0] = m[2];
t[2][1] = m[5];
t[2][2] = m[8];
t[0][0] = -t[0][0];
t[0][1] = -t[0][1];
t[0][2] = -t[0][2];
t[2][0] = -t[2][0];
t[2][1] = -t[2][1];
t[2][2] = -t[2][2];
m[0] = t[0][0];
m[3] = t[0][1];
m[6] = t[0][2];
m[1] = t[1][0];
m[4] = t[1][1];
m[7] = t[1][2];
m[2] = t[2][0];
m[5] = t[2][1];
m[8] = t[2][2];
}
+62
View File
@@ -0,0 +1,62 @@
/*
* func_80106AA8 — 136 bytes at 0x80106AA8..0x80106B30
*
* Goal B, Phase 11. **First attempt.** Found by the REDUNDANCY filter (cookbook 109): 136 bytes,
* **zero branches**, and the same five-instruction shape repeated eight times. Not adjacent to any
* of this worker's matches — the ranker's redundancy score is what surfaced it.
*
* A 4-byte command/parameter burst: eight stores through four global POINTERS, each pointer
* re-loaded immediately before its store.
*
* *D_80120154 = 2;
* *D_8012015C = a0[0]; *D_80120160 = a0[1];
* *D_80120154 = 3;
* *D_80120158 = a0[2]; *D_8012015C = a0[3];
* *D_80120160 = 32;
* return 0;
*
* The observed instructions are (34 words, no branches, no frame, no callee-saved register):
* lui v1,0x8012 / lw v1,0x154(v1) / li v0,2 / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x15c(v1) / lbu v0,0(a0) / nop / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x160(v1) / lbu v0,1(a0) / nop / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x154(v1) / li v0,3 / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x158(v1) / lbu v0,2(a0) / nop / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x15c(v1) / lbu v0,3(a0) / nop / sb v0,0(v1)
* lui v1,0x8012 / lw v1,0x160(v1) / li v0,32 / sb v0,0(v1)
* jr ra / move v0,zero
*
* THE RE-READ GRANULARITY IS THE **PER-STATEMENT** SETTING (cookbook 105). Every one of the eight
* stores re-materialises its pointer from the global with its own `lui`/`lw` pair, and the load of
* `a0` is repeated for each byte. That is the same dial measured at the opposite end from the 700 B
* row's *once per statement* — here the dial's "per statement re-read" setting is exactly right, on a
* row with no local pointer at all. Together with 0x8006BC74 (per statement, 46 times) and
* 0x800320D8 (once per block), the dial now has three measured settings.
*
* The 0x154/0x158/0x15c/0x160 offsets are a 4-pointer table at 0x80120154 with 0x80120158 and
* 0x8012015C both used twice and 0x80120154/0x80120160 used twice each — the write order is
* 0x154, 0x15c, 0x160, 0x154, 0x158, 0x15c, 0x160, i.e. NOT monotonic, and the source order must be
* reproduced exactly.
*
* LIMITS: the function name and the meaning of the four pointers and of the constants 2/3/32 are
* hypotheses reconstructed from the disassembly; only the compiled bytes are evidence. The pointers
* are declared `unsigned char *` because the stores are `sb` and the sources `lbu`; the registry only
* supplies the ADDRESS, so the pointer-ness comes from these declarations. No symbol rows required —
* the `D_XXXXXXXX` names resolve implicitly.
*/
extern unsigned char *D_80120154;
extern unsigned char *D_80120158;
extern unsigned char *D_8012015C;
extern unsigned char *D_80120160;
int func_80106AA8(unsigned char *a0)
{
*D_80120154 = 2;
*D_8012015C = a0[0];
*D_80120160 = a0[1];
*D_80120154 = 3;
*D_80120158 = a0[2];
*D_8012015C = a0[3];
*D_80120160 = 32;
return 0;
}