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:
@@ -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
|
||||
|
||||
|
@@ -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.**
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user