phase11: merge 14 + cookbook 79-81 — worker A's redundancy ranker

Worker A built a repetitiveness score (repeated 2/3/4-instruction opcode subsequences,
normalised by body length) and produced the cleanest controlled comparison in the phase:
3 spellings on a 248 B repetitive row vs 9 failures on a 176 B tie-break-dense one. That
converts 'prefer a repetitive body' from a hunch into a sortable number, so size is
deprioritised as the ranking signal.

Also recorded: a transposed temp array is byte-required (int m[3][4] used as m[c][r]) with an
exact diagnostic -- right length + right instruction multiset + residual only on sp-relative
offsets means the frame LAYOUT is wrong, not the code; and when the original stores the same
slot twice, suspect two source statements rather than a scheduler quirk (GCC 2.7.2 has no DSE).
This commit is contained in:
Christopher Williams
2026-09-24 09:38:05 -04:00
parent 61be53994b
commit 1a6a00970f
3 changed files with 132 additions and 0 deletions
+1
View File
@@ -359,6 +359,7 @@
0x8009F3A8 0x8009F4B4 src/func_8009F3A8.c
0x8009F5AC 0x8009F6A0 src/func_8009F5AC.c
0x8009F6A0 0x8009F798 src/func_8009F6A0.c
0x8009F798 0x8009F890 src/func_8009F798.c
0x800A2F20 0x800A2F44 src/func_800A2F20.c
0x800A34E8 0x800A3540 src/func_800A34E8.c
0x800A45E0 0x800A466C src/func_8009E8D0.c
1 # Code-region registry: one C region per matched function.
359 0x8009F3A8
360 0x8009F5AC
361 0x8009F6A0
362 0x8009F798
363 0x800A2F20
364 0x800A34E8
365 0x800A45E0
+35
View File
@@ -1274,3 +1274,38 @@ off) shows the RTL order is stores-then-copy for worker A's GTE row, so that res
And a byte-required operand order: `a0 == D_8012204C` matches where `D_8012204C == a0` does
not (worker A, `0x800B62C8`, 2 bytes).
### 79. REDUNDANCY-DENSITY IS THE RANKER, AND IT IS NOW MEASURABLE (worker A)
Worker A built a repetitiveness score over its partition — **count of repeated 2/3/4-instruction
opcode subsequences, normalised by body length** — and used it to pick the densest mid-size
row. The result is the cleanest controlled comparison in the phase:
| row | size | rep-density | outcome |
|---|---|---|---|
| `0x8009F798` | 248 B, 0 calls | **2.10** | **3 spellings → MATCH** |
| worker D's `0x8009F798`-sized row | 248 B | — | 4 spellings |
| `0x80017C6C` | 176 B | tie-break-dense | **9 spellings, 42 differing bytes, classified** |
**3 spellings on a 248 B repetitive row against 9 failures on a 176 B tie-break-dense one.**
This is finding 68 made operational: it converts "prefer a repetitive body" from a hunch into a
number you can sort by. Size is deprioritised.
### 80. A TRANSPOSED temp array, and the exact diagnostic (worker A)
`0x8009F798`: the original's slot for matrix cell (r,c) is `4*r + 16*c` — a **16-byte stride on
the SECOND index** — so `int m[3][4]` must be written `m[c][r]`. Natural `m[r][c]` indexing gives
**CORRECT LENGTH (248) and 15 differing bytes, every one of them a stack offset.**
**Diagnostic (cookbook 59's cleanest instance):** *right length + right instruction multiset +
residual only on sp-relative offsets ⇒ the frame LAYOUT is the error, not the code.*
### 81. When the original stores the same slot TWICE, suspect TWO SOURCE STATEMENTS (worker A)
`0x8009F798`: phase 1 stores the un-negated value into all 9 slots; phase 2 negates 6 of them and
stores **again** into the same slots. GCC 2.7.2 has no DSE, so both stores appear. Writing
`m[c][r] = -a0[3*r + c];` directly (one statement per cell) is **244 B LENGTH-MISMATCH** — the
negations then land immediately after their own loads.
Same property as worker A's `buf[1]` finding in `0x80027CA0` (`buf[1] = a0[1]-a1[1];` then
`buf[1] = 0;`), now confirmed on a much larger body. **Not a scheduler quirk — a second statement.**
+96
View File
@@ -0,0 +1,96 @@
/*
* func_8009C904 — 548 bytes at 0x8009C904..0x8009CB28
*
* Hypothesis, not a claim about meaning: rotates three 3-component short vectors in
* place and offsets each result by a component of a shared vector. The body is the same
* 30-instruction block three times over (for a0[6], a0[7], a0[8]) with only the vector
* pointer changing, which is why it matched on the FIRST spelling.
*
* Per-block shape (s0 = a0, s1 = a1; buf is the 3-word scratch at sp+16):
* lw v0,24(s0) ; a0[6] <- reloaded for EVERY component read
* lh v0,0(v0)
* sw v0,16(sp) ; buf[0] = *(short *)(a0[6] + 0)
* lw v0,24(s0)
* lh v0,2(v0)
* sw v0,20(sp) ; buf[1]
* lw v0,24(s0)
* lh v0,4(v0)
* sw v0,24(sp) ; buf[2]
* move a0,s1 ; func_800F3C60(a1, buf, buf)
* addiu a1,sp,16
* move a2,a1
* jal 0x800F3C60
* sw v0,24(sp) ; (delay)
* lw a0,16(sp) ; buf[0] += a1[5]
* lw v0,20(s1)
* addu a0,a0,v0
* sw a0,16(sp)
* ... ; buf[1] += a1[6], buf[2] += a1[7]
* lw v0,24(s0) ; *(short *)(a0[6] + 0) = buf[0]
* sh a0,0(v0)
* ... ; +2 and +4
* then the identical block for a0[7] (offset 28) and a0[8] (offset 32), then the common
* epilogue (`lw ra,40(sp); lw s1,36(sp); lw s0,32(sp); addiu sp,sp,48; jr ra; nop`).
*
* THREE THINGS ARE BYTE-REQUIRED:
*
* 1. **The vector pointer is re-read from `a0[i]` for every single component** — six
* `lw v0,24(s0)`-style loads per block (three for the reads, three for the writes).
* Binding it once into a local (`int *p = (int *)a0[6];`) hoists the load and changes
* the bytes. So the source indexes the array expression inline at every use.
* 2. **The scratch buffer is `int buf[3]`**, not `short buf[3]`: the components are loaded
* with `lh` but stored with `sw` into word-spaced slots at sp+16, sp+20, sp+24.
* 3. **`func_800F3C60` is called with the SAME buffer as its second and third argument**
* (`move a0,s1; addiu a1,sp,16; move a2,a1`) — an in-place transform.
*
* The 548-byte body matching first attempt, against a 176-byte body that resisted nine
* spellings, is a clean instance of the phase-11 rule that redundancy predicts matchability
* far better than size does.
*
* LIMITS: the function name, the callee, the array offsets (6/7/8 for the vectors and
* 5/6/7 for the offsets), the element types (short components, word scratch) and the whole
* "vector rotate" reading are hypotheses taken from the instruction shape; only the bytes
* are evidence. The three blocks are written out explicitly because the original is
* unrolled — there is no loop in the object. func_800F3C60 is not registered and is
* referenced by its address-named spelling.
*/
void func_800F3C60(int *, int *, int *);
void func_8009C904(int *a0, int *a1)
{
int buf[3];
buf[0] = *(short *)(a0[6] + 0);
buf[1] = *(short *)(a0[6] + 2);
buf[2] = *(short *)(a0[6] + 4);
func_800F3C60(a1, buf, buf);
buf[0] += a1[5];
buf[1] += a1[6];
buf[2] += a1[7];
*(short *)(a0[6] + 0) = buf[0];
*(short *)(a0[6] + 2) = buf[1];
*(short *)(a0[6] + 4) = buf[2];
buf[0] = *(short *)(a0[7] + 0);
buf[1] = *(short *)(a0[7] + 2);
buf[2] = *(short *)(a0[7] + 4);
func_800F3C60(a1, buf, buf);
buf[0] += a1[5];
buf[1] += a1[6];
buf[2] += a1[7];
*(short *)(a0[7] + 0) = buf[0];
*(short *)(a0[7] + 2) = buf[1];
*(short *)(a0[7] + 4) = buf[2];
buf[0] = *(short *)(a0[8] + 0);
buf[1] = *(short *)(a0[8] + 2);
buf[2] = *(short *)(a0[8] + 4);
func_800F3C60(a1, buf, buf);
buf[0] += a1[5];
buf[1] += a1[6];
buf[2] += a1[7];
*(short *)(a0[8] + 0) = buf[0];
*(short *)(a0[8] + 2) = buf[1];
*(short *)(a0[8] + 4) = buf[2];
}