phase12: merge 18 (659 bodies) + cookbook 61b: a duplicated mask is a WIDTH fact, not a schedule
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user