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 =