phase12: recovery merge 9 — 635 bodies / 644 regions (+33, past the cycle-1 checkpoint)
All four workers stopped when the account ran out of credit. This commit recovers every row they
had produced and verified but not yet reported, and it is the first thing done on resumption.
RECOVERY AUDIT, because work that was produced but never claimed is INVISIBLE to the merge flow
(charter rule 13):
untracked src/ files produced this phase 11
distinct addresses claimed in ANY staging file 29
src/ files claimed by nothing (orphans) 0
claims whose source file is missing 0
One false alarm worth recording: my first pass globbed `.run/p12/w-*/claims.tsv` and reported
`src/func_801008DC.c` as an orphan. It is claimed -- in `claims-altcc1.tsv`, which is a DELIBERATE
separate pool because those rows need the `cc1bin` override. The audit tool was wrong, not the
worker. A `src/`-vs-claims audit must glob EVERY claims file a charter defines, not just the default
one, or it manufactures orphans.
I re-verified ALL 29 claimed rows from fresh --work directories with `--symbols` (and
`--cc1 gcc-2.8.1-psx/cc1` for the four `cc1bin` rows) BEFORE merging: **29 of 29
`differing_bytes=0 result=MATCH`, exit 0.** Then one `apply --skip-registered` over the combined
input: 19 skipped as already merged, 10 added, 0 rejected.
sf3_match gate c_regions=644 differing_bytes=0 result=MATCH
sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9 (unchanged)
make check exit 0 extents-verify regions=644 disagreements=0 AGREE
make worklist listed=984, excluded_already_registered=642
registry audit 644 rows, 635 distinct bodies, 0 missing, 4 carry cc1bin
The 10 recovered rows, and they are the phase's best evidence that the dispatch re-cut worked:
0x8006D3B0 (192 B) and 0x8006AA88 (244 B) -- worker A, adjacency
0x8006D250 (172 B) and 0x8006D2FC (180 B) -- worker B, family/adjacency, sibling with a 5th arg
0x8002515C (60 B) and 0x800254B0 (60 B) -- worker D: BOTH twins of the registered 0x80025070,
a THREE-member family, and twins of each other
0x80101C5C (32 B) and 0x80102FE4 (48 B) -- worker D, GTE class
0x80091490 (84 B) -- worker C, DEPENDENT, merged on my ruling (below)
0x801008DC (88 B) -- worker C, `cc1bin=gcc-2.8.1-psx`, 3 `jr $31`
**RULING ON THE DEPENDENT CLAIM `0x80091490`.** Worker C flagged it rather than quietly merging it,
correctly applying charter rule 2: its callee `0x80018284` is NOT reconstructed, and the 3-argument
prototype is INFERRED from the call site (the tell is `move a1,zero` -- an integer 0, not a null
pointer in v0). Ruling: MERGE, with the dependency recorded here and in the ledger. Reasoning:
(i) rule 2 forbids verifying against a SUPERSET, and no superset was used; (ii) the whole-binary
gate proves the bytes exact, so the call site's register setup is right; (iii) the residual risk is
that a future `0x80018284` reconstruction disagrees about arity, and **that risk is caught by the
gate the moment it happens** -- the row would stop matching and the cause would be visible. The
alternative, holding a verified body, costs a body for a risk the gate already covers.
**The worker who takes `0x80018284` must treat `0x80091490`'s prototype as a hypothesis to check.**
Also recorded: `excluded_already_registered` still lags the registry by exactly 2 (642 vs 644). The
lag did NOT grow when this merge added four negatives-derived rows, so it is not "negatives rows are
counted elsewhere" -- it is a fixed pair of registry rows the counter never counts. Still open,
still a diagnostic rather than a gate, still to be reconciled at the close with the full census.
Phase position: 602 -> 635 bodies, +33. The cycle-1 checkpoint was +30.
This commit is contained in:
+693
-697
File diff suppressed because it is too large
Load Diff
@@ -87,6 +87,8 @@
|
||||
0x8002311C 0x800231BC src/func_8002311C.c
|
||||
0x80024C14 0x80024C34 src/func_80024C14.c
|
||||
0x80025070 0x800250AC src/func_80025070.c
|
||||
0x8002515C 0x80025198 src/func_8002515C.c
|
||||
0x800254B0 0x800254EC src/func_800254B0.c
|
||||
0x800259A0 0x800259DC src/func_800259A0.c
|
||||
0x80025A2C 0x80025ADC src/func_80025A2C.c
|
||||
0x80025ADC 0x80025B64 src/func_80025ADC.c
|
||||
@@ -291,6 +293,7 @@
|
||||
0x800697C4 0x800697FC src/func_800697C4.c
|
||||
0x8006A98C 0x8006AA10 src/func_8006A98C.c
|
||||
0x8006AA10 0x8006AA88 src/func_8006AA10.c
|
||||
0x8006AA88 0x8006AB7C src/func_8006AA88.c
|
||||
0x8006AD5C 0x8006ADB0 src/func_8006AD5C.c
|
||||
0x8006ADB0 0x8006AE04 src/func_8006ADB0.c
|
||||
0x8006AE04 0x8006AE54 src/func_8006AE04.c
|
||||
@@ -314,6 +317,9 @@
|
||||
0x8006BC34 0x8006BC74 src/func_8006BC34.c
|
||||
0x8006BC74 0x8006BF30 src/func_8006BC74.c
|
||||
0x8006D1C4 0x8006D250 src/func_8006D1C4.c
|
||||
0x8006D250 0x8006D2FC src/func_8006D250.c
|
||||
0x8006D2FC 0x8006D3B0 src/func_8006D2FC.c
|
||||
0x8006D3B0 0x8006D470 src/func_8006D3B0.c
|
||||
0x8006EC94 0x8006ECD4 src/func_8006EC94.c
|
||||
0x8006F28C 0x8006F2F4 src/func_8006F28C.c
|
||||
0x8006F6BC 0x8006F6F4 src/func_8006F6BC.c
|
||||
@@ -398,6 +404,7 @@
|
||||
0x800912FC 0x8009132C src/func_800912FC.c
|
||||
0x8009132C 0x80091370 src/func_8009132C.c
|
||||
0x80091370 0x8009141C src/func_80091370.c
|
||||
0x80091490 0x800914E4 src/func_80091490.c
|
||||
0x800914E4 0x80091674 src/func_800914E4.c
|
||||
0x80091674 0x800916DC src/func_80091674.c
|
||||
0x80092068 0x80092088 src/func_80092068.c
|
||||
@@ -580,17 +587,20 @@
|
||||
0x80100038 0x801000A0 src/func_80100038.c maspsx=epilogue
|
||||
0x80100318 0x80100334 src/func_80100318.c
|
||||
0x80100740 0x801007E0 src/func_80100740.c
|
||||
0x801008DC 0x80100934 src/func_801008DC.c cc1bin=gcc-2.8.1-psx
|
||||
0x80100964 0x8010097C src/func_80100964.c
|
||||
0x8010097C 0x80100998 src/func_8010097C.c
|
||||
0x80101244 0x8010128C src/func_80101244.c
|
||||
0x80101838 0x80101878 src/func_80101838.c
|
||||
0x80101C2C 0x80101C54 src/func_80101C2C.c
|
||||
0x80101C5C 0x80101C7C src/func_80101C5C.c
|
||||
0x80101CAC 0x80101CDC src/func_80101CAC.c
|
||||
0x801027CC 0x801027F8 src/func_801027CC.c
|
||||
0x80102B10 0x80102B2C src/func_80102B10.c maspsx=off
|
||||
0x80102B30 0x80102B5C src/func_80102B30.c maspsx=off
|
||||
0x80102FA4 0x80102FD4 src/func_80102FA4.c
|
||||
0x80102FD4 0x80102FE0 src/func_80102FD4.c
|
||||
0x80102FE4 0x80103014 src/func_80102FE4.c
|
||||
0x801032D4 0x80103320 src/func_801032D4.c
|
||||
0x80103A94 0x80103AA0 src/func_80103A94.c
|
||||
0x80103B54 0x80103B60 src/func_80103B54.c
|
||||
|
||||
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* func_8002515C — 60 bytes at 0x8002515C..80025198
|
||||
*
|
||||
* GOAL B ROW (worker D, Phase 12): recorded in
|
||||
* with class `-`. A raw-word scan (cookbook 166/169) against every registered
|
||||
* region of the same size found it is the registered `0x80025070` with EXACTLY
|
||||
* TWO WORDS different, and both are JUMP TARGETS:
|
||||
*
|
||||
* word 6 j -> this row's else-arm call (0x80025088 in the parent)
|
||||
* word 9 jal -> func_80025198 (func_800250AC in the parent)
|
||||
*
|
||||
* Every other word is identical, so the whole body is shared: read
|
||||
* `*(int **)((char *)a0 + 0x20)`, and if it is non-null pass ITS `+0x20` word to
|
||||
* the callee, otherwise pass 0. See `src/func_80025070.c` for the sibling's
|
||||
* documentation.
|
||||
*
|
||||
* The other member of this family, `0x800254B0`, is the same body against
|
||||
* `func_800254EC`; the registered parent plus these two make a three-member
|
||||
* family, and the pool rows are one-attempt rows because the parent supplies the
|
||||
* body (cookbook 125: the finder varies, the price does not).
|
||||
*
|
||||
* LIMITS: inherited from the parent. The struct field at +0x20 and the callee's
|
||||
* three arguments are read off the emitted code; the callee is named for its
|
||||
* address. whether either of the two arguments is really a pointer is not
|
||||
* established — `(int)a0` is written because the parent does, and only the
|
||||
* register placement is evidence.
|
||||
*/
|
||||
void func_80025198(int, int, int);
|
||||
|
||||
int func_8002515C(int *a0, int a1)
|
||||
{
|
||||
int *obj2 = *(int **)((char *)a0 + 0x20);
|
||||
|
||||
if (obj2 == 0)
|
||||
func_80025198((int)a0, 0, a1);
|
||||
else
|
||||
func_80025198((int)a0, *(int *)((char *)obj2 + 0x20), a1);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* func_800254B0 — 60 bytes at 0x800254B0..800254EC
|
||||
*
|
||||
* GOAL B ROW (worker D, Phase 12): recorded in
|
||||
* with class `-`. A raw-word scan (cookbook 166/169) against every registered
|
||||
* region of the same size found it is the registered `0x80025070` with EXACTLY
|
||||
* TWO WORDS different, and both are JUMP TARGETS:
|
||||
*
|
||||
* word 6 j -> this row's else-arm call (0x80025088 in the parent)
|
||||
* word 9 jal -> func_800254EC (func_800250AC in the parent)
|
||||
*
|
||||
* Every other word is identical, so the whole body is shared: read
|
||||
* `*(int **)((char *)a0 + 0x20)`, and if it is non-null pass ITS `+0x20` word to
|
||||
* the callee, otherwise pass 0. See `src/func_80025070.c` for the sibling's
|
||||
* documentation.
|
||||
*
|
||||
* The other member of this family, `0x800254B0`, is the same body against
|
||||
* `func_800254EC`; the registered parent plus these two make a three-member
|
||||
* family, and the pool rows are one-attempt rows because the parent supplies the
|
||||
* body (cookbook 125: the finder varies, the price does not).
|
||||
*
|
||||
* LIMITS: inherited from the parent. The struct field at +0x20 and the callee's
|
||||
* three arguments are read off the emitted code; the callee is named for its
|
||||
* address. whether either of the two arguments is really a pointer is not
|
||||
* established — `(int)a0` is written because the parent does, and only the
|
||||
* register placement is evidence.
|
||||
*/
|
||||
void func_800254EC(int, int, int);
|
||||
|
||||
int func_800254B0(int *a0, int a1)
|
||||
{
|
||||
int *obj2 = *(int **)((char *)a0 + 0x20);
|
||||
|
||||
if (obj2 == 0)
|
||||
func_800254EC((int)a0, 0, a1);
|
||||
else
|
||||
func_800254EC((int)a0, *(int *)((char *)obj2 + 0x20), a1);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* func_8006AA88 — 244 bytes at 0x8006AA88..0x8006AB7C
|
||||
*
|
||||
* Byte-identical reconstruction. A RECURSIVE two-phase body: it may recurse into
|
||||
* a sub-object found at `p->+0x0c -> +0x184`, then, unless a bit is already set
|
||||
* in `q`'s status word, asks func_8006AA10 which of two mutual-exclusion paths to
|
||||
* take and records the choice in that same status word.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-32 / sw s0,16(sp) / move s0,a0 / sw ra,24(sp) / sw s1,20(sp)
|
||||
* lw v0,12(s0) / nop / lw a0,388(v0) / nop
|
||||
* beqz a0,second / move s1,a1 ; (delay — s1 is established on BOTH paths)
|
||||
* first:
|
||||
* lw a1,12(a0) / nop
|
||||
* lw v0,260(a1) / nop / andi v0,v0,0x1000
|
||||
* beqz v0,after / nop
|
||||
* jal func_8006AA88 ; RECURSIVE, with a0 = t, a1 = u in place
|
||||
* nop
|
||||
* after:
|
||||
* jal func_80069A10 / move a0,s0 ; (delay) func_80069A10(p)
|
||||
* second:
|
||||
* lw v0,260(s1) / nop / andi v0,v0,0x800
|
||||
* bnez v0,epilogue / move a0,s0 ; (delay)
|
||||
* jal func_8006AA10 / move a1,s1 ; (delay) func_8006AA10(p, q)
|
||||
* andi v0,v0,0xff
|
||||
* bnez v0,third / addiu a0,s1,128 ; (delay) a0 = (char *)q + 128
|
||||
* move a0,s0 / move a1,zero / jal func_8006A654 / li a2,1
|
||||
* move a0,s0 / move a1,zero / jal func_80069C90 / li a2,1
|
||||
* lw v0,260(s1) / j join / ori v0,v0,0x800 ; (delay) q->+0x104 |= 0x800
|
||||
* third:
|
||||
* jal func_80068874 / move a1,zero ; (delay)
|
||||
* move a1,zero
|
||||
* lw v0,8(s0) / lw a2,12(s0) / lw a0,12(v0)
|
||||
* jal func_800250AC / addiu a2,a2,128 ; (delay)
|
||||
* lw v0,260(s1) / li v1,-2049 / and v0,v0,v1
|
||||
* join:
|
||||
* sw v0,260(s1) ; q->+0x104 = v0 (shared by both arms)
|
||||
* epilogue:
|
||||
* lw ra,24(sp) / lw s1,20(sp) / lw s0,16(sp) / addiu sp,sp,32 / jr ra / nop
|
||||
*
|
||||
* FOUR things the bytes fix:
|
||||
*
|
||||
* 1. THE func_8006AA10 TEST IS MIRRORED. The original's fall-through arm is the
|
||||
* one that calls func_8006A654 and func_80069C90 and ORs 0x800 into the status
|
||||
* word; the func_80068874 arm is the BRANCH TARGET (`bnez v0,third`). Written
|
||||
* in the natural polarity — `if (func_8006AA10(p, q) & 0xff) { 68874 arm }
|
||||
* else { 654 arm }` — cc1 keeps the `then` arm inline, the body comes out 4
|
||||
* bytes LONG with the branch inverted (`beqz`), and the whole tail shifts.
|
||||
* Writing the test NEGATED with the arms swapped lands it exactly
|
||||
* (cookbook 28/100/132).
|
||||
* 2. THE RECURSIVE CALL PASSES ITS OWN LIVE VALUES. `jal func_8006AA88` sets no
|
||||
* argument register: `a0` already holds `t` and `a1` holds `u`, so the source
|
||||
* is `func_8006AA88(t, u)` (cookbook 111/157/162). The prototype must be in
|
||||
* scope — the function is its own callee.
|
||||
* 3. THE SHARED STORE IS WHERE THE TWO ARMS JOIN. Both arms compute a status
|
||||
* word and fall into ONE `sw v0,260(s1)`; the OR arm reaches it through
|
||||
* `j join` with the `ori` in the delay slot, which is what makes the join a
|
||||
* real source-level joint rather than a duplicated store.
|
||||
* 4. `s1` IS ESTABLISHED IN THE FIRST BRANCH'S DELAY SLOT, so the second argument
|
||||
* survives the recursive call and the two calls in the first arm — which is
|
||||
* why the frame is 32 with `s0`, `s1` and `ra` and nothing else.
|
||||
*
|
||||
* LIMITS: every displacement, bit mask and the frame are read from the bytes. The
|
||||
* record layout is modelled with `char *` chains because that is how the matched
|
||||
* members of this translation unit express the same records; the field names and
|
||||
* types are hypotheses. `func_8006AA10`'s declaration (`int (void *, void *)`) is
|
||||
* taken from the existing corpus spelling, and the `& 0xff` on its result is
|
||||
* written explicitly because the mask is in the bytes — whether the callee
|
||||
* actually returns a byte-sized type is not established. The meanings of the
|
||||
* 0x1000 and 0x800 bits are unknown; only their positions are evidence.
|
||||
*/
|
||||
|
||||
extern int func_8006AA10(void *, void *);
|
||||
|
||||
void func_80069A10(char *);
|
||||
void func_8006A654(char *, int, int);
|
||||
void func_80069C90(char *, int, int);
|
||||
void func_80068874(char *, int);
|
||||
void func_800250AC(int, int, char *);
|
||||
void func_8006AA88(char *, char *);
|
||||
|
||||
void func_8006AA88(char *p, char *q)
|
||||
{
|
||||
char *t = *(char **)(*(char **)(p + 12) + 388);
|
||||
|
||||
if (t != 0) {
|
||||
char *u = *(char **)(t + 12);
|
||||
|
||||
if (*(int *)(u + 260) & 0x1000)
|
||||
func_8006AA88(t, u);
|
||||
func_80069A10(p);
|
||||
}
|
||||
if (!(*(int *)(q + 260) & 0x800)) {
|
||||
if (!(func_8006AA10(p, q) & 0xff)) {
|
||||
func_8006A654(p, 0, 1);
|
||||
func_80069C90(p, 0, 1);
|
||||
*(int *)(q + 260) |= 0x800;
|
||||
} else {
|
||||
func_80068874(q + 128, 0);
|
||||
func_800250AC(*(int *)(*(int *)(p + 8) + 12), 0, (char *)(*(int *)(p + 12) + 128));
|
||||
*(int *)(q + 260) &= -2049;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* func_8006B470 — 84 bytes at 0x8006B470..0x8006B4C4
|
||||
*
|
||||
* GOAL B ROW (worker D, Phase 12): recorded in `config/near_match_negatives.tsv`
|
||||
* with class `-`, i.e. no mechanism at all. It is the third member of worker B's
|
||||
* cluster (B matched `0x8006B328`; I matched `0x8006B398`), and reading the two
|
||||
* neighbours' sources gave this body on the FIRST spelling.
|
||||
*
|
||||
* Byte-identical reconstruction: walk the gp-relative chain head D_80121E88 and
|
||||
* call func_8006AB7C(p, n) for every object whose +0x160 field is NONZERO.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw a0,1360(gp) ; a0 = D_80121E88 (gp + 0x550)
|
||||
* addiu sp,sp,-24
|
||||
* sw ra,20(sp)
|
||||
* beqz a0,epilogue
|
||||
* sw s0,16(sp) ; (delay slot)
|
||||
* loop:
|
||||
* lw a1,12(a0) ; n = *(char **)(p + 12)
|
||||
* nop
|
||||
* lw v0,352(a1) ; flag = *(int *)(n + 352)
|
||||
* lw s0,396(a1) ; next = *(char **)(n + 396)
|
||||
* beqz v0,advance ; if (flag == 0) skip the call
|
||||
* nop
|
||||
* jal func_8006AB7C ; called with a0 = p, a1 = n ALREADY IN PLACE
|
||||
* nop
|
||||
* advance:
|
||||
* move a0,s0 ; p = next
|
||||
* bnez a0,loop
|
||||
* nop
|
||||
* epilogue:
|
||||
* lw ra,20(sp) / lw s0,16(sp) / addiu sp,sp,24 / jr ra / nop
|
||||
*
|
||||
* THREE things the bytes fix:
|
||||
*
|
||||
* 1. THE CALL PASSES ITS OWN LIVE POINTERS. The `jal` sets NO argument register:
|
||||
* `a0` already holds the walk pointer and `a1` the inner object, so the source
|
||||
* is `func_8006AB7C(p, n)`. Declaring the callee prototyped with two arguments
|
||||
* is what pins them there (cookbook 111/157/162) — with an unprototyped callee
|
||||
* the walk drifts to v0/v1 and the row stops matching.
|
||||
* 2. THE CONDITION IS `flag != 0` AND THE CALL FALLS THROUGH. `beqz v0,advance`
|
||||
* skips the call, so the call is on the FALL-THROUGH path — the natural
|
||||
* spelling here, unlike its two siblings which need their tests inverted.
|
||||
* 3. THE FRAME IS 24 WITH `ra` AND `s0` ONLY, AND `s0` EXISTS BECAUSE `next` IS
|
||||
* LOADED BEFORE THE TEST. The successor is read whether or not the call
|
||||
* happens, so it must survive the call in a callee-saved register. Loading it
|
||||
* after the test would remove `s0` and the frame's 16(sp) slot.
|
||||
*
|
||||
* The `beqz a0,epilogue` entry guard plus the `bnez a0,loop` back edge is a plain
|
||||
* `while (p != 0)` rotated by cc1 — the same shape as worker B's `0x8006B328`.
|
||||
*
|
||||
* LIMITS: every displacement, the gp offset and the frame arithmetic are read
|
||||
* from the bytes. The chain is modelled as `char *` with byte/word fields because
|
||||
* that is how the matched siblings express the same chain; the field types and
|
||||
* names are hypotheses. `func_8006AB7C` is named for its address and its two
|
||||
* arguments are evidenced by the argument registers being left in place. Nothing
|
||||
* here establishes what the walk means.
|
||||
*/
|
||||
|
||||
extern char *D_80121E88;
|
||||
|
||||
void func_8006AB7C(char *, char *);
|
||||
|
||||
void func_8006B470(void)
|
||||
{
|
||||
char *p = D_80121E88;
|
||||
|
||||
while (p != 0) {
|
||||
char *n = *(char **)(p + 12);
|
||||
int flag = *(int *)(n + 352);
|
||||
char *next = *(char **)(n + 396);
|
||||
|
||||
if (flag != 0)
|
||||
func_8006AB7C(p, n);
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* func_8006D250 — 172 bytes at 0x8006D250..0x8006D2FC
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: a request-builder. It fills a 64-byte record on its own
|
||||
* stack from two optional 16-byte vectors, stamps a flags word, then tail-calls a function
|
||||
* pointer with (the second parameter, &record). The two vector copies batch their loads (four
|
||||
* `lw` then four `sw` each), which is the struct-assignment shape.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFA8 addiu sp,sp,-88
|
||||
* 00804821 move t1,a0 ; the callee is kept in t1 (a0 gets clobbered below)
|
||||
* 00A04021 move t0,a1 ; the second parameter is kept in t0 (a1 is clobbered)
|
||||
* 24020002 li v0,2
|
||||
* AFBF0050 sw ra,80(sp)
|
||||
* 10C00009 beqz a2,0x8006D28C ; no first vector -> skip its copy
|
||||
* AFA20014 sw v0,20(sp) ; (delay) rec[1] = 2
|
||||
* 8CC20000 lw v0,0(a2) ; (L) 4 loads ...
|
||||
* 8CC30004 lw v1,4(a2)
|
||||
* 8CC40008 lw a0,8(a2)
|
||||
* 8CC5000C lw a1,12(a2)
|
||||
* AFA20018 sw v0,24(sp) ; ... then 4 stores -> rec[2..5] = a2[0..3]
|
||||
* AFA3001C sw v1,28(sp)
|
||||
* AFA40020 sw a0,32(sp)
|
||||
* AFA50024 sw a1,36(sp)
|
||||
* 10E0000A beqz a3,0x8006D2B8 ; no second vector -> skip its copy
|
||||
* 2402FFFE li v0,-2 ; (delay) the mask, FIRST materialisation
|
||||
* 8CE20000 lw v0,0(a3) ; (L) 4 loads ...
|
||||
* 8CE30004 lw v1,4(a3)
|
||||
* 8CE40008 lw a0,8(a3)
|
||||
* 8CE5000C lw a1,12(a3)
|
||||
* AFA20028 sw v0,40(sp) ; ... then 4 stores -> rec[6..9] = a3[0..3]
|
||||
* AFA3002C sw v1,44(sp)
|
||||
* AFA40030 sw a0,48(sp)
|
||||
* AFA50034 sw a1,52(sp)
|
||||
* 2402FFFE li v0,-2 ; the SECOND materialisation of the same mask
|
||||
* $L2:
|
||||
* 01002021 move a0,t0 ; argument 0 = the saved second parameter
|
||||
* 8FA30038 lw v1,56(sp) ; (L) v1 = rec[10]
|
||||
* 27A50010 addiu a1,sp,16 ; argument 1 = &record
|
||||
* 00621824 and v1,v1,v0 ; rec[10] &= -2
|
||||
* 0006102B sltu v0,zero,a2 ; (a2 != 0)
|
||||
* 00621825 or v1,v1,v0 ; rec[10] |= (a2 != 0)
|
||||
* 2402FFFD li v0,-3
|
||||
* 00621824 and v1,v1,v0 ; rec[10] &= -3
|
||||
* 0007102B sltu v0,zero,a3 ; (a3 != 0)
|
||||
* 00021040 sll v0,v0,0x1
|
||||
* 00621825 or v1,v1,v0 ; rec[10] |= (a3 != 0) << 1
|
||||
* 0120F809 jalr t1 ; tail call: t1(a0,a1)
|
||||
* AFA30038 sw v1,56(sp) ; (delay) rec[10] = v1
|
||||
* 8FBF0050 lw ra,80(sp) ; (END)
|
||||
* 27BD0058 addiu sp,sp,88
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* THREE BYTE-REQUIRED SPELLINGS, EACH MEASURED (3 spellings, 172/200/168/172):
|
||||
*
|
||||
* (1) THE VECTOR COPIES MUST BE STRUCT ASSIGNMENTS. Four separate element stores (`rec[2] =
|
||||
* a2->v[0]; ...`) interleave load/store and cost FOUR load-delay `nop`s each: 200 bytes,
|
||||
* +28. `*(struct V4 *)&rec[2] = *a2;` gives the original's four batched loads then four
|
||||
* stores. Cookbook 76/96/102.
|
||||
*
|
||||
* (2) THE SECOND BOOLEAN MUST BE NAMED. `rec[10] |= (a3 != 0) << 1;` written inline becomes a
|
||||
* BRANCH (`beqz a3` + `ori v0,v0,0x2` + a SECOND `sw` of rec[10]); only
|
||||
* `b = (a3 != 0); rec[10] |= b << 1;` gives the original's `sltu`/`sll`/`or`. The FIRST
|
||||
* boolean needs no help (`rec[10] |= (a2 != 0);` is already branchless) — cookbook 44/123's
|
||||
* "name the boolean" direction, per-site rather than per-function.
|
||||
*
|
||||
* (3) THE MASK MUST BE A LITERAL, AND THAT IS WHAT FIXES THE REGISTER ALLOCATION. With the mask
|
||||
* in a variable (`int f = -2; rec[10] &= f;`) GCC keeps f in `t0` ACROSS the two record
|
||||
* copies: the row comes out 168 bytes, **one instruction SHORT**, and the allocation shifts
|
||||
* (`fn`->t2, second parameter->t1, mask->t0) where the original has `fn`->t1,
|
||||
* parameter->t0, mask->v0. Written as the literal `rec[10] &= -2;` the mask is
|
||||
* REMATERIALISED once per path (`li v0,-2` in the `beqz a3` delay slot AND again inside the
|
||||
* a3 block — two `li`s for one source expression), which is both the missing instruction and
|
||||
* the reason `v0` stays free for it, leaving t0 and t1 for the two saved parameters.
|
||||
* Diagnostic: the allocation of the two SAVED parameters moves together with the mask's
|
||||
* register, so a "wrong register" residual on a saved parameter can be a constant-liveness
|
||||
* problem somewhere else in the function.
|
||||
*
|
||||
* LIMITS: the function name, the callee (a call through t1, i.e. the first parameter), every
|
||||
* record offset and the meaning of the record and its flags are read from the instruction shape;
|
||||
* only the bytes are evidence. The local is modelled as `int rec[16]` because the frame (88 =
|
||||
* 16 outgoing + 64 local + ra, rounded to 8) implies 64 bytes of local at sp+16..79 and `ra` at
|
||||
* sp+80; the indices actually touched are 1, 2-5, 6-9 and 10. `struct V4` is a 16-byte/4-int
|
||||
* vector type chosen for the copy shape, not a recovered declaration. The flags bits (0 = "first
|
||||
* vector present", 1 = "second vector present") and the constant 2 stored at rec[1] are literal
|
||||
* observations from the masks (-2 clears bit 0, -3 clears bit 1).
|
||||
*/
|
||||
|
||||
struct V4 { int v[4]; };
|
||||
|
||||
void func_8006D250(int (*fn)(), int a1, struct V4 *a2, struct V4 *a3)
|
||||
{
|
||||
int rec[16];
|
||||
int b;
|
||||
|
||||
rec[1] = 2;
|
||||
if (a2 != 0)
|
||||
*(struct V4 *)&rec[2] = *a2;
|
||||
if (a3 != 0)
|
||||
*(struct V4 *)&rec[6] = *a3;
|
||||
rec[10] &= -2;
|
||||
rec[10] |= (a2 != 0);
|
||||
rec[10] &= -3;
|
||||
b = (a3 != 0);
|
||||
rec[10] |= b << 1;
|
||||
fn(a1, rec);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* func_8006D2FC — 180 bytes at 0x8006D2FC..0x8006D3B0
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: the SIBLING of func_8006D250 (the row immediately
|
||||
* before it in the image). Same request-builder shape — two optional 16-byte vector copies into
|
||||
* a 64-byte stack record, a flags word, then a call through the function pointer — with three
|
||||
* differences: the record's type tag at index 1 is 3 instead of 2, the record has a FIFTH
|
||||
* incoming parameter which is stored at index 10, and the flags word is therefore at index 11
|
||||
* instead of 10.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFA8 addiu sp,sp,-88
|
||||
* 00805021 move t2,a0 ; the callee is kept in t2 (a0/a1 get clobbered below)
|
||||
* 00A04821 move t1,a1 ; the second parameter is kept in t1
|
||||
* 8FA80068 lw t0,104(sp) ; t0 = the FIFTH incoming parameter (caller sp+16)
|
||||
* 24020003 li v0,3
|
||||
* AFBF0050 sw ra,80(sp)
|
||||
* 10C00009 beqz a2,0x8006D33C ; no first vector -> skip its copy
|
||||
* AFA20014 sw v0,20(sp) ; (delay) rec[1] = 3
|
||||
* 8CC20000 lw v0,0(a2) ; (L) 4 loads ...
|
||||
* 8CC30004 lw v1,4(a2)
|
||||
* 8CC40008 lw a0,8(a2)
|
||||
* 8CC5000C lw a1,12(a2)
|
||||
* AFA20018 sw v0,24(sp) ; ... then 4 stores -> rec[2..5] = a2[0..3]
|
||||
* AFA3001C sw v1,28(sp)
|
||||
* AFA40020 sw a0,32(sp)
|
||||
* AFA50024 sw a1,36(sp)
|
||||
* 10E0000A beqz a3,0x8006D368 ; no second vector -> skip its copy
|
||||
* 2402FFFE li v0,-2 ; (delay) the mask, FIRST materialisation
|
||||
* 8CE20000 lw v0,0(a3) ; (L) 4 loads ...
|
||||
* 8CE30004 lw v1,4(a3)
|
||||
* 8CE40008 lw a0,8(a3)
|
||||
* 8CE5000C lw a1,12(a3)
|
||||
* AFA20028 sw v0,40(sp) ; ... then 4 stores -> rec[6..9] = a3[0..3]
|
||||
* AFA3002C sw v1,44(sp)
|
||||
* AFA40030 sw a0,48(sp)
|
||||
* AFA50034 sw a1,52(sp)
|
||||
* 2402FFFE li v0,-2 ; the SECOND materialisation of the same mask
|
||||
* $L2:
|
||||
* 01202021 move a0,t1 ; argument 0 = the saved second parameter
|
||||
* 8FA3003C lw v1,60(sp) ; (L) v1 = rec[11]
|
||||
* 27A50010 addiu a1,sp,16 ; argument 1 = &record
|
||||
* AFA80038 sw t0,56(sp) ; (delay) rec[10] = the fifth parameter
|
||||
* 00621824 and v1,v1,v0 ; rec[11] &= -2
|
||||
* 0006102B sltu v0,zero,a2 ; (a2 != 0)
|
||||
* 00621825 or v1,v1,v0 ; rec[11] |= (a2 != 0)
|
||||
* 2402FFFD li v0,-3
|
||||
* 00621824 and v1,v1,v0 ; rec[11] &= -3
|
||||
* 0007102B sltu v0,zero,a3 ; (a3 != 0)
|
||||
* 00021040 sll v0,v0,0x1
|
||||
* 00621825 or v1,v1,v0 ; rec[11] |= (a3 != 0) << 1
|
||||
* 0140F809 jalr t2 ; tail call: t2(a0,a1)
|
||||
* AFA3003C sw v1,60(sp) ; (delay) rec[11] = v1
|
||||
* 8FBF0050 lw ra,80(sp) ; (END)
|
||||
* 27BD0058 addiu sp,sp,88
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* THE FIVE PARAMETERS ARE READ OFF THE REGISTER AND STACK SET-UP, and the FIFTH is what shifts
|
||||
* the flags word. `lw t0,104(sp)` at entry is the o32 fifth argument: this frame is 88 bytes, so
|
||||
* the caller's outgoing-argument area starts at sp+88+16 = sp+104. The value is loaded ONCE at
|
||||
* the top and kept in `t0` across both record copies, then stored at index 10 in the argument
|
||||
* set-up delay slot — so the source stores it after the copies and the flags RMW works on index
|
||||
* 11. The frame arithmetic is unchanged from func_8006D250 (88 = 16 outgoing + 64 local + ra).
|
||||
*
|
||||
* THE THREE BYTE-REQUIRED SPELLINGS ARE THE SIBLING'S, VERIFIED HERE AGAIN in one spelling each:
|
||||
* the vector copies are STRUCT ASSIGNMENTS (four batched `lw` then four `sw`; element stores
|
||||
* interleave and add four load-delay `nop`s each), the second boolean is a NAMED local
|
||||
* (`b = (a3 != 0); rec[11] |= b << 1;` — written inline it becomes a branch plus a second
|
||||
* `sw`), and the mask is the LITERAL `-2` so it is rematerialised once per path (`li v0,-2`
|
||||
* twice) instead of being held in a register across the copies.
|
||||
*
|
||||
* LIMITS: the function name, the callee, every record offset, the tag value 3 and the meaning of
|
||||
* the record and its flags are read from the instruction shape; only the bytes are evidence. The
|
||||
* fifth parameter's TYPE is `int` because it is stored with a full `sw`; nothing establishes its
|
||||
* meaning. The local is modelled as `int rec[16]` because the frame implies 64 bytes of local at
|
||||
* sp+16..79 with `ra` at sp+80; the indices touched are 1, 2-5, 6-9, 10 and 11. `struct V4` is a
|
||||
* 16-byte/4-int vector type chosen for the copy shape, not a recovered declaration.
|
||||
*/
|
||||
|
||||
struct V4 { int v[4]; };
|
||||
|
||||
void func_8006D2FC(int (*fn)(), int a1, struct V4 *a2, struct V4 *a3, int a5)
|
||||
{
|
||||
int rec[16];
|
||||
int b;
|
||||
|
||||
rec[1] = 3;
|
||||
if (a2 != 0)
|
||||
*(struct V4 *)&rec[2] = *a2;
|
||||
if (a3 != 0)
|
||||
*(struct V4 *)&rec[6] = *a3;
|
||||
rec[10] = a5;
|
||||
rec[11] &= -2;
|
||||
rec[11] |= (a2 != 0);
|
||||
rec[11] &= -3;
|
||||
b = (a3 != 0);
|
||||
rec[11] |= b << 1;
|
||||
fn(a1, rec);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* func_8006D3B0 — 192 bytes at 0x8006D3B0..0x8006D470
|
||||
*
|
||||
* Builds a 48-byte descriptor on the stack and hands it to a caller-supplied
|
||||
* function pointer: `fn(a1, &desc)`. The descriptor carries a type word, up to
|
||||
* two 4-word vectors copied in from optional pointers, one scalar taken from the
|
||||
* FIFTH argument, and a packed flag word.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-0x58
|
||||
* move t2,a0 fn
|
||||
* move t1,a1 a1
|
||||
* lw t0,0x68(sp) a4 — the FIFTH argument, see below
|
||||
* li v0,4
|
||||
* sw ra,0x50(sp)
|
||||
* beq a2,zero,skip1 \
|
||||
* _sw v0,0x14(sp) / desc.f4 = 4 (delay slot)
|
||||
* lw v0,0(a2) / lw v1,4(a2) / lw a0,8(a2) / lw a1,12(a2)
|
||||
* sw v0,0x18(sp) / sw v1,0x1c(sp) / sw a0,0x20(sp) / sw a1,0x24(sp)
|
||||
* skip1:
|
||||
* beq a3,zero,skip2
|
||||
* _li v0,-0x101
|
||||
* (the same four-and-four from a3 into 0x28..0x34)
|
||||
* skip2:
|
||||
* move a0,t1 \
|
||||
* lw v1,0x3c(sp) | desc.flags read-modify-write
|
||||
* addiu a1,sp,0x10 / a1 = &desc
|
||||
* sw t0,0x38(sp) desc.f28 = a4
|
||||
* and v1,v1,~0x100 / sltu v0,zero,a2 / sll v0,v0,8 / or v1,v1,v0
|
||||
* and v1,v1,~0x200 / sltu v0,zero,a3 / sll v0,v0,9 / or v1,v1,v0
|
||||
* li v0,1
|
||||
* sw v1,0x3c(sp) desc.flags = ...
|
||||
* jalr t2 fn(a1, &desc)
|
||||
* _sb v0,0x3c(sp) (delay slot) desc.type = 1
|
||||
* lw ra,0x50(sp) / addiu sp,sp,0x58 / jr ra / nop
|
||||
*
|
||||
* THE DESCRIPTOR LAYOUT (relative to the struct at sp+0x10, 48 bytes):
|
||||
* +0x00 (unwritten) the field at sp+0x10 is never stored here
|
||||
* +0x04 f4 = 4
|
||||
* +0x08 v1[4] copied from a2 when a2 != 0
|
||||
* +0x18 v2[4] copied from a3 when a3 != 0
|
||||
* +0x28 f28 = a4
|
||||
* +0x2c the flag word AND a byte "type" field in the same slot
|
||||
*
|
||||
* FOUR THINGS THE BYTES PIN DOWN, each of which cost a spelling:
|
||||
*
|
||||
* 1. THE FLAG WORD IS A BITFIELD, NOT MASK ARITHMETIC. It is both read-modified
|
||||
* with `~0x100`/`~0x200` masks AND written with a single `sb` of 1 to the same
|
||||
* address. That pair is only producible by `unsigned int type : 8; unsigned int
|
||||
* f8 : 1; unsigned int f9 : 1;` — the 8-bit field is emitted as a byte store
|
||||
* and the 1-bit fields make cc1 read the word, clear the bit and OR the new
|
||||
* one in, preserving every other bit. Note the flag word is READ BEFORE IT IS
|
||||
* EVER WRITTEN: the source leaves it uninitialised, and the read is real (it
|
||||
* preserves the garbage in the other bits). That is a property of the
|
||||
* original, not a reconstruction error.
|
||||
* 2. THE COPIES ARE STRUCT ASSIGNMENTS, not element stores. `s.v1 = *a2;` gives
|
||||
* the original's four loads then four stores; four `s.v1[i] = a2[i];`
|
||||
* statements make maspsx insert a load-delay nop after every load and the row
|
||||
* comes out 220 bytes instead of 192 (cookbook 76/102).
|
||||
* 3. THE FRAME NEEDS A 16-BYTE UNREFERENCED ARRAY LOCAL. With the descriptor
|
||||
* alone the frame is 72 and every descriptor offset is right, but the two
|
||||
* epilogue instructions are 16 bytes off; `int pad[4];` (never referenced)
|
||||
* reproduces the original's 88-byte frame exactly. cc1 homes an unreferenced
|
||||
* ARRAY local but not an unreferenced scalar (cookbook 131, now its fourth
|
||||
* independent instance).
|
||||
* 4. THE SCALAR READ AT 0x68(sp) IS THE FIFTH ARGUMENT, NOT THE NINTH. With a
|
||||
* 0x58 frame, the first stack argument sits at sp+0x68 — that is
|
||||
* `callee_sp + frame + 16`, the o32 incoming-argument position. Declaring four
|
||||
* extra unused parameters moves the load to sp+0x78 and is the ONLY remaining
|
||||
* difference (1 differing byte). The function takes five arguments and four of
|
||||
* them (the two vectors, a1 and a4) are all used.
|
||||
*
|
||||
* LIMITS: the struct/field names, the descriptor's meaning, the callee's
|
||||
* signature and the flag bit meanings are hypotheses reconstructed from the
|
||||
* disassembly. The uninitialised flag-word read and the unused 16-byte local are
|
||||
* properties of the reconstruction that the original's bytes require; only the
|
||||
* compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
struct V4 {
|
||||
int v[4];
|
||||
};
|
||||
|
||||
struct S {
|
||||
int f0; /* +0x00 */
|
||||
int f4; /* +0x04 */
|
||||
struct V4 v1; /* +0x08 */
|
||||
struct V4 v2; /* +0x18 */
|
||||
int f28; /* +0x28 */
|
||||
unsigned int type : 8; /* +0x2c, byte-wide */
|
||||
unsigned int f8 : 1; /* +0x2c, bit 8 */
|
||||
unsigned int f9 : 1; /* +0x2c, bit 9 */
|
||||
};
|
||||
|
||||
void func_8006D3B0(void (*fn)(), int a1, struct V4 *a2, struct V4 *a3, int a4)
|
||||
{
|
||||
struct S s;
|
||||
int pad[4];
|
||||
|
||||
s.f4 = 4;
|
||||
|
||||
if (a2 != 0)
|
||||
s.v1 = *a2;
|
||||
|
||||
if (a3 != 0)
|
||||
s.v2 = *a3;
|
||||
|
||||
s.f28 = a4;
|
||||
s.f8 = a2 != 0;
|
||||
s.f9 = a3 != 0;
|
||||
s.type = 1;
|
||||
|
||||
fn(a1, &s);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* func_80091490 — 84 bytes at 0x80091490..0x800914E4
|
||||
*
|
||||
* PHASE 12 WORKER C. **A DEPENDENT CLAIM (charter rule 2): the callee `func_80018284` is NOT
|
||||
* yet reconstructed** — it is itself an unmatched row (`0x80018284`, 80 B, in worker C's
|
||||
* partition, classed "struct-copy jr-slot shape"). Nothing here verifies against the callee's
|
||||
* body: only the CALL SITE's register setup is reproduced, and the prototype below is
|
||||
* INFERRED from that call site. If the callee's eventual reconstruction disagrees about how
|
||||
* many arguments it takes or what the second one is, this declaration must be revisited.
|
||||
*
|
||||
* MATCH: candidate_bytes=84, differing_bytes=0, result=MATCH, exit 0 on the DEFAULT
|
||||
* toolchain (gcc-2.7.2-psx).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-24
|
||||
* sw s0,16(sp)
|
||||
* move s0,a1 s0 = a1 (the pointer must survive the call)
|
||||
* move a1,zero arg2 = 0
|
||||
* sw ra,20(sp)
|
||||
* jal 0x80018284
|
||||
* move a2,s0 (delay slot) arg3 = the original a1
|
||||
* bnez v0,0x800914D0 if (r != 0) goto epilogue (r stays in v0)
|
||||
* nop
|
||||
* move v0,zero the fall-through path returns 0
|
||||
* lw v1,20(s0) x = a1[5]
|
||||
* lw a0,4(s0) y = a1[1]
|
||||
* negu v1,v1 -x
|
||||
* negu a0,a0 -y
|
||||
* sw v1,4(s0) a1[1] = -x
|
||||
* sw a0,20(s0) a1[5] = -y
|
||||
* D0: lw ra,20(sp)
|
||||
* lw s0,16(sp)
|
||||
* addiu sp,sp,24
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* The two `lw`s are issued in the order 20(s0), 4(s0) and the two `sw`s in the MIRRORED order
|
||||
* 4(s0), 20(s0) — so the source must READ both fields into locals before writing either, and
|
||||
* the local for the field at +20 must be declared first. The earlier recorded spelling wrote
|
||||
* the stores the other way round and reached the correct length with 2 differing bytes: the
|
||||
* difference is purely which load lands in which register, i.e. the declaration order of the
|
||||
* two locals. `move a1,zero` (rather than `move v0,zero`) is the tell that the second argument
|
||||
* is the integer 0 and not a null pointer being passed in v0.
|
||||
*
|
||||
* LIMITS: the layout (+4 and +20 as words of the object a1 points at) and the meaning of the
|
||||
* operation (negating and exchanging two fields, apparently a direction/velocity pair) are read
|
||||
* off the instruction stream; there is no evidence for a struct declaration, so the body is
|
||||
* written as explicit pointer arithmetic. The return type is `int` because the call's result is
|
||||
* returned unchanged on one path. Only the compiled bytes are evidence. `D_80122154`-style
|
||||
* symbol names are absent here, so nothing in this file depends on the gp registry.
|
||||
*/
|
||||
|
||||
extern int func_80018284(int a0, int a1, int *a2);
|
||||
|
||||
int func_80091490(int a0, int *a1)
|
||||
{
|
||||
int r = func_80018284(a0, 0, a1);
|
||||
int x;
|
||||
int y;
|
||||
|
||||
if (r != 0)
|
||||
return r;
|
||||
x = a1[5];
|
||||
y = a1[1];
|
||||
a1[1] = -x;
|
||||
a1[5] = -y;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* func_801008DC — 88 bytes at 0x801008DC..0x80100934
|
||||
*
|
||||
* PHASE 12 WORKER C. A THREE-exit search function, and the row that produced a new,
|
||||
* REUSABLE lever for a residual class this partition keeps hitting (see LIMITS).
|
||||
*
|
||||
* MATCH: candidate_bytes=88, differing_bytes=0, result=MATCH, exit 0 under
|
||||
* --cc1 tools/old-gcc/gcc-2.8.1-psx/cc1
|
||||
* The project default (gcc-2.7.2-psx) gives 88 bytes but 58 differing bytes on the same
|
||||
* source: it merges the three `return`s into one shared epilogue. This row has THREE
|
||||
* `jr $31`, so the `cc1bin` override is allowed by the gate AND required by the bytes.
|
||||
*
|
||||
* MECHANISM, measured: cc1 2.7.2 SHARES one return epilogue (materialising the return value
|
||||
* before the branch and jumping to a single `j $31`); the original has one `j $31` per
|
||||
* `return`. Census: 0 of 634 registered regions contain >= 2 `jr $31`; 12 negatives rows do.
|
||||
* 14 flag sets on 2.7.2-psx all still give one exit; the cc1 BUILDS 2.8.0-psx / 2.8.1-psx /
|
||||
* 2.91.66-psx / 2.95.2-psx emit one per return on this shape. (The generalisation is NOT
|
||||
* established and must not be stated as one: the result is shape-dependent, and a separate
|
||||
* probe of worker D's could not reproduce it. What is solid is the census and the row-level
|
||||
* results like this one.)
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw a1,28(a0) p = *(int **)((char *)a0 + 28)
|
||||
* nop (load delay)
|
||||
* bnez a1,0x801008F4 if (p == 0) return 0;
|
||||
* lui v1,0x2044 (delay slot: the tag's high half, hoisted)
|
||||
* jr ra
|
||||
* move v0,zero
|
||||
* F4: lw v0,0(a1) if (*p == 0x2044494D)
|
||||
* ori v1,v1,0x494D return p; <- v0 = p in the delay slot
|
||||
* beq v0,v1,0x80100924
|
||||
* move v0,a1 (delay slot: the return value's copy)
|
||||
* lbu v0,7(a1) c = *(unsigned char *)((char *)p + 7)
|
||||
* lbu a0,25(a0) d = *(unsigned char *)(a0 + 25)
|
||||
* addiu v0,v0,-1 if (c - 1 < d)
|
||||
* slt v0,v0,a0
|
||||
* bnez v0,0x8010092C return 0;
|
||||
* sll v0,a0,0x2 (delay slot: d * 4)
|
||||
* addu v0,a1,v0 p + d*4
|
||||
* lw v0,16(v0) return *(int *)(p + d*4 + 16)
|
||||
* 24: jr ra return p;
|
||||
* nop
|
||||
* 2C: jr ra return 0;
|
||||
* move v0,zero
|
||||
*
|
||||
* The tag word `0x2044494D` is "MID " as little-endian ASCII bytes (4D 49 44 20), which is
|
||||
* what a Sony container id looks like; the value is evidence, the reading of it is a guess.
|
||||
*
|
||||
* LIMITS — and the LEVER, which is the interesting part of this file.
|
||||
* This row's residual after the structure was solved was ONE differing byte: the operand
|
||||
* order of the final `addu`. Original `addu v0,a1,v0` (0x00A21021); candidate
|
||||
* `addu v0,v0,a1` (0x00451021). That is a POINTER-plus-INT, and — like
|
||||
* `0x80050674`/`0x80050604`, whose only residual is the same class of operand order on an
|
||||
* `addu` — re-spelling the addition does NOT move it: five forms were tried here
|
||||
* (`(char *)p + 16 + d*4`, `(char *)p + (d*4 + 16)`, a named `off` variable,
|
||||
* `((char *)p + d*4)` indexed with `[4]`, a reversed `(char *)p + 16` then `base + 16`) and
|
||||
* all five give the same 1-byte residual.
|
||||
* **What DOES move it: the pointer-plus must be BOUND TO A NAMED `char *` LOCAL, with the
|
||||
* constant left outside as the load displacement** —
|
||||
* base = (char *)p + *(unsigned char *)(a0 + 25) * 4;
|
||||
* return (int *)*(int *)(base + 16);
|
||||
* That flips the comparison to `addu v0,a1,v0` and the row is byte-exact. So the operand
|
||||
* order of a commutative/pointer add here is controlled by a named binding, not by the
|
||||
* expression's text order, and the `+ 16` must stay a displacement rather than being folded
|
||||
* into the sum. This is the mirror of what worker C measured on `0x80050674`, where the same
|
||||
* class resisted every spelling INCLUDING the named-local variants: the class is real and
|
||||
* recurring, and this file is one worked instance of it. Note also that cookbook 42's claim
|
||||
* that the operand order and the register allocation are COUPLED is false in this direction —
|
||||
* here only the order moves.
|
||||
*
|
||||
* The rest of the limits: `a0` is typed `char *` because every access is a byte displacement
|
||||
* from it; the layout (a pointer at +28, a byte at +25 and words at +0/+16 of the sub-object,
|
||||
* a byte at +7 of the sub-object) is read off the instruction stream with no evidence for a
|
||||
* struct declaration, so it is written as explicit pointer arithmetic. The return type is
|
||||
* `int *` because two of the three exits return the sub-object pointer; the third returns a
|
||||
* word from a table and is cast. Only the compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
int *func_801008DC(char *a0)
|
||||
{
|
||||
int *p = *(int **)(a0 + 28);
|
||||
char *base;
|
||||
|
||||
if (p == 0)
|
||||
return 0;
|
||||
if (*p == 0x2044494D)
|
||||
return p;
|
||||
if (*(unsigned char *)((char *)p + 7) - 1 < *(unsigned char *)(a0 + 25))
|
||||
return 0;
|
||||
base = (char *)p + *(unsigned char *)(a0 + 25) * 4;
|
||||
return (int *)*(int *)(base + 16);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* func_80101C5C — 32 bytes at 0x80101C5C..0x80101C7C
|
||||
*
|
||||
* GOAL B ROW (worker D, Phase 12): recorded in
|
||||
* `config/near_match_negatives.tsv` with class `-`. It is a **GTE class** row —
|
||||
* the class the phase reopened with a known lever — and it matched on the FIRST
|
||||
* spelling.
|
||||
*
|
||||
* Byte-identical reconstruction: copy the translation vector into the GTE's
|
||||
* control registers $5/$6/$7 (TRX/TRY/TRZ) from `src[5..7]` (byte offsets
|
||||
* +0x14/+0x18/+0x1c).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw t0,20(a0) / lw t1,24(a0) / lw t2,28(a0) ; ALL THREE LOADS FIRST
|
||||
* ctc2 t0,$5 / ctc2 t1,$6 / ctc2 t2,$7
|
||||
* jr ra / nop
|
||||
*
|
||||
* TWO things the bytes fix:
|
||||
*
|
||||
* 1. THE LOADS ARE BATCHED, AND THAT IS A SOURCE FACT, NOT A SCHEDULE. Written
|
||||
* as three bare `gte_ldTRx(src[n])` calls the operands are materialised inside
|
||||
* each `__asm__ volatile`, cc1 emits `lw / #nop / ctc2` three times, and the
|
||||
* region comes out 44 bytes instead of 32: the asm statements stop the loads
|
||||
* from being hoisted. Binding the three values to REGISTER VARIABLES first
|
||||
* (the idiom the neighbouring matched `src/func_80101CAC.c` uses) hoists all
|
||||
* three loads above the first asm statement and reproduces the original
|
||||
* exactly. Cookbook 160: a register binding is not inline assembly and needs
|
||||
* no exemption.
|
||||
* 2. THE REGISTERS ARE $8/$9/$10 — `t0`/`t1`/`t2` — which is what the bindings
|
||||
* pin, and matches the neighbouring row's convention.
|
||||
*
|
||||
* LIMITS: the source pointer, the three displacements and the control-register
|
||||
* numbers are read off the bytes; the macro names are the re-derived ones from
|
||||
* `include/gtemac.h` (the numbering itself is cookbook 29's map, where $5/$6/$7
|
||||
* are the translation vector). Nothing here establishes what the values mean.
|
||||
*/
|
||||
#include "../include/gtemac.h"
|
||||
|
||||
void func_80101C5C(int *src)
|
||||
{
|
||||
register int t0 __asm__("$8");
|
||||
register int t1 __asm__("$9");
|
||||
register int t2 __asm__("$10");
|
||||
|
||||
t0 = src[5];
|
||||
t1 = src[6];
|
||||
t2 = src[7];
|
||||
|
||||
gte_ldTRX(t0);
|
||||
gte_ldTRY(t1);
|
||||
gte_ldTRZ(t2);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* func_80102FE4 — 48 bytes at 0x80102FE4..0x80103014
|
||||
*
|
||||
* GOAL B ROW (worker D, Phase 12): recorded in
|
||||
* `config/near_match_negatives.tsv` with class `-`. GTE class; matched on the
|
||||
* FIRST spelling with the same lever as its neighbour `0x80101C5C` (below).
|
||||
*
|
||||
* Byte-identical reconstruction: load the light-source colour matrix from
|
||||
* `src[0..4]` into the GTE's control registers $16..$20 (LR1LR2, LR3LG1,
|
||||
* LG2LG3, LB1LB2, LB3).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw t0,0(a0) / lw t1,4(a0) / lw t2,8(a0) / lw t3,12(a0) / lw t4,16(a0)
|
||||
* ctc2 t0,$16 / ctc2 t1,$17 / ctc2 t2,$18 / ctc2 t3,$19 / ctc2 t4,$20
|
||||
* jr ra / nop
|
||||
*
|
||||
* All five loads are batched before the first `ctc2`, which is what the
|
||||
* register-bound form produces: the bare macro form interleaves `lw / nop /
|
||||
* ctc2` five times and is 68 bytes against the original's 48 (cookbook 160,
|
||||
* and the idiom of the neighbouring registered `src/func_80101CAC.c`).
|
||||
*
|
||||
* LIMITS: as for `0x80101C5C` — displacements, the five register numbers and the
|
||||
* source pointer are read from the bytes; `include/gtemac.h` supplies the macro
|
||||
* names and cookbook 29 the register map. The initial source pointer is modelled
|
||||
* as `int *` because only word loads are performed through it.
|
||||
*/
|
||||
#include "../include/gtemac.h"
|
||||
|
||||
void func_80102FE4(int *src)
|
||||
{
|
||||
register int t0 __asm__("$8");
|
||||
register int t1 __asm__("$9");
|
||||
register int t2 __asm__("$10");
|
||||
register int t3 __asm__("$11");
|
||||
register int t4 __asm__("$12");
|
||||
|
||||
t0 = src[0];
|
||||
t1 = src[1];
|
||||
t2 = src[2];
|
||||
t3 = src[3];
|
||||
t4 = src[4];
|
||||
|
||||
gte_ldLR1LR2(t0);
|
||||
gte_ldLR3LG1(t1);
|
||||
gte_ldLG2LG3(t2);
|
||||
gte_ldLB1LB2(t3);
|
||||
gte_ldLB3(t4);
|
||||
}
|
||||
Reference in New Issue
Block a user