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:
+1022
-1023
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
@@ -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.**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user