diff --git a/config/regions.tsv b/config/regions.tsv index 4ffea24..ecf785b 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -106,6 +106,7 @@ 0x800262F8 0x80026304 src/func_800262F8.c 0x800263A8 0x800263E8 src/func_800263A8.c 0x80026650 0x800266A8 src/func_80026650.c +0x800266A8 0x800266EC src/func_800266A8.c 0x800267C0 0x800267CC src/func_800267C0.c 0x800268C4 0x800268F4 src/func_800268C4.c 0x80026C2C 0x80026C7C src/func_80026C2C.c @@ -470,6 +471,7 @@ 0x800A745C 0x800A74BC src/func_800A745C.c 0x800A74BC 0x800A74D0 src/func_800A74BC.c 0x800A8224 0x800A82D0 src/func_800A8224.c +0x800A8920 0x800A8984 src/func_800A8920.c 0x800A8B48 0x800A8B8C src/func_800A8B48.c 0x800A9C24 0x800A9CC4 src/func_800A9C24.c 0x800A9D58 0x800A9D90 src/func_800A9D58.c @@ -588,6 +590,7 @@ 0x800FB5D4 0x800FB5DC src/func_800FB5D4.c 0x800FB5E4 0x800FB5FC src/func_800FB5E4.c 0x800FB6C4 0x800FB6D8 src/func_800FB6C4.c +0x800FB758 0x800FB7C8 src/func_800FB758.c maspsx=epilogue 0x800FBDF8 0x800FBE04 src/func_800FBDF8.c 0x800FBE04 0x800FBE84 src/func_800FB13C.c 0x800FBE84 0x800FBF5C src/func_800FBE84.c diff --git a/config/symbols.tsv b/config/symbols.tsv index 8a9f05f..04e085d 100644 --- a/config/symbols.tsv +++ b/config/symbols.tsv @@ -195,6 +195,7 @@ D_8012215C 0x8012215C gp D_80122160 0x80122160 gp D_80122164 0x80122164 gp D_80122168 0x80122168 gp +D_8012216C 0x8012216C gp D_80122170 0x80122170 gp D_80122174 0x80122174 gp D_80122178 0x80122178 gp diff --git a/phase-ends/logs/Phase12.md b/phase-ends/logs/Phase12.md index c3774ff..091305b 100644 --- a/phase-ends/logs/Phase12.md +++ b/phase-ends/logs/Phase12.md @@ -893,3 +893,68 @@ offer to label the two buckets is now backed by six measured rows. The offer was precise reason worth stating — an OUTCOME tally of six finished rows is a measurement of the past, while the note-wording proxy I tested is a PREDICTOR for the remaining rows, and the predictor failed at 3/7. The tally is recorded as the tally; it does not re-rank anything. + +### The harness-class hypothesis in the index is wrong on TWO rows (worker C) + +The index marks rows as `HARNESS ROW (finding-40 class)`, and **finding 40 is the `nopmarker` +predicate**. Worker C measured both tokens on `0x800FBB20` (128 B): **`--honour-nop-marker` changes +nothing (132 bytes either way), while `--fill-epilogue` takes it to 128 = the correct length.** The +original ends `lw ra,28(sp) / lw s0,24(sp) / jr ra / addiu sp,sp,32` — the release IN the jump's slot +— which is **shape B** of the epilogue transform (the `lw ra` is not immediately before the release; +`lw s0,24(sp)` sits between them), so the release moves into the slot and the trailing nop is +DROPPED. A previous session read it as a `nopmarker` row and stopped at 132. + +That is the second row this session whose recorded harness class named the wrong token family, and +its remaining residual is **1 byte**: at `0x800FBB4C` the original has `move a1,a0` (`0x00802821`, +copying the register `move a0,zero` just set) where every spelling gives `move a1,zero` +(`0x00002821`, re-materialised from `$zero`). The source passed the same value twice and the +original cc1 reused `a0`; ours prefers `$zero` for a duplicated constant. Four spellings +(`f(0,0)`; a named `int z = 0;` at top; the same inside the `if`; `char *z = 0;` twice) all give +`move a1,zero`. **Untried lever, recorded for whoever takes it: a duplicated value that is NOT +compile-time constant, so cc1 must keep it in `a0`.** + +### Worker C declined `0x800F6948` on cost, and the judgement is endorsed + +Not a classification — a **cost judgement, reported rather than taken**, and it is the right call: +the row is a trampoline with **5 callees (one unregistered), a 9-argument signature whose 9th +parameter is read from `56(sp)`, and its own outgoing stack argument area**, and a previous session's +floor on it is `116 / differing_bytes=4`. Reconstructing five prototypes and a nine-parameter +signature to chase 4 bytes, **at a dependency surface where a wrong prototype is invisible until the +gate**, is a bad trade against two rows with independent structures — and C said so and asked rather +than deciding silently or grinding. Recorded as `NOT ATTEMPTED, cost-declined` with the reason, so +the row is not silently dropped. **This is the behaviour I want from all four workers: a cost +judgement stated, with the evidence, and the call handed up.** + +### Worker A adds a cheap predictive test to the cross-jump entry (cookbook 86) + +`0x80107CCC` (76 B) was re-taken with the `goto` lever fully applied — its guard is solved — and it +still resists: the original has **two inline `jr ra` return-0 blocks** (0xCDC and 0xD10) plus the +found-case `jr ra` (0xCE4), and every spelling makes cc1 merge the two identical `return 0;`s into +`j ` (80 vs 76). Three levers tried and why each fails: two plain returns → shared; +`goto zeroret;` + a label → the extra label restructures the head and breaks the guard (72/59); +labels ordered to match the original's layout → 80. **The addition:** *the cross-jump needs **two +identical return blocks** to fire.* That is exactly why the sibling `0x8002DF1C` **closed** with the +same lever — it has only **one** `return 0;`. So **"does the original show two identical inline +returns?" is the cheap test for whether the goto lever will suffice**, and it is now the entry's +first line rather than something a worker finds after six spellings. + +Also from A: `0x800FB758` (112 B) was closed by the `epilogue` token at the first spelling, and its +recorded 6-byte residual *is* the inserted load-delay `nop` (shape A). **One encode note with a second +instance in A's own claims: `D_80122140` is marked `gp` in the registry but the original writes it +ABSOLUTELY (`lui at,0x8012 / sw v0,0x2140(at)`), so the source must use the LITERAL address** — the +symbol spelling lets the harness rewrite it to `%gp_rel` and fails. That is cookbook 16/46's per-SITE +behaviour, now with worked instances in three workers' partitions. + +### Worker B's symbol request, verified from two independent directions before acting + +B asked for `D_8012216C 0x8012216C gp` for row `0x800FFB74`, having verified it against a **scratch +registry** (finding 90's documented technique) and **without touching `config/`** — the correct +conduct for a shared tracked file. I verified it independently rather than taking the request: +`0x800FFB74` reads **eight** gp globals (`gp+2096 .. gp+2128`), and the implied gp base from +`D_8012216C` at `gp+2100` is **`0x80121938`**. That base is confirmed by a second, unrelated +observation already on the record — worker C measured `D_801224DC` as `gp+0xBA4`, and +`0x80121938 + 0xBA4 = 0x801224DC`. Two independent derivations, same base, so the marker is right and +the symbol is added. Gate re-run with it (`c_regions=666, differing_bytes=0, MATCH`) and +`make check` exit 0, because adding a symbol changes the environment every region is compiled in. + +**Position: 657 bodies / 666 regions. Phase: 602 → 657 = +55.** diff --git a/src/func_800FBF5C.c b/src/func_800FBF5C.c new file mode 100644 index 0000000..3142d80 --- /dev/null +++ b/src/func_800FBF5C.c @@ -0,0 +1,25 @@ +typedef struct { + int f0; + int f4; + char pad[24]; +} Rec_801455F0; + +extern Rec_801455F0 D_801455F0[8]; +extern int D_80122790; +extern int D_80122794; + +void func_800FBF5C(void) +{ + Rec_801455F0 *p; + int i; + + i = 7; + p = D_801455F0; + p += 7; + for (; i >= 0; i--) { + p->f4 = 0; + p--; + } + D_80122790 = (int)D_801455F0; + D_80122794 = (int)D_801455F0; +} diff --git a/src/func_801092C0.c b/src/func_801092C0.c index fc3eadd..c136964 100644 --- a/src/func_801092C0.c +++ b/src/func_801092C0.c @@ -1,15 +1,69 @@ +/* + * func_801092C0 — 52 bytes at 0x801092C0..0x801092F4 + * + * Clears the first field of `count` 32-byte records in the table at D_801461C8, + * starting at record `start`. Matched on the SIXTH spelling, from the + * un-attempted band (an index row whose attempt predates the current lever set). + * FOUR separate levers were required and three of them are new: + * + * 1. THE PARAMETERS ARE UNSIGNED. `int start, int count` gives `blez a1` and a + * SIGNED `slt`; `unsigned int` reproduces the original's `beqz a1` AND `sltu`. + * The entry test alone is not enough to tell -- both forms test the count, and + * the second instruction is what names the signedness. + * 2. **`volatile` ON THE TABLE POINTER IS REQUIRED, AND FOR AN ALIAS REASON.** + * With `extern Rec *D_801461C8;` cc1 HOISTS the pointer load out of the loop + * (`lw a2,25032(a2)` before the loop, one instruction shorter inside it) because + * the store's type (a pointer field of `Rec`) is provably a different type from + * the global's own type. The original RELOADS it every iteration -- visible in + * the original at 0x801092D0/D4 -- so the load must be un-movable. `volatile` + * reproduces it exactly. This is a proof-by-emission, not a taste: the two + * spellings differ by the load's POSITION, not by its instruction. + * 3. **THE FRAME WAS A LOOP-SHAPE ARTIFACT (cookbook 19), on a FORWARD loop.** + * Written as `for (i = 0; i < count; i++)` cc1 emits an unused + * `addiu sp,sp,-8` / `addiu sp,sp,8` pair -- 64 bytes against 52. Cookbook 19 + * records this for a BACKWARD `for` on 0x800F7FB4 and warns that it does not + * generalise; here it generalises to the guard + `do`/`while` form: + * i = 0; if (count != 0) { do { ... } while (++i < count); } + * which removes the frame. Note the `++i` must be the WHILE's operand, and the + * body's index is the PRE-increment value -- the original increments `i` in the + * middle of the body (0x801092CC) and tests the incremented value. + * 4. **`p += i + start; p->p0 = 0;` AND NOT `p[i + start].p0 = 0;` -- the final + * `addu` TARGETS THE BASE REGISTER.** With the subscript form cc1 writes the + * address sum into the OFFSET's register (`addu v0,v0,v1`, store via v0) and the + * branch delay slot gets filled with a repeated index computation (a rotated + * loop). Accumulating into a pointer variable makes the sum's destination the + * BASE register -- the original's `addu v1,v1,v0` and `sw zero,0(v1)` -- and the + * store then falls into the branch delay slot, which is the original's shape. + * The volatile in (2) also blocks the strength reduction that would otherwise + * turn this into a single `p += 32` induction variable. + * + * LIMITS: `D_801461C8` is named for its address; it is used here as a pointer to a + * 32-byte record whose FIRST field is a pointer, but the record's real type and the + * meaning of the cleared field are NOT established by these bytes -- a 4-byte store + * of zero at offset 0 is all the evidence there is, and the `char pad[28]` is a + * stride, not a recovered layout. Whether the original source said `volatile` is + * unknowable; what is knowable is that the load is un-movable in the original, and + * `volatile` is the smallest lever that reproduces it here. One caller is not + * identified, so the meaning of `start` relative to the table is not recovered. + */ typedef struct { - int f0; + void *p0; char pad[28]; } Rec_801461C8; -extern Rec_801461C8 *D_801461C8; +extern Rec_801461C8 * volatile D_801461C8; -void func_801092C0(int start, int count) +void func_801092C0(unsigned int start, unsigned int count) { - int i; + unsigned int i; - for (i = 0; i < count; i++) { - D_801461C8[i + start].f0 = 0; + i = 0; + if (count != 0) { + do { + Rec_801461C8 *p = D_801461C8; + + p += i + start; + p->p0 = 0; + } while (++i < count); } } diff --git a/src/func_8010AA28.c b/src/func_8010AA28.c new file mode 100644 index 0000000..fe4acdf --- /dev/null +++ b/src/func_8010AA28.c @@ -0,0 +1,92 @@ +/* + * func_8010AA28 — 112 bytes at 0x8010AA28..0x8010AA98 + * + * Hypothesis, not a claim about meaning: a one-shot initialiser guarded by a flag global. On the + * first call only it sets the guard, invokes a syscall wrapper, hands a fixed address to two + * helpers, calls a fourth helper with a constant triple, stores that helper's result into a + * second global and passes it on, then invokes a second syscall wrapper and returns. Every later + * call does nothing. + * + * Original words: + * 3C028012 lui v0,0x8012 + * 8C421118 lw v0,4376(v0) ; (L) v0 = D_80121118 (ABSOLUTE, 2 instructions) + * 27BDFFE8 addiu sp,sp,-24 + * 14400014 bnez v0,0x8010AA88 ; already initialised -> the shared exit + * AFBF0010 sw ra,16(sp) ; (delay) + * 24020001 li v0,1 + * 3C018012 lui at,0x8012 + * 0C040FFB jal 0x80103FEC ; (1) an argument-less syscall wrapper + * AC221118 sw v0,4376(at) ; (delay) D_80121118 = 1 + * 3C048011 lui a0,0x8011 + * 0C042AB0 jal 0x8010AAC0 ; (2) + * 2484A388 addiu a0,a0,-23672 ; (delay) a0 = 0x8010A388 (`la` form) + * 3C04F000 lui a0,0xF000 + * 34840009 ori a0,a0,0x9 ; a0 = 0xF0000009 + * 24050020 li a1,32 + * 24062000 li a2,8192 + * 0C042017 jal 0x8010805C ; (3) + * 00003821 move a3,zero ; (delay) a3 = 0 <- FILLED SLOT, the whole row + * 00402021 move a0,v0 ; a0 = the result + * 3C018012 lui at,0x8012 + * 0C042005 jal 0x80108014 ; (4) + * AC2410B0 sw a0,4272(at) ; (delay) D_801210B0 = a0 + * 0C040FF3 jal 0x80103FCC ; (5) a second argument-less syscall wrapper + * 00000000 nop + * $E: 8FBF0010 lw ra,16(sp) + * 27BD0018 addiu sp,sp,24 + * 03E00008 jr ra + * 00000000 nop + * + * ONE SPELLING, WITH THE REGION TOKEN `maspsx=moves` — AND THE TOKEN IS THE WHOLE ROW. + * + * The recorded class was "maspsx mutual exclusion" (cookbook 17/61) with the measurements + * `maspsx=off -> 2 differing bytes` and `maspsx on -> 124 bytes, three unfilled delay slots`. + * Both are the SAME exclusion seen from its two sides, and `maspsx=moves` is the mode that + * satisfies both at once: + * - DEFAULT (maspsx on): 124 bytes. maspsx emits its own unconditional `nop` after every jump + * whose slot cc1 left empty, so the argument set-ups cc1 put before the `jal`s are never + * moved into the slots (three slots stay unfilled, +12 bytes). + * - `maspsx=off`: the LENGTH is right because GNU `as` reorder mode performs the fills — but + * `as` expands cc1's `move` macro as `or` (funct 0x25) where the original has `addu` + * (funct 0x21), so the two `move`s in this row come out 2 bytes wrong. + * - `maspsx=moves`: the `move`->`addu` rewrite ONLY (no maspsx nop insertion), which leaves GNU + * `as` in reorder mode to fill the jump slots exactly as ASPSX did. **112 bytes, 0 differing.** + * This is the first row I know of where `maspsx=moves` has closed a region; the two `move`s it has + * to rewrite here are `move a3,zero` (the 4th argument of call 3, which lands IN the `jal` slot) + * and `move a0,v0` (the result forwarding between calls 3 and 4). + * + * TWO FURTHER FACTS READ OFF THE BYTES: `D_80121118` and `D_801210B0` are accessed ABSOLUTELY + * (`lui`+`lw`, `lui at`+`sw`) and are address-named placeholders, not registry gp symbols; and the + * fixed address passed to call 2 is materialised with `addiu` (the symbol/`la` form, cookbook + * 4/89), so it is written as an address-named array. + * + * LIMITS: the function name, all five callees, the two globals, the constant triple + * (0xF0000009, 32, 8192), the fixed address 0x8010A388 and the fact that calls 1 and 5 take no + * arguments are read from the instruction encodings; only the bytes are evidence. Nothing about + * what the initialiser sets up, or what the two syscall wrappers do, is established here. + */ + +extern int D_80121118; +extern int D_801210B0; +extern char D_8010A388[]; + +void func_80103FEC(void); +void func_8010AAC0(char *); +int func_8010805C(int, int, int, int); +void func_80108014(int); +void func_80103FCC(void); + +void func_8010AA28(void) +{ + int r; + + if (D_80121118 != 0) + return; + D_80121118 = 1; + func_80103FEC(); + func_8010AAC0(D_8010A388); + r = func_8010805C(0xF0000009, 32, 8192, 0); + D_801210B0 = r; + func_80108014(r); + func_80103FCC(); +}