From 98a15efca573e7a2a5ba227237d90a7b10c4620f Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 18:47:53 -0400 Subject: [PATCH] phase12: merge 18 (659 bodies) + cookbook 61b: a duplicated mask is a WIDTH fact, not a schedule --- config/regions.tsv | 1 + docs/MATCHING_COOKBOOK.md | 30 +++++++++++++ src/func_8002DE28.c | 91 +++++++++++++++++++++++++++++++++++++++ src/func_8002FB54.c | 80 ++++++++++++++++++++++++++++++++++ src/func_800FBF5C.c | 8 ++-- 5 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 src/func_8002DE28.c create mode 100644 src/func_8002FB54.c diff --git a/config/regions.tsv b/config/regions.tsv index fda49c0..68fbc5d 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -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 diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index 6c8bea2..0bbf08c 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -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 s-q`, +`h+q 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 diff --git a/src/func_8002DE28.c b/src/func_8002DE28.c new file mode 100644 index 0000000..6b07535 --- /dev/null +++ b/src/func_8002DE28.c @@ -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; +} diff --git a/src/func_8002FB54.c b/src/func_8002FB54.c new file mode 100644 index 0000000..d63e133 --- /dev/null +++ b/src/func_8002FB54.c @@ -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; +} diff --git a/src/func_800FBF5C.c b/src/func_800FBF5C.c index d1dbb33..a8c9855 100644 --- a/src/func_800FBF5C.c +++ b/src/func_800FBF5C.c @@ -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; -} +} \ No newline at end of file