phase12: merge 16 (657 bodies), three-worker per-site gp evidence, C's decline endorsed, A's cross-jump test

This commit is contained in:
Christopher Williams
2026-09-24 18:45:28 -04:00
parent 401c92558e
commit 3cd0f91515
6 changed files with 246 additions and 6 deletions
+3
View File
@@ -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
1 # Code-region registry: one C region per matched function.
106 0x800262F8
107 0x800263A8
108 0x80026650
109 0x800266A8
110 0x800267C0
111 0x800268C4
112 0x80026C2C
471 0x800A745C
472 0x800A74BC
473 0x800A8224
474 0x800A8920
475 0x800A8B48
476 0x800A9C24
477 0x800A9D58
590 0x800FB5D4
591 0x800FB5E4
592 0x800FB6C4
593 0x800FB758
594 0x800FBDF8
595 0x800FBE04
596 0x800FBE84
+1
View File
@@ -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
1 # Symbol registry: absolute addresses for cross-references used by C regions.
195 D_80122160
196 D_80122164
197 D_80122168
198 D_8012216C
199 D_80122170
200 D_80122174
201 D_80122178
+65
View File
@@ -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 <shared epilogue>` (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.**
+25
View File
@@ -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;
}
+60 -6
View File
@@ -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);
}
}
+92
View File
@@ -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();
}