phase12: merge 23 (667 bodies) + five multi-exit rows ARE gate-permitted for cc1bin
MERGE 23: +2 bodies from worker D's un-attempted band, both re-verified from fresh --work dirs: 0x800582AC (64 B) and 0x800A82D0 (64 B). Gate c_regions=676, differing_bytes=0, MATCH. DISPATCH CORRECTION, and it came from checking a claim rather than forwarding it. D reported three multi-exit rows as unclosable because 'my multi-return probes share ONE exit under all ten cc1 builds'. Measured from the binary: ALL SEVEN unregistered multi-exit rows are >=2 jr $31, which is the gate's precondition for naming an alternative cc1 -- so the restriction PERMITS the lever on every one, with four merged rows as precedent. And C's measured row refutes the probe conclusion directly: 0x801008DC is 3 exits and the default gives the correct 88 bytes with 58 DIFFERING bytes because it merges all three returns, while cc1bin=gcc-2.8.1-psx gives 88/0/MATCH. The merge is SHAPE-dependent, and a synthetic probe speaks only for the shape it probes. I made exactly this error earlier in the phase with a probe-dependent mechanism written into tools/sf3_match. Rows routed: A gets 0x80107CCC (3 exits, A has the diagnosis), B gets 0x800FF6DC / 0x80100998 / 0x80107D7C, D gets 0x800FFBBC. Also recorded: D's self-caught harness hazard -- its run helper leaves the LAST variant on disk, so its first verification of 0x800A82D0 ran against the wrong variant and report.tsv's md5 described it. D re-verified from a fresh directory and made the re-write explicit in its procedure. This is the failure mode that would produce a FALSE MATCH CLAIM rather than a missed one, and it is invisible from outside because the report's own md5 agrees with the wrong file.
This commit is contained in:
@@ -273,6 +273,7 @@
|
||||
0x80057DFC 0x80057E04 src/func_80057DFC.c
|
||||
0x80058230 0x80058288 src/func_80058230.c
|
||||
0x80058288 0x800582AC src/func_80058288.c
|
||||
0x800582AC 0x800582EC src/func_800582AC.c
|
||||
0x8005A33C 0x8005A474 src/func_8005A33C.c
|
||||
0x8005E17C 0x8005E1E4 src/func_8005E17C.c
|
||||
0x8005E29C 0x8005E340 src/func_8005E29C.c
|
||||
@@ -474,6 +475,7 @@
|
||||
0x800A745C 0x800A74BC src/func_800A745C.c
|
||||
0x800A74BC 0x800A74D0 src/func_800A74BC.c
|
||||
0x800A8224 0x800A82D0 src/func_800A8224.c
|
||||
0x800A82D0 0x800A8310 src/func_800A82D0.c
|
||||
0x800A8920 0x800A8984 src/func_800A8920.c
|
||||
0x800A8B48 0x800A8B8C src/func_800A8B48.c
|
||||
0x800A9C24 0x800A9CC4 src/func_800A9C24.c
|
||||
|
||||
|
@@ -1097,3 +1097,55 @@ is a precise statement of a boundary that four workers have been circling all ph
|
||||
* **`p += i + start; p->p0 = 0;` rather than `p[i + start].p0 = 0;`** — the subscript form writes the
|
||||
address sum into the OFFSET's register and rotates the loop; accumulating into a pointer makes the
|
||||
sum's destination the BASE register (the original's `addu v1,v1,v0`).
|
||||
|
||||
### FIVE UNREGISTERED MULTI-EXIT ROWS ARE GATE-PERMITTED FOR `cc1bin`, AND ONE WORKER IS SKIPPING THREE OF THEM
|
||||
|
||||
**This is the largest single dispatch correction of the phase and it came from checking a claim I was
|
||||
about to accept.** Worker D reported that three rows in its band (`0x800FF6DC`, `0x80107CCC`,
|
||||
`0x80100998`) are the multi-exit class, and that its measurement stands: *"my multi-return probes share
|
||||
ONE exit under all ten cc1 builds in `tools/old-gcc`, framed and frameless, so no source spelling can
|
||||
produce them with this toolchain"* — therefore D skips them and the escalation is the coordinator's.
|
||||
|
||||
That conclusion is **over-generalised from synthetic probes, and the phase's own record refutes it.**
|
||||
I measured the exit counts directly from the binary:
|
||||
|
||||
| row | `jr $31` | gate permits `cc1bin`? | whose pool |
|
||||
|---|---|---|---|
|
||||
| `0x80107CCC` | **3** | yes | A (claimed) |
|
||||
| `0x800FF6DC` | 2 | yes | B |
|
||||
| `0x80100998` | 2 | yes | B |
|
||||
| `0x80107D7C` | 2 | yes | B |
|
||||
| `0x800FFBBC` | 2 | yes | D |
|
||||
| `0x80010418` | 2 | yes | — |
|
||||
| `0x800FD220` | 4 | yes | — |
|
||||
|
||||
**All seven are ≥2, which is the gate's precondition for naming an alternative cc1 — so the restriction
|
||||
PERMITS the lever on every one of them**, and the precedent is four merged rows
|
||||
(`0x800FF43C`, `0x800FF47C`, `0x80108578`, `0x801008DC`).
|
||||
|
||||
**And C's measured row contradicts D's probes directly.** `0x801008DC` is 3 exits and DID NOT MATCH on
|
||||
the default: 2.7.2 gives the correct 88 bytes with **58 differing bytes because it merges all three
|
||||
returns into one shared epilogue** — and `cc1bin=gcc-2.8.1-psx` gives 88 / 0 / MATCH. So "the ten cc1
|
||||
builds all merge multi-returns" is false as stated: **the merge is shape-dependent, and a synthetic
|
||||
probe can only speak for the shape it probes.** I made exactly this error earlier in the phase (I wrote
|
||||
a probe-shape-dependent mechanism into `tools/sf3_match` as a finding and had to correct it), which is
|
||||
why the claim was checked rather than forwarded.
|
||||
|
||||
**Action taken: A told to try `cc1bin` on `0x80107CCC` (where A has the full diagnosis — every spelling
|
||||
merges the two identical return-0 blocks, which is a COMPILER behaviour and therefore the right lever);
|
||||
B told about its three; D told about its one and that its probe conclusion does not generalise.**
|
||||
|
||||
### Worker D's harness hazard, self-caught, and it is the dangerous kind
|
||||
|
||||
D reported an error in its own verification with the correction for the log: **its first verification of
|
||||
`0x800A82D0` ran against the wrong variant left on disk by its `run` helper** (the helper leaves the
|
||||
LAST variant written), so `report.tsv`'s md5 described the 68-byte variant while the claim was the
|
||||
64-byte one. D caught it, re-verified from a fresh `--work` dir at 64/0, and made explicit in its
|
||||
procedure that **the final verification must always re-write the matched source first.**
|
||||
|
||||
**Recorded because this is the failure mode that would produce a FALSE MATCH CLAIM rather than a missed
|
||||
one**, and it is invisible from the outside: a report's own md5 would agree with the wrong file. D found
|
||||
it in its own work and reported it unprompted, which is the only reason it is in the record at all.
|
||||
**Every merge I perform re-verifies from a fresh `--work` directory against the file named in the
|
||||
claim**, which is what makes the roster's numbers safe against this class of slip — the gate is the
|
||||
backstop, but re-derivation from a clean directory is the primary defence.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* func_800582AC — 64 bytes at 0x800582AC..0x800582EC
|
||||
*
|
||||
* Clears the first field of 42 records in the 44-byte-stride table at 0x8013186C
|
||||
* and sets a parallel table of 42 words at 0x80131F84 to -1.
|
||||
*
|
||||
* Matched on the FOURTH spelling, and the lever is the ORDER OF THE
|
||||
* INITIALISATIONS, which is the order of the DECLARATIONS:
|
||||
*
|
||||
* move a1,zero ; i = 0
|
||||
* li a2,-1 ; n = -1 <-- BETWEEN the two
|
||||
* lui/addiu a0,0x80131F84 ; q = D_80131F84
|
||||
* move v1,zero ; the loop's induction variable
|
||||
*
|
||||
* Written the obvious way -- `q = D_80131F84;` first, then a `for (i = 0; ...)`,
|
||||
* with the -1 as a LITERAL in the body -- cc1 emits the counter and the pointer
|
||||
* first and then materialises -1 LAST (a constant used only inside the loop is
|
||||
* hoisted to the preheader, after the pointer init): 18 differing bytes, then 11
|
||||
* once the pointer/`i` order is fixed. Declaring
|
||||
*
|
||||
* int i = 0; int n = -1; int *q = D_80131F84;
|
||||
*
|
||||
* and storing the VARIABLE `n` puts the `li a2,-1` exactly between the other two,
|
||||
* because C executes declaration initialisers in declaration order. **A named
|
||||
* variable for a value that never changes is the lever whenever a hoisted constant
|
||||
* lands in the wrong place.**
|
||||
*
|
||||
* THE TWO TABLES ARE ACCESSED DIFFERENTLY, and the bytes show it: the 44-byte table
|
||||
* is INDEXED (`D_8013186C[i].f0 = 0`), so cc1's strength reduction creates the
|
||||
* `v1` induction variable (`addiu v1,v1,44` in the loop's branch delay slot) and
|
||||
* re-materialises the base per iteration (`lui at,0x8013 / addu at,at,v1 /
|
||||
* sw zero,6252(at)`); the word table is WALKED (`*q++ = n`, `addiu a0,a0,4`). A
|
||||
* single base register for both tables, or indexing both, does not reproduce this.
|
||||
*
|
||||
* LIMITS: the tables are named for their addresses and typed from the strides
|
||||
* (44 = the record size the `addiu v1,v1,44` proves; 4 = a word). Nothing here
|
||||
* establishes what the records are, what field 0 means, why one table is cleared
|
||||
* and the other set to -1, or that the two tables are related at all -- only that
|
||||
* both are touched 42 times, which is the `slti v0,a1,42`. The `int` element type
|
||||
* of the second table is the 4-byte store; it could be a pointer array with the
|
||||
* same instruction. The `i = 0` declaration order matters only for the register
|
||||
* pair (a1/a0); with the pointer declared first cc1 swaps them.
|
||||
*/
|
||||
typedef struct {
|
||||
int f0;
|
||||
char pad[40];
|
||||
} Rec_8013186C;
|
||||
|
||||
extern Rec_8013186C D_8013186C[42];
|
||||
extern int D_80131F84[42];
|
||||
|
||||
void func_800582AC(void)
|
||||
{
|
||||
int i = 0;
|
||||
int n = -1;
|
||||
int *q = D_80131F84;
|
||||
|
||||
for (; i < 42; i++) {
|
||||
D_8013186C[i].f0 = 0;
|
||||
*q++ = n;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* func_800A82D0 — 64 bytes at 0x800A82D0..0x800A8310
|
||||
*
|
||||
* `lookup(id)`: the low byte indexes a 28-byte-stride table at 0x8013B878; if the
|
||||
* record's byte at offset 21 equals `id >> 8` the record's ADDRESS is returned, but
|
||||
* only when the index is below 64, and zero otherwise.
|
||||
*
|
||||
* Matched on the SIXTH spelling. Two levers, both about how a single value is
|
||||
* returned:
|
||||
*
|
||||
* 1. **THE EARLY RETURN IS THE NEGATED ONE.** Written the natural way --
|
||||
* if ((id >> 8) == p->b21)
|
||||
* return (idx < 64) ? (unsigned int)p : 0;
|
||||
* return 0;
|
||||
* cc1 emits the value path, then a `j` to the shared epilogue, and a separate
|
||||
* zero path: 68 bytes. Written with the test NEGATED and the zero returned
|
||||
* FIRST --
|
||||
* if ((id >> 8) != p->b21)
|
||||
* return 0;
|
||||
* return (idx < 64) ? (unsigned int)p : 0;
|
||||
* the zero is set in the BRANCH DELAY SLOT (`move v0,zero`, which runs on both
|
||||
* paths) and the value path falls straight through into the single `jr ra`:
|
||||
* 64 bytes, MATCH. Note the delay slot writes v0 -- the RETURN register -- which
|
||||
* is what makes the fall-through value computation land in v0 directly instead
|
||||
* of in a temporary that is moved at the end (that shape is 5 differing bytes:
|
||||
* the value in a2 plus a `move v0,a2`).
|
||||
* 2. **THE INDEX IS SIGNED.** `int idx` gives the original's `slti v0,a1,64`;
|
||||
* `unsigned int idx` gives `sltiu` (1 differing byte, and the wrong one).
|
||||
*
|
||||
* The value itself is a BRANCHLESS masked select -- `slti` / `negu` / `and` -- which
|
||||
* is cc1's form for `(idx < 64) ? (unsigned int)p : 0`. Splitting it into an `if`
|
||||
* branches and does not match; the ternary IS the source shape here.
|
||||
*
|
||||
* The address arithmetic is `idx * 28`, emitted as `sll 3` / `subu` / `sll 2`
|
||||
* (x8, -x, x4), and the record base is added with the OFFSET as the first operand
|
||||
* (`addu v1,v0,v1`), which is what `&table[idx]` produces.
|
||||
*
|
||||
* LIMITS: the table is named for its address; the 28-byte stride is proved by the
|
||||
* `sll/subu/sll` sequence and the offset 21 by the `lbu`, but the field's meaning is
|
||||
* NOT recovered, and the record could be a longer object of which only these two
|
||||
* fields are touched. The `idx < 64` bound is the only evidence for the table's
|
||||
* length; nothing proves 64 is a size rather than a validity mask. `id`'s two
|
||||
* halves are treated as (low byte, next byte) because that is what the `andi`/`srl`
|
||||
* pair does -- whether the game calls them an index and a tag is unknown. The
|
||||
* function's return type is written `unsigned int` because the returned value is an
|
||||
* ADDRESS masked by a boolean; a pointer return type produces the same bytes.
|
||||
*/
|
||||
typedef struct {
|
||||
char pad[21];
|
||||
unsigned char b21;
|
||||
char pad2[6];
|
||||
} Rec_8013B878;
|
||||
|
||||
extern Rec_8013B878 D_8013B878[64];
|
||||
|
||||
unsigned int func_800A82D0(unsigned int id)
|
||||
{
|
||||
int idx;
|
||||
Rec_8013B878 *p;
|
||||
|
||||
idx = id & 0xff;
|
||||
p = &D_8013B878[idx];
|
||||
if ((id >> 8) != p->b21)
|
||||
return 0;
|
||||
return (idx < 64) ? (unsigned int)p : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user