From dd412fe0b11318ab79f130e93557fef1fd0a6718 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 18:48:48 -0400 Subject: [PATCH] phase12: merge 19 (660 bodies), the twin TYPES refinement, and the 24-byte dual-cause trap --- config/regions.tsv | 1 + phase-ends/logs/Phase12.md | 38 ++++++++++++++++ src/func_800FBF5C.c | 12 ++--- src/func_80102A80.c | 93 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 src/func_80102A80.c diff --git a/config/regions.tsv b/config/regions.tsv index 68fbc5d..0ae1427 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -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 diff --git a/phase-ends/logs/Phase12.md b/phase-ends/logs/Phase12.md index 091305b..527134e 100644 --- a/phase-ends/logs/Phase12.md +++ b/phase-ends/logs/Phase12.md @@ -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. diff --git a/src/func_800FBF5C.c b/src/func_800FBF5C.c index a8c9855..e9be921 100644 --- a/src/func_800FBF5C.c +++ b/src/func_800FBF5C.c @@ -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; } \ No newline at end of file diff --git a/src/func_80102A80.c b/src/func_80102A80.c new file mode 100644 index 0000000..ed71da6 --- /dev/null +++ b/src/func_80102A80.c @@ -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; +}