diff --git a/config/regions.tsv b/config/regions.tsv index 84cdfa8..4ffea24 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -91,6 +91,7 @@ 0x80025070 0x800250AC src/func_80025070.c 0x8002515C 0x80025198 src/func_8002515C.c 0x800254B0 0x800254EC src/func_800254B0.c +0x800256F0 0x80025758 src/func_800256F0.c 0x800259A0 0x800259DC src/func_800259A0.c 0x80025A2C 0x80025ADC src/func_80025A2C.c 0x80025ADC 0x80025B64 src/func_80025ADC.c diff --git a/phase-ends/logs/Phase12.md b/phase-ends/logs/Phase12.md index 247e718..c3774ff 100644 --- a/phase-ends/logs/Phase12.md +++ b/phase-ends/logs/Phase12.md @@ -848,3 +848,48 @@ watching the aggregate, and D's own conclusion is the re-rank criterion: not "wh `differing_bytes=0`. Per cookbook 50 the flag is diagnosis-only and never attaches to a claim row, so it stays a negative, but the row is now the cleanest post-pass/scheduler candidate in A's pool. * **A's `0x8010400C`** and **C's `0x80107874`/`0x800F6ED0`**: classified with mechanisms, no lever left. + +### Worker C gives finding 44 its concrete direction, and the phase an expectation + +**`0x800F7930` (96 B), residual 72 bytes, is a PURE REGISTER-ROLE SWAP** — the original starts +`move t0,a0` (record pointer into `$8`, freeing `a0` for the flag, which has the shorter live range); +every spelling keeps the record in `a0`. Four spellings tied. Recorded as finding 44 seen in the +mirror, because the useful part is the mechanism: + +> When the original is BRANCHY, an `&&`/`||` written on the **assignment** side gets BRANCHLESS +> codegen — the `&&` spelling compiles to `sltu v0,zero,v0; negu v0,v0; andi t0,v0,0x5`, which is 74 +> differing bytes and not the original's shape. The INVERTED spelling +> `flag = 5; if (a == 0 || b == 0) flag = 0;` restores the branchy form (a `beqz` with `li a0,5` +> hoisted into its delay slot, then a `bnez` skipping the `move a0,zero`) and drops the residual to +> the register swap alone. + +Finding 44's entry states this paired rule abstractly; this is the measured direction and the spelling. + +**`0x800256F0` (104 B) is the FIRST prio-1 row in this batch where the recorded mechanism WAS the +mechanism.** The note said "the original RELOADS the sub-object field after the call", and the store +after the call genuinely re-dereferences — `*(char *)(*(int *)((char *)a0 + 32) + 44) = 1;` — so the +`lw v1,32(s0)` after the `jal` is REQUIRED because the call may have changed `a0->sub`. Hoisting the +field into a local and reusing it gives 96 bytes, eight short, because cc1 then keeps it in `a0` +across the call and the re-load vanishes. Merged as a DEPENDENT claim: its callee `func_800F95CC` is +not a registered region and the two-argument prototype is inferred from the call sites (the +`move a1,a0` in each call's delay slot is why both arguments are the same pointer). + +**The expectation-setting number, and it is the most important one for the milestone projection.** +Worker C's outcome tally across its last six prio-1 rows: + +| residual kind | count | closable by spelling? | +|---|---|---| +| a cc1 behaviour (reorg duplication ×2, a loop shape cc1 never emits) | 3 | **no** | +| a clobber or arity fact (post-call re-load, 6-parameter signature, named binding) | 3 | yes | +| genuinely a register/spelling problem | 1 | yes | + +**So roughly half of the remaining prio-1 pool may be unreachable by source work.** That is not a +reason to stop — it is the number the milestone projection has to be made against, and it is why the +`negatives-d-unattempted.tsv` band (74 rows nobody pushed to a floor) matters: those rows are +unreachable-looking only because nobody has asked them the question with the current lever set. + +**Also recorded, in the workers' favour and against my own instruction:** worker C noted that its +offer to label the two buckets is now backed by six measured rows. The offer was still declined, for a +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. diff --git a/src/func_800256F0.c b/src/func_800256F0.c new file mode 100644 index 0000000..e6128e0 --- /dev/null +++ b/src/func_800256F0.c @@ -0,0 +1,75 @@ +/* + * func_800256F0 — 104 bytes at 0x800256F0..0x80025758 + * + * PHASE 12 WORKER C. **A DEPENDENT CLAIM (charter rule 2): the callee `func_800F95CC` is not a + * registered region**, so the two-argument prototype below is INFERRED from the call sites. Only + * the call sites' register setup is reproduced. + * + * MATCH: candidate_bytes=104, differing_bytes=0, result=MATCH, exit 0 on the DEFAULT toolchain. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw s0,16(sp) + * move s0,a0 s0 = a0 (the argument must survive the calls) + * bnez s0,0x8002570C if (a0 == 0) + * sw ra,20(sp) (delay slot) + * j 0x80025744 + * li v0,24 (delay slot) return 24; + * 0C: lw a0,32(s0) sub = *(int *)((char *)a0 + 32) + * nop + * beqz a0,0x80025734 if (sub != 0) { + * nop + * jal 0x800F95CC + * move a1,a0 (delay slot) func_800F95CC(sub, sub); + * lw v1,32(s0) *** THE FIELD IS RE-LOADED HERE *** + * li v0,1 + * j 0x80025740 + * sb v0,44(v1) (delay slot) *(char *)(*(int *)((char *)a0 + 32) + 44) = 1; + * 34: move a0,s0 } else { + * jal 0x800F95CC + * move a1,a0 (delay slot) func_800F95CC(a0, a0); + * 40: move v0,zero } + * 44: lw ra,20(sp) return 0; + * lw s0,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * THE LEVER IS THE RE-LOAD, and it is a CLOBBER fact rather than a spelling: the store after the + * call dereferences the field AGAIN (`*(char *)(*(int *)((char *)a0 + 32) + 44) = 1;`), so the + * `lw v1,32(s0)` after the `jal` is required — the call may have changed `a0->sub`. **Measured: + * hoisting the loaded value into a local and reusing it (`sub` in both the call and the store) + * gives candidate_bytes=96, eight bytes short**, because cc1 then keeps `sub` in `a0` across the + * call and the re-load disappears. The recorded note ("LENGTH-MISMATCH 108 vs 104 ... the original + * RELOADS the sub-object field after the call") names exactly this, and it is finding 170/178's + * family — a value that must be re-derived because a call can clobber the source of the value — + * reached from the STORE side rather than from an entry-block copy. + * + * The `move a1,a0` in each call's delay slot is why both arguments are the same pointer + * (`func_800F95CC(sub, sub)` and `func_800F95CC(a0, a0)`): the compiler set `a0` first and the + * second argument copies it. `return 24` on the null path is a literal from the instruction + * stream, and `sw ra,20(sp)` in the first branch's delay slot is the frame setup scheduled there. + * + * LIMITS: the offsets (32 and 44) and the `char` store at +44 are read off the instruction + * stream; the object types and the meaning of the flag and of the return value 24 are unknown, so + * the body is written as explicit pointer arithmetic. The callee's arity and parameter types are + * INFERRED. Only the compiled bytes are evidence. + */ + +extern void func_800F95CC(int a0, int a1); + +int func_800256F0(int a0) +{ + int sub; + + if (a0 == 0) + return 24; + sub = *(int *)((char *)a0 + 32); + if (sub != 0) { + func_800F95CC(sub, sub); + *(char *)(*(int *)((char *)a0 + 32) + 44) = 1; + } else { + func_800F95CC(a0, a0); + } + return 0; +} diff --git a/src/func_800A8920.c b/src/func_800A8920.c new file mode 100644 index 0000000..e39a850 --- /dev/null +++ b/src/func_800A8920.c @@ -0,0 +1,80 @@ +/* + * func_800A8920 — 100 bytes at 0x800A8920..0x800A8984 + * + * Hypothesis, not a claim about meaning: an indexed lookup with a NULL guard. For an index below + * 8 it forms a pointer into a table of 24-byte records at 0x8013BF78; otherwise the pointer is + * NULL. It then runs an argument-less helper, and if the pointer is non-NULL it returns the low + * byte of a second call's result, else 0. + * + * Original words: + * 27BDFFE8 addiu sp,sp,-24 + * 2C820008 sltiu v0,a0,8 ; UNSIGNED compare + * AFBF0014 sw ra,20(sp) + * 14400003 bnez v0,0x800A893C ; index < 8 -> BRANCH INTO THE BODY + * AFB00010 sw s0,16(sp) ; (delay) + * 0802A255 j 0x800A8954 ; otherwise skip the body + * 00008021 move s0,zero ; (delay) p = 0 + * $B: 00041040 sll v0,a0,0x1 + * 00441021 addu v0,v0,a0 ; v0 = index * 3 + * 000210C0 sll v0,v0,0x3 ; v0 = index * 24 + * 3C038014 lui v1,0x8014 + * 2463BF78 addiu v1,v1,-16520 ; v1 = 0x8013BF78 (the symbol form, `addiu`) + * 00438021 addu s0,v0,v1 ; p = (index * 24) + base INDEX FIRST + * $C: 0C02A089 jal 0x800A8224 ; the argument-less helper + * 00000000 nop + * 12000004 beqz s0,0x800A8970 ; p == 0 -> the shared exit + * 00001021 move v0,zero ; (delay) return 0 + * 0C02A228 jal 0x800A88A0 ; second helper + * 02002021 move a0,s0 ; (delay) its argument + * 304200FF andi v0,v0,0xFF ; low byte + * $E: 8FBF0014 lw ra,20(sp) + * 8FB00010 lw s0,16(sp) + * 27BD0018 addiu sp,sp,24 + * 03E00008 jr ra + * 00000000 nop + * + * THREE SPELLINGS (108 / 108 / 100), AND BOTH HALVES OF THE ROW ARE MIRRORED-VERSUS-NATURAL + * LAYOUT DECISIONS — the same family as cookbook 28/77, one per if-statement: + * + * (1) THE FIRST if/else MUST BE WRITTEN INVERTED WITH THE ARMS SWAPPED. The natural + * `if (a0 < 8) p = ; else p = 0;` gives `beqz v0,` with the table computation + * inline (+8 bytes). `if (a0 >= 8) p = 0; else p =
;` reproduces the original's + * `bnez v0,` with `move s0,zero` in the following `j`'s delay slot — i.e. the short + * path inline and the long path as the branch target, which is cookbook 132's stated + * direction ("when the original's short path is the fall-through, invert the condition"). + * (2) THE TAIL MUST BE THE POSITIVE TEST: `if (p != 0) return func(p) & 0xff; return 0;`. + * Written as `if (p == 0) return 0; return func(p) & 0xff;` cc1 out-lines the `return 0` + * block, leaves a `nop` in the `beqz` delay slot and adds a `j` (+8 bytes, 108 total). Only + * the positive spelling puts `move v0,zero` in the `beqz s0` DELAY SLOT, which is what the + * original has. + * Note both fixes are needed: (1) alone still gives 108 bytes. + * + * The pointer arithmetic is written with the STRIDE FIRST — `(char *)(a0 * 24) + (int)D_8013BF78` + * — because that is the spelling that produces the original's INDEX-FIRST `addu s0,v0,v1` + * (cookbook 22's third row). `sltiu` makes `a0` UNSIGNED (cookbook 48). The base is materialised + * with `addiu`, i.e. the symbol form rather than a literal (cookbook 4/89). + * + * LIMITS: the function name, both callees, the table base, the record stride 24, the index bound + * 8 and the low-byte mask are read from the instruction encodings; only the bytes are evidence. + * Nothing establishes the table's layout or what the two helpers do. The table base is referenced + * as an address-named symbol (`extern char D_8013BF78[]`) because no registry row names it. + */ + +extern char D_8013BF78[]; + +void func_800A8224(void); +int func_800A88A0(char *); + +int func_800A8920(unsigned int a0) +{ + char *p; + + if (a0 >= 8) + p = 0; + else + p = (char *)(a0 * 24) + (int)D_8013BF78; + func_800A8224(); + if (p != 0) + return func_800A88A0(p) & 0xff; + return 0; +} diff --git a/src/func_800FB758.c b/src/func_800FB758.c new file mode 100644 index 0000000..7b88856 --- /dev/null +++ b/src/func_800FB758.c @@ -0,0 +1,100 @@ +/* + * func_800FB758 — 112 bytes at 0x800FB758..0x800FB7C8 + * + * A guarded teardown: if the "active" flag is set, run two conditional/one + * unconditional helper calls, hand a one-byte parameter block to a fourth + * helper, clear the flag and stamp a second global with -1. + * + * The observed instructions are: + * lw v0,0x7ac(gp) if (D_801220E4 == 0) return; (before the frame) + * addiu sp,sp,-0x20 + * beq v0,zero,epilogue + * _sw ra,0x18(sp) (delay slot) + * lw v0,0x7b0(gp) \ + * nop | if (D_801220E8 != 0) 0x800FBD80(); + * beq v0,zero,skip | + * _nop | + * jal 0x800FBD80 / (delay slot nop) + * _nop + * skip: + * jal 0x80107AA0 \ + * _move a0,zero / 0x80107AA0(0) + * lw a0,0x7d0(gp) \ + * jal 0x800F8B6C / 0x800F8B6C(D_80122108) + * _nop + * li a0,14 \ + * li v0,0x80 | + * addiu a1,sp,0x10 | b = 128; 0x800F8CBC(14, &b); + * jal 0x800F8CBC | + * _sb v0,0x10(sp) / (the store is the call's delay slot) + * li v0,-1 + * sw zero,0x7ac(gp) D_801220E4 = 0 + * lui at,0x8012 \ + * sw v0,0x2140(at) / *(int *)0x80122140 = -1 + * epilogue: + * lw ra,0x18(sp) + * nop <- the release is NOT here + * jr ra + * addiu sp,sp,0x20 <- IT IS IN THE JUMP DELAY SLOT + * + * REGION TOKEN: `maspsx=epilogue`, decided from the CANDIDATE's tail + * (cookbook 147/165/180). This row is the one the negatives index recorded as + * "rare-epilogue-order-35b; residual 6B at 0x800FB7BC", and it closes on the + * FIRST spelling once the token is applied. It is SHAPE A of cookbook 144 — `lw ra` + * immediately precedes the frame release, so the transform moves the release into + * the jump slot AND inserts the load-delay `nop` after `lw ra`. That inserted `nop` + * is the 6-byte residual the index recorded: the earlier attempts are all recorded + * as compiler/maspsx switches (`--no-maspsx`, `--cc1 gcc-2.91.66-psx`, + * `--cc1 gcc-2.8.1-psx`), i.e. the token is what was missing, not a compiler. + * + * THE LITERAL ADDRESS FOR D_80122140 IS DELIBERATE AND IS NOT A GP TRAP IN + * REVERSE. `config/symbols.tsv` marks D_80122140 `gp`, but the original writes it + * with an ABSOLUTE `lui at,0x8012` / `sw v0,0x2140(at)` pair. Spelling it as the + * symbol would let the harness rewrite the access to `%gp_rel(...)($gp)` and the + * row would fail; the literal keeps the absolute encoding. This is the per-SITE + * gp-versus-absolute behaviour of cookbook 16/46 — the registry's marker is + * per-SYMBOL, the original is per-access — and the harness rewrite is inert on a + * literal by construction (cookbook 189's neighbourhood). + * + * The three other globals ARE read through `gp` (0x7ac / 0x7b0 / 0x7d0), so they + * are spelled as symbols and are registered `gp`: 0x801220E4, 0x801220E8 and + * 0x80122108. + * + * The frame is 32 bytes: the 16-byte o32 outgoing argument area, the one-byte + * local handed to 0x800F8CBC at sp+0x10, `ra` at sp+0x18 and the alignment gap at + * sp+0x14. + * + * LIMITS: the globals' names and widths, the helper signatures (only the argument + * registers actually set are evidenced) and the meaning of the flag and of the + * -1 stamp are hypotheses read off the instruction shape. Only the compiled bytes + * are evidence. + */ + +extern int D_801220E4; +extern int D_801220E8; +extern int D_80122108; + +void func_800FBD80(void); +void func_80107AA0(int a0); +void func_800F8B6C(int a0); +void func_800F8CBC(int a0, char *a1); + +void func_800FB758(void) +{ + char b; + + if (D_801220E4 == 0) + return; + + if (D_801220E8 != 0) + func_800FBD80(); + + func_80107AA0(0); + func_800F8B6C(D_80122108); + + b = 128; + func_800F8CBC(14, &b); + + D_801220E4 = 0; + *(int *)0x80122140 = -1; +} diff --git a/src/func_801092C0.c b/src/func_801092C0.c new file mode 100644 index 0000000..fc3eadd --- /dev/null +++ b/src/func_801092C0.c @@ -0,0 +1,15 @@ +typedef struct { + int f0; + char pad[28]; +} Rec_801461C8; + +extern Rec_801461C8 *D_801461C8; + +void func_801092C0(int start, int count) +{ + int i; + + for (i = 0; i < count; i++) { + D_801461C8[i + start].f0 = 0; + } +}