phase12: merge 15 (654 bodies), C's finding-44 direction, and the 3-of-6 reachability expectation

This commit is contained in:
Christopher Williams
2026-09-24 18:42:41 -04:00
parent 5af450c491
commit 401c92558e
6 changed files with 316 additions and 0 deletions
+1
View File
@@ -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
1 # Code-region registry: one C region per matched function.
91 0x80025070
92 0x8002515C
93 0x800254B0
94 0x800256F0
95 0x800259A0
96 0x80025A2C
97 0x80025ADC
+45
View File
@@ -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.
+75
View File
@@ -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;
}
+80
View File
@@ -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 = <table>; else p = 0;` gives `beqz v0,<else>` with the table computation
* inline (+8 bytes). `if (a0 >= 8) p = 0; else p = <table>;` reproduces the original's
* `bnez v0,<body>` 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;
}
+100
View File
@@ -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;
}
+15
View File
@@ -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;
}
}