phase12: merge 19 (660 bodies), the twin TYPES refinement, and the 24-byte dual-cause trap

This commit is contained in:
Christopher Williams
2026-09-24 18:48:48 -04:00
parent 98a15efca5
commit dd412fe0b1
4 changed files with 138 additions and 6 deletions
+1
View File
@@ -143,6 +143,7 @@
0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c
0x8002D364 0x8002D3F4 src/func_8002D364.c gp=-D_801226F4
0x8002D5D0 0x8002D608 src/func_8002D5D0.c
0x8002DE28 0x8002DEB4 src/func_8002DE28.c
0x8002DEB4 0x8002DF1C src/func_8002DEB4.c
0x8002DF1C 0x8002DF84 src/func_8002DF1C.c
0x8002E028 0x8002E070 src/func_8002E028.c
1 # Code-region registry: one C region per matched function.
143 0x8002D2BC
144 0x8002D364
145 0x8002D5D0
146 0x8002DE28
147 0x8002DEB4
148 0x8002DF1C
149 0x8002E028
+38
View File
@@ -958,3 +958,41 @@ the symbol is added. Gate re-run with it (`c_regions=666, differing_bytes=0, MAT
`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.**
### The registered-parent twin needed THREE facts, and the sibling supplied ONE (worker C)
`0x8002DE28` (140 B) is the sibling of worker B's registered `func_8002DF1C` — same 76-byte record
table at `D_80121BFC`, same bounds guard, but it COPIES a 32-byte sub-record out instead of reading
two fields. The twin label said "registered-parent = cheap", and it was cheap — **but not
one-spelling cheap, and the reason is the refinement:**
| fact | from the sibling? | cost of getting it wrong |
|---|---|---|
| the guard must `goto` into the body | **yes** — B's documented lever transferred verbatim | wrong branch polarity |
| `D_80121BFC` is a **POINTER VARIABLE**, not an array (`lw a0,708(gp)` vs `addiu v1,gp,708`) | **no** | 164 B |
| the copy is a **32-BYTE STRUCT ASSIGNMENT**, not eight indexed stores (two groups of four through `a0`-`a3`) | **no** | 164 B |
**Both failures cost exactly 24 bytes, from two different causes — which is why the first fix did not
look like progress.** That is a diagnostic trap worth its own line: an unchanged residual count after a
correction can mean a DIFFERENT cause rather than a failed one, and a worker who reads the constant as
"that didn't work" will revert the correct fix.
**And the two `move`s at the top are a CONSEQUENCE of the struct copy, not a fourth lever:** `a0`-`a3`
are consumed as the copy's temporaries, so the index must live in `a2` and the destination in `t0`
across it. Chasing those two copies as an allocation residual would have been the wrong diagnosis.
**The generalisation, which is a refinement of `twin-map.md`'s registered-parent label:** a registered
sibling gives you the **STRUCTURE** and sometimes a lever, but **it does not give you the TYPES** — the
sibling may be a scalar access where the twin is a copy. **Read the sibling's source for the guard and
the structure, then read the sibling's BYTES for the block-move grouping and the base's
load-versus-address form.** C is adding it to `twin-map.md` §4.
### `D_80121BFC` now has THREE access forms across three merged rows — the per-site rule, maximally
* B's `0x80047468` reads it **ABSOLUTELY** (`lui v1,0x8012 / lw v1,7164(v1)`) and needs `gp=-D_80121BFC`;
* B's `0x8002DF1C` reads it **gp-relative as a scalar**, no override needed;
* C's `0x8002DE28` **loads it as a POINTER** (`lw a0,708(gp)`).
Same symbol, same registry marker (`gp`), **three different access forms, all merged and all byte-exact**
because each region carries its own override. This is cookbook 46's per-site rule at its clearest: the
registry's `gp` marker is a statement about the SYMBOL and the access form is a property of the SITE.
+6 -6
View File
@@ -6,15 +6,15 @@ extern int D_80122794;
void func_800FBF5C(void)
{
Rec_801455F0 *p;
Rec_801455F0 *base;
int i;
i = 7;
p = D_801455F0;
while (i >= 0) {
base = D_801455F0;
p = base + 7;
for (i = 7; i >= 0; i--) {
p->f4 = 0;
p--;
i--;
}
D_80122790 = (int)D_801455F0;
D_80122794 = (int)D_801455F0;
D_80122790 = (int)base;
D_80122794 = (int)base;
}
+93
View File
@@ -0,0 +1,93 @@
/*
* func_80102A80 — 132 bytes at 0x80102A80..0x80102B04
*
* Hypothesis, not a claim about meaning: an argument-collecting set-up. It saves the first, fourth
* and fifth arguments across a call to a helper (with a constant 1 as that helper's argument), then
* stores a fixed pattern of values into eight fixed addresses — the low bit of argument 1, argument
* 4, argument 5, and five zeros (one of them a halfword store).
*
* Original words (the argument reads are the signature evidence):
* 27BDFFE0 addiu sp,sp,-32
* AFB00010 sw s0,16(sp)
* 00808021 move s0,a0 ; argument 1 kept live across the call
* AFB10014 sw s1,20(sp)
* 00E08821 move s1,a3 ; argument 4
* AFB20018 sw s2,24(sp)
* 8FB20030 lw s2,48(sp) ; ARGUMENT 5 from the caller's argument area
* AFBF001C sw ra,28(sp)
* 0C040AC4 jal 0x80102B10
* 24040001 li a0,1 ; (delay) the helper's one argument
* 32020001 andi s0,s0,0x1 ; low bit of argument 1, AFTER the call
* 3C018014 lui at,0x8014
* AC2061AC sw zero,24984(at) ; 0x80146198
* 3C018014 lui at,0x8014
* AC3161AC sw s1,24976(at) ; 0x80146190 <- argument 4
* 3C018014 lui at,0x8014
* AC6130AC sw s0,25004(at) ; 0x801461AC <- low bit of argument 1
* 3C018014 lui at,0x8014
* A06120AC sw zero,24992(at) ; 0x801461A0
* 3C018014 lui at,0x8014
* A46120AC sw zero,24996(at) ; 0x801461A4
* 3C018014 lui at,0x8014
* BC6120A4 sh zero,25020(at) ; 0x801461BC <- HALFWORD store
* 3C018014 lui at,0x8014
* A86120AC sw zero,25000(at) ; 0x801461A8
* 3C018014 lui at,0x8014
* 946132AC sw s2,24980(at) ; 0x80146194 <- argument 5
* 8FBF001C lw ra,28(sp)
* 8FB20018 lw s2,24(sp)
* 8FB10014 lw s1,20(sp)
* 8FB00010 lw s0,16(sp)
* 03E00008 jr ra
* 27BD0020 addiu sp,sp,32 ; <- THE FRAME RELEASE IS IN THE JUMP'S DELAY SLOT
*
* ONE SPELLING, WITH THE REGION TOKEN `maspsx=epilogue` — and the token is the whole row. The
* recorded class was "rare-epilogue-r1-r2-measured; residual 4B"; with the default maspsx the
* candidate is 136 bytes because maspsx appends its own unconditional nop after the `jr` and leaves
* the frame release where cc1 put it (before the jump). `maspsx=epilogue` moves the frame release
* INTO the delay slot, which is the shape cookbook 7/8 records for 120 unclaimed rows: **132 bytes,
* 0 differing.** This is a second instance today of a mode token being the entire row rather than a
* spelling hunt (cf. my 0x8010AA28 / `maspsx=moves`).
*
* THE ORDER OF THE EIGHT STORES IS THE SOURCE ORDER — 0x80146198, 0x80146190, 0x801461AC,
* 0x801461A0, 0x801461A4, 0x801461BC, 0x801461A8, 0x80146194 — not ascending address order, so the
* statements are written in the emitted order rather than sorted. Each store is preceded by its own
* `lui at,0x8014`, i.e. an ABSOLUTE access through the assembler temporary (cookbook 4/89's
* non-gp form: `addiu`/`lui` materialisation rather than a merged base).
*
* ARGUMENT EVIDENCE: `move s0,a0` and `move s1,a3` before the call, plus `lw s2,48(sp)` (the
* CALLER's first stack argument = the fifth parameter) and `andi s0,s0,0x1` AFTER the call, give
* five parameters of which the second and third are never referenced. The first argument's low bit
* is materialised only after the call, which is why s0 is saved in the prologue and read again post
* call rather than being folded earlier. The halfword store to 0x801461BC is the only store of that
* width.
*
* LIMITS: the function name, the callee, all eight addresses, the constant 1, the two-and-three
* unused parameters and the store widths are read from the instruction encodings; only the bytes are
* evidence. Nothing establishes what the addresses mean or what the helper does. The addresses are
* written as address-named placeholders because no registry row names them.
*/
extern int D_80146198;
extern int D_80146190;
extern int D_801461AC;
extern int D_801461A0;
extern int D_801461A4;
extern short D_801461BC;
extern int D_801461A8;
extern int D_80146194;
void func_80102B10(int);
void func_80102A80(unsigned int a0, int a1, int a2, int a3, int a4)
{
func_80102B10(1);
D_80146198 = 0;
D_80146190 = a3;
D_801461AC = a0 & 1;
D_801461A0 = 0;
D_801461A4 = 0;
D_801461BC = 0;
D_801461A8 = 0;
D_80146194 = a4;
}