phase12: merge 18 (659 bodies) + cookbook 61b: a duplicated mask is a WIDTH fact, not a schedule

This commit is contained in:
Christopher Williams
2026-09-24 18:47:53 -04:00
parent dd28188b43
commit 98a15efca5
5 changed files with 205 additions and 5 deletions
+1
View File
@@ -160,6 +160,7 @@
0x8002F2F8 0x8002F300 src/func_8002F2F8.c
0x8002F404 0x8002F450 src/func_8002F404.c
0x8002FAB8 0x8002FB54 src/func_8002FAB8.c
0x8002FB54 0x8002FBC0 src/func_8002FB54.c
0x800301FC 0x8003022C src/func_800301FC.c
0x8003022C 0x80030284 src/func_8003022C.c
0x80030284 0x800302DC src/func_80030284.c
1 # Code-region registry: one C region per matched function.
160 0x8002F2F8
161 0x8002F404
162 0x8002FAB8
163 0x8002FB54
164 0x800301FC
165 0x8003022C
166 0x80030284
+30
View File
@@ -1065,6 +1065,36 @@ broken to `or`), so it is the natural next test. **Not generalised past the evid
argued from one row, and the entry's conclusion that the *rare-epilogue* half still needs a post-pass
is untouched — `0x800FA5D8` needs all four fills back, which is a different missing-instruction shape.
### 61b. A DUPLICATED MASK is evidence of the variable's WIDTH, not of the code's shape
Phase 12, worker A, `0x8002FB54` (108 B, merged). The index had it at 112 (4 over) and the lever was
**the declared type of the result variable**:
```c
int r = 1; ... r &= 0xFF; /* correct LENGTH, 20 differing bytes -- all where the mask lands */
unsigned char r; ... /* byte-exact */
```
A byte-typed object makes cc1 **mask at EVERY READ**, so the original's **two** `andi v1,t1,255` — one
in the copy path's jump delay slot, one at the fall-through merge — are the signature of a *one-byte
variable*. An explicit `&= 0xFF` is a single assignment and gets scheduled once, so **no amount of
placement can fake a byte-typed read pattern.** This is the unsigned-char behaviour of findings 7/48
used as a **width** lever rather than a value lever.
**The generalisable rule, and why it is filed separately: a duplicated mask is evidence of the WIDTH of
the variable, not of the code's shape.** A worker who reads two `andi ...,255` as "the same mask
scheduled twice" will spend spellings on the schedule; the answer is the declaration.
Two confirmations measured on the same row: the selector is tested with `sltiu`, so the parameter is
`unsigned int` (finding 48) — **and a 4-word copy is a struct assignment** (findings 76/102).
**Resolved semantics for `0x80011484` (132 B), which the index recorded as UNRESOLVED** ("108 vs 132,
my reconstruction is wrong"). Worker A derived it from the bytes: an **octagonal distance
approximation** — `s = |dx|+|dz|`, `d = ||dx|-|dz||`, `h = s>>1`, `q = s>>2`, then `d<h -> s-q`,
`h+q<d -> s`, else `s-(s>>3)`, with both absolutes spelled as SWAPPED SUBTRACTIONS (finding 101). The
build is 120; the residual is cc1 hoisting all four loads and merging the two abs tests, i.e. the
scheduler class. **The index's "unresolved" is now a named class rather than a gap.**
### 62. Fail fast on an invalid region row
A worker placed the source md5 in a claim row's 4th column. `sf3_merge` passed it through as a region
+91
View File
@@ -0,0 +1,91 @@
/*
* func_8002DE28 — 140 bytes at 0x8002DE28..0x8002DEB4
*
* PHASE 12 WORKER C. The SIBLING of the already-registered `func_8002DF1C` (see
* src/func_8002DF1C.c, matched by worker B): the same 76-byte record table at D_80121BFC and the
* same bounds guard, but this one COPIES a 32-byte sub-record out instead of reading two fields.
* The sibling's header supplied the guard lever; the copy shape had to be measured here.
*
* MATCH: candidate_bytes=140, differing_bytes=0, result=MATCH, exit 0 on the DEFAULT toolchain.
*
* The observed instructions are:
* move a2,a0 index = a0 (the index must survive a0 being reused)
* lw v0,2428(gp) v0 = D_801222B4
* nop
* slt v0,a2,v0 index < limit
* beqz v0,0x8002DE48 if (!(index < limit)) -> the failure block
* move t0,a1 (delay slot) dest = a1
* bgez a2,0x8002DE50 if (index >= 0) -> THE BODY
* li v0,1 (delay slot) the return value, hoisted
* 48: j 0x8002DEAC the failure jumps over the body to the shared exit
* move v0,zero (delay slot)
* 50: lw a0,708(gp) a0 = D_80121BFC <- the base is LOADED (a POINTER variable)
* sll v1,a2,0x2 ---- index * 76 built 4 -> 5 -> 20 -> 19 -> 76 ----
* addu v1,v1,a2
* sll v1,v1,0x2
* subu v1,v1,a2
* sll v1,v1,0x2
* addu v1,v1,a0 v1 = &rec[index]
* lw a0,4(v1) ---- the 32-byte copy, four words at a time ----
* lw a1,8(v1)
* lw a2,12(v1)
* lw a3,16(v1)
* sw a0,0(t0)
* sw a1,4(t0)
* sw a2,8(t0)
* sw a3,12(t0)
* lw a0,20(v1) ---- the second group of four ----
* lw a1,24(v1)
* lw a2,28(v1)
* lw a3,32(v1)
* sw a0,16(t0)
* sw a1,20(t0)
* sw a2,24(t0)
* sw a3,28(t0)
* AC: jr ra
* nop
*
* THREE FACTS, each of which alone leaves the row wrong:
* 1. **THE GUARD MUST `goto` INTO THE BODY** — the same lever worker B documented for the
* registered sibling (`src/func_8002DF1C.c`): the natural `if (index < D && index >= 0) {
* body; return 1; } return 0;` inlines the body with the failure block at the end and emits
* `bltz` where the original has `bgez`.
* 2. **`D_80121BFC` IS A POINTER VARIABLE, not an array.** The original LOADS it (`lw a0,708(gp)`)
* and adds the offset; declaring `extern char D_80121BFC[]` instead makes cc1 take the
* symbol's ADDRESS (`addiu v1,gp,708`) and the shape is wrong. Measured: with the array
* spelling the row is 164 bytes; with `extern int *D_80121BFC` it is 140.
* 3. **THE COPY IS A 32-BYTE STRUCT ASSIGNMENT.** The original moves the eight words as TWO
* GROUPS OF FOUR with `a0`-`a3`, which is a struct copy; writing it as eight `dest[i] =
* rec[i+1];` statements makes cc1 use ONE register (`v0`) and emit load/store pairs with a
* nop between each — 164 bytes, and the loads/stores interleave instead of blocking. Measured:
* the indexed spelling is 164; `*dest = *(struct S32 *)...;` is 140 and byte-exact. The
* sub-record begins at +4 of the record and is 32 bytes.
* The `move a2,a0` at the top and `move t0,a1` in the first branch's delay slot are the two
* copies the allocator must make because `a0`-`a3` are all reused as the copy's temporaries: the
* index lives in `a2` and the destination in `t0` across the copy. They appear only when the body
* is a block move, so they are a CONSEQUENCE of fact 3 and not a separate lever.
*
* LIMITS: the layout (a 76-byte record whose 32-byte payload starts at +4) and the guard's
* meaning are read off the instruction stream with no evidence for a struct declaration beyond
* that shape; `D_801222B4` is typed `int` because it is compared against a word index. No callees,
* so this row has NO dependencies. The sibling relationship is an observation about the bytes, not
* a claimed source relationship.
*/
struct S32 { int w[8]; };
extern int D_801222B4;
extern int *D_80121BFC;
int func_8002DE28(int a0, int *a1)
{
int index = a0;
struct S32 *dest = (struct S32 *)a1;
if (index < D_801222B4 && index >= 0)
goto body;
return 0;
body:
*dest = *(struct S32 *)((char *)D_80121BFC + index * 76 + 4);
return 1;
}
+80
View File
@@ -0,0 +1,80 @@
/*
* func_8002FB54 — 108 bytes at 0x8002FB54..0x8002FBC0
*
* A three-way dispatch on an unsigned selector that either copies one word, copies
* a whole 4-word vector, or refuses — and then, only when the result byte is 1,
* records the source pointer alongside the copy. Returns that result byte.
*
* The observed instructions are:
* move t0,a0 p = argument0
* bne a2,zero,L1 \
* _li t1,1 / r = 1
* lw v0,0(t0) \
* j L4 | selector == 0: ONE word
* _sw v0,8(a3) / dst[2] = p[0]
* L1:
* sltiu v0,a2,3 UNSIGNED compare
* beq v0,zero,L3
* _nop
* lw v0,0(t0) / lw v1,4(t0) / lw a0,8(t0) / lw a1,12(t0)
* sw v0,8(a3) / sw v1,12(a3) / sw a0,16(a3) / sw a1,20(a3) dst[2..5] = p[0..3]
* j L5
* _andi v1,t1,255 (delay slot)
* L3:
* move t1,zero r = 0
* L4:
* andi v1,t1,255 <- the SAME mask again, on the other paths
* L5:
* li v0,1
* bne v1,v0,L6
* _nop
* sw t0,24(a3) dst[6] = p
* L6:
* jr ra
* _move v0,v1 return r
*
* THE LEVER IS THE TYPE OF `r`, AND IT IS THE ONLY THING THAT MATCHED. With
* `int r = 1; ... r &= 0xFF;` the row is the correct length with 20 differing
* bytes — every difference is where the mask lands. With **`unsigned char r`** it
* is exact, because the object is a byte: cc1 then masks at EVERY READ of `r`
* (the comparison and the return), which is why the original carries the
* `andi v1,t1,255` TWICE — once in the copy path's jump delay slot and once at the
* fall-through merge point. The explicit `&= 0xFF` statement is a single
* assignment and gets scheduled once, so it cannot reproduce a byte-typed read
* pattern. This is cookbook 7/48's unsigned-char behaviour used as a *width* lever:
* the duplicated mask is the tell that the variable is one byte wide.
*
* The selector test is `sltiu a2,3` — an UNSIGNED comparison — so the third
* parameter is declared `unsigned int` (cookbook 48: `sltiu` versus `slti` is the
* signedness evidence). The second parameter is never touched by the body.
*
* The four-word copy is a STRUCT ASSIGNMENT (`*(struct V4 *)&dst[2] = *src;`), not
* four statements: the emitted four-loads-then-four-stores batch is the signal
* (cookbook 76/102).
*
* LIMITS: the record types, the meaning of the selector values 0/3 and of the
* flag stored at dst[6], and the second parameter's existence are hypotheses read
* off the instruction shape. Only the compiled bytes are evidence.
*/
struct V4 {
int v[4];
};
int func_8002FB54(struct V4 *src, int a1, unsigned int a2, int *dst)
{
unsigned char r = 1;
if (a2 == 0) {
dst[2] = src->v[0];
} else if (a2 < 3) {
*(struct V4 *)&dst[2] = *src;
} else {
r = 0;
}
if (r == 1)
dst[6] = (int)src;
return r;
}
+3 -5
View File
@@ -8,15 +8,13 @@ void func_800FBF5C(void)
Rec_801455F0 *p;
int i;
int n;
n = 7;
i = 7;
p = D_801455F0;
p += n;
for (; i >= 0; i--) {
while (i >= 0) {
p->f4 = 0;
p--;
i--;
}
D_80122790 = (int)D_801455F0;
D_80122794 = (int)D_801455F0;
}
}