phase10: *** MILESTONE MET — 480 distinct bodies / 489 regions (target 475) ***
+6 bodies (worker A claims 23-26, worker B2 claims 8-9). Candidate gate MATCH
before every promotion.
FULL CLEAN AUDIT GREEN at the milestone:
make clean && make all exit 0
cmp exit 0
SHA-1 both files e173426c157384ebf1b6caf8c6fea18a85a14af9
registry 489 rows, 0 overlap, 0 unsorted, 0 bad extents,
0 missing sources, 480 distinct sources
firewall 0 tracked paths under any prohibited root (583 files)
suite 237 tests OK
The phase goal (475 from the 400 baseline) is met with 5 bodies to spare.
Per the plan, phase close requires the developer's explicit confirmation of the
milestone; this commit records the state, not the close.
SIZE-BAND FINDING now confirmed a third time, within-worker: worker B2's nine
matches cost 1, 4, 6, 4, 1, 2, 2, 1, 1 attempts -- the three <=120 B frameless leaves
all cost exactly ONE attempt, while the two >200 B P1 rows it opened with cost 6 and
4 attempts and produced ZERO matches.
Two more levers recorded from the closing rows:
- RECORD IDENTITY FROM WIDTHS (0x80041610): the two arms read three shorts at
+264/+266/+268 versus three ints at +20/+24/+28 through one extra indirection,
so they are TWO record types; declaring one shared type would have been wrong.
- a1[1] = -a1[1] RELOADS FROM MEMORY and its source order matters: the negation
reloads 4(a1) because the intervening store to 8(a1) may alias it.
- A 2-D ARRAY'S ROW STRIDE IS BYTE-LOAD-BEARING (0x800AC7A0): D[a3][a4] emits
sll a3,4 + sll a4,2 + add (correct length); D[a3*4 + a4] folds the outer *4 into
a second sll and comes out 4 bytes SHORT. When a scaled index is one sll short,
the source is a 2-D array, not a flattened index -- a LENGTH-class lever.
This commit is contained in:
+1193
-1199
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
0x80012F24 0x80012F80 src/func_80012F24.c
|
||||
0x80013AF0 0x80013B80 src/func_80013AF0.c
|
||||
0x80013C88 0x80013C90 src/func_80013C88.c
|
||||
0x80013C90 0x80013D04 src/func_80013C90.c
|
||||
0x80013D04 0x80013D44 src/func_80013D04.c
|
||||
0x8001587C 0x8001590C src/func_8001587C.c
|
||||
0x8001590C 0x80015950 src/func_8001590C.c
|
||||
@@ -85,6 +86,7 @@
|
||||
0x80026C2C 0x80026C7C src/func_80026C2C.c
|
||||
0x80026F14 0x80026F3C src/func_80026F14.c
|
||||
0x800276B0 0x800276D4 src/func_800276B0.c
|
||||
0x80027C44 0x80027CA0 src/func_80027C44.c
|
||||
0x80027E1C 0x80027ECC src/func_80027E1C.c
|
||||
0x80028150 0x800281A4 src/func_80028150.c
|
||||
0x800281A4 0x800281E4 src/func_800281A4.c
|
||||
@@ -147,6 +149,7 @@
|
||||
0x8003B320 0x8003B34C src/func_8003B320.c
|
||||
0x8003CB8C 0x8003CBE4 src/func_8003CB8C.c
|
||||
0x8003D310 0x8003D35C src/func_8003D310.c
|
||||
0x80041610 0x80041688 src/func_80041610.c
|
||||
0x800419D0 0x80041A24 src/func_800419D0.c
|
||||
0x80041A24 0x80041A58 src/func_80041A24.c
|
||||
0x80041FE4 0x80042088 src/func_80041FE4.c
|
||||
@@ -222,6 +225,7 @@
|
||||
0x8006ADB0 0x8006AE04 src/func_8006ADB0.c
|
||||
0x8006B184 0x8006B1CC src/func_8006B184.c
|
||||
0x8006B1CC 0x8006B214 src/func_8006B1CC.c
|
||||
0x8006B270 0x8006B2D4 src/func_8006B270.c
|
||||
0x8006B66C 0x8006B6BC src/func_8006B66C.c
|
||||
0x8006B778 0x8006B7C0 src/func_8006B778.c
|
||||
0x8006B9E0 0x8006BA3C src/func_8006B9E0.c
|
||||
@@ -240,6 +244,7 @@
|
||||
0x800734A4 0x800734DC src/func_800734A4.c
|
||||
0x80073F78 0x8007405C src/func_80073F78.c
|
||||
0x80074C00 0x80074C74 src/func_80074C00.c
|
||||
0x8007A2AC 0x8007A324 src/func_8007A2AC.c
|
||||
0x8007A404 0x8007A428 src/func_8007A404.c
|
||||
0x8007A428 0x8007A4FC src/func_8007A428.c
|
||||
0x8007BC64 0x8007BD0C src/func_8007BC64.c
|
||||
@@ -376,6 +381,7 @@
|
||||
0x800B7230 0x800B7264 src/func_800B7230.c
|
||||
0x800B74D0 0x800B7524 src/func_800B74D0.c
|
||||
0x800BBDEC 0x800BBDF8 src/func_800BBDEC.c
|
||||
0x800BC960 0x800BC9C4 src/func_800BC960.c
|
||||
0x800BFE80 0x800BFEA0 src/func_800BFE80.c
|
||||
0x800BFEA0 0x800BFEC0 src/func_800BFEA0.c
|
||||
0x800BFEC0 0x800BFEE0 src/func_800BFEC0.c
|
||||
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* func_80013C90 — 104 bytes at 0x80013C90..0x80013CF8
|
||||
*
|
||||
* Flag-guarded notify: when a byte flag on the object is set, clears a four-byte
|
||||
* scratch record, fills three of its bytes from the flag, submits it, and clears the
|
||||
* flag.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFD8 addiu sp,sp,-40
|
||||
* AFB20020 sw s2,32(sp)
|
||||
* 00809021 addu s2,a0,zero ; s2 = a0
|
||||
* AFBF0024 sw ra,36(sp)
|
||||
* AFB1001C sw s1,28(sp)
|
||||
* AFB00018 sw s0,24(sp)
|
||||
* 9250013C lbu s0,316(s2) ; s0 = a0->byte_13c
|
||||
* 00000000 nop
|
||||
* 1200000D beq s0,zero,0x80013CE8 ; clear -> done
|
||||
* 00A08821 addu s1,a1,zero ; (delay) s1 = a1
|
||||
* 27A40010 addiu a0,sp,16 ; a0 = &buf
|
||||
* 00002821 addu a1,zero,zero ; a1 = 0
|
||||
* 0C03DBC0 jal 0x800F6F00
|
||||
* 24060004 addiu a2,zero,4 ; (delay) func_800F6F00(buf, 0, 4)
|
||||
* 27A40010 addiu a0,sp,16 ; a0 = &buf
|
||||
* 24050001 addiu a1,zero,1 ; a1 = 1
|
||||
* 02203021 addu a2,s1,zero ; a2 = a1
|
||||
* A3B00010 sb s0,16(sp) ; buf[0] = flag
|
||||
* A3B00011 sb s0,17(sp) ; buf[1] = flag
|
||||
* 0C004EE0 jal 0x80013B80
|
||||
* A3B00012 sb s0,18(sp) ; (delay) buf[2] = flag
|
||||
* A240013C sb zero,316(s2) ; a0->byte_13c = 0
|
||||
* 8FBF0024 lw ra,36(sp) ; (END)
|
||||
* 8FB20020 lw s2,32(sp)
|
||||
* 8FB1001C lw s1,28(sp)
|
||||
* 8FB00018 lw s0,24(sp)
|
||||
* 27BD0028 addiu sp,sp,40
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* The first call CLEARS the record (it is passed the buffer, zero and a length of 4),
|
||||
* and the three byte stores come AFTER it — so the source is a clear call followed by
|
||||
* three assignments, not an initialised local. The third store is scheduled into the
|
||||
* second call's delay slot, and the second call is `f(buf, 1, a1)` with the incoming
|
||||
* second argument forwarded.
|
||||
*
|
||||
* Frame arithmetic: `s0` is at 0x18, so the local between the outgoing argument area
|
||||
* and the saves is 0x10..0x18 — 8 bytes. Only three bytes are written, so the
|
||||
* declared size is not recoverable from the stores alone.
|
||||
*
|
||||
* LIMITS: the function name, both callees, the flag offset 316, the record length 4
|
||||
* and the meaning of the flag are hypotheses read from the instruction shape; only the
|
||||
* bytes are evidence. The flag is loaded with `lbu`, so it is an UNSIGNED byte.
|
||||
* func_800F6F00 is this row's unregistered family callee (13 worklist rows) and
|
||||
* func_80013B80 is unregistered too; both are referenced by address-named spellings.
|
||||
* The function sets no result, so it is `void`.
|
||||
*/
|
||||
|
||||
extern void func_800F6F00(char *buf, int a1, int a2);
|
||||
extern void func_80013B80(char *buf, int a1, int a2);
|
||||
|
||||
void func_80013C90(int a0, int a1)
|
||||
{
|
||||
char buf[8];
|
||||
unsigned char flag = *(unsigned char *)((char *)a0 + 316);
|
||||
|
||||
if (flag == 0)
|
||||
return;
|
||||
|
||||
func_800F6F00(buf, 0, 4);
|
||||
buf[0] = flag;
|
||||
buf[1] = flag;
|
||||
buf[2] = flag;
|
||||
func_80013B80(buf, 1, a1);
|
||||
*(unsigned char *)((char *)a0 + 316) = 0;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* func_80027C44 — 92 bytes at 0x80027C44..0x80027CA0
|
||||
*
|
||||
* Component-wise difference of two 3-word vectors into a stack temporary, passed on
|
||||
* with a third argument; always returns 0.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFD8 addiu sp,sp,-40
|
||||
* AFBF0020 sw ra,32(sp)
|
||||
* 8C820000 lw v0,0(a0) ; v0 = a0[0]
|
||||
* 8CA30000 lw v1,0(a1) ; v1 = a1[0]
|
||||
* 00000000 nop
|
||||
* 00431023 subu v0,v0,v1 ; v0 -= v1
|
||||
* AFA20010 sw v0,16(sp) ; d[0] = v0
|
||||
* 8C820004 lw v0,4(a0)
|
||||
* 8CA30004 lw v1,4(a1)
|
||||
* 00000000 nop
|
||||
* 00431023 subu v0,v0,v1
|
||||
* AFA20014 sw v0,20(sp) ; d[1] = v0
|
||||
* 8C820008 lw v0,8(a0)
|
||||
* 27A40010 addiu a0,sp,16 ; a0 = &d
|
||||
* 8CA30008 lw v1,8(a1)
|
||||
* 00C02821 addu a1,a2,zero ; a1 = a2
|
||||
* 00431023 subu v0,v0,v1
|
||||
* 0C008C6F jal 0x800231BC
|
||||
* AFA20018 sw v0,24(sp) ; (delay) d[2] = v0
|
||||
* 8FBF0020 lw ra,32(sp)
|
||||
* 00001021 addu v0,zero,zero ; return 0
|
||||
* 03E00008 jr ra
|
||||
* 27BD0028 addiu sp,sp,40 ; (delay) frame release
|
||||
*
|
||||
* The three pairs are the same expression with offsets 0, 4 and 8, so this is three
|
||||
* statements and not a loop. The third store is scheduled into the `jal` delay slot
|
||||
* and the third subtraction's operands are loaded before the two argument moves, so
|
||||
* the source order is three difference statements followed by the call.
|
||||
*
|
||||
* Frame arithmetic fixes the temporary at 12 bytes: `ra` is at 0x20, so the local
|
||||
* between the outgoing argument area and the save is 0x10..0x1C — three words.
|
||||
*
|
||||
* THE EPILOGUE IS THE `rare-real` SHAPE (cookbook findings 11/35): `lw ra,32(sp)` /
|
||||
* `addu v0,zero,zero` / `jr ra` / `addiu sp,sp,40`. The instruction between the `ra`
|
||||
* load and the jump is real and does not read `ra`, so GNU `as` reproduces it with the
|
||||
* default toolchain and no override.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the vector element count (3) and the return
|
||||
* constant are hypotheses read from the instruction shape; only the bytes are
|
||||
* evidence. The two vector arguments are `int *` (32-bit loads at offsets 0, 4, 8) and
|
||||
* the difference is signed `subu`. func_800231BC is not registered and is referenced
|
||||
* by its address-named spelling; it takes the temporary's ADDRESS as its first
|
||||
* argument.
|
||||
*/
|
||||
|
||||
extern void func_800231BC(int *d, int a1);
|
||||
|
||||
int func_80027C44(int *a0, int *a1, int a2)
|
||||
{
|
||||
int d[3];
|
||||
|
||||
d[0] = a0[0] - a1[0];
|
||||
d[1] = a0[1] - a1[1];
|
||||
d[2] = a0[2] - a1[2];
|
||||
|
||||
func_800231BC(d, a2);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/* func_80041610 — 0x80041610..0x80041688 (120 bytes).
|
||||
*
|
||||
* Original words (objdump of the validated payload, little-endian):
|
||||
* lw v1,12(a0) p = *(char **)(a0 + 12)
|
||||
* nop
|
||||
* beqz v1,0x80041644 if (p == 0) take the second arm
|
||||
* _nop
|
||||
* lh v0,264(v1) a1[0] = *(short *)(p + 264)
|
||||
* nop
|
||||
* sw v0,0(a1)
|
||||
* lh v0,266(v1) a1[1] = *(short *)(p + 266)
|
||||
* nop
|
||||
* sw v0,4(a1)
|
||||
* lh v0,268(v1) a1[2] = *(short *)(p + 268)
|
||||
* j 0x80041680
|
||||
* _sw v0,8(a1) (delay slot)
|
||||
* 0x80041644:
|
||||
* lw v0,8(a0) \ q = *(char **)(*(char **)(a0 + 8) + 12)
|
||||
* nop | two levels of indirection, see point 2
|
||||
* lw v1,12(v0) |
|
||||
* nop /
|
||||
* lw v0,20(v1) a1[0] = *(int *)(q + 20)
|
||||
* nop
|
||||
* sw v0,0(a1)
|
||||
* lw v0,24(v1) a1[1] = *(int *)(q + 24)
|
||||
* nop
|
||||
* sw v0,4(a1)
|
||||
* lw v0,4(a1) \
|
||||
* lw v1,28(v1) | a1[2] = *(int *)(q + 28);
|
||||
* negu v0,v0 | a1[1] = -a1[1];
|
||||
* sw v1,8(a1) |
|
||||
* sw v0,4(a1) /
|
||||
* 0x80041680: jr ra; _nop
|
||||
*
|
||||
* Fills a 3-word output array from one of two sources depending on whether the
|
||||
* object's +12 pointer is null. The two arms read DIFFERENT field widths from
|
||||
* DIFFERENT offsets, so they are two genuinely different records.
|
||||
*
|
||||
* WHAT THE BYTES PIN DOWN:
|
||||
*
|
||||
* 1. THE TWO ARMS USE DIFFERENT WIDTHS AND OFFSETS, SO THEY ARE TWO RECORD
|
||||
* TYPES. The first arm reads three `short`s at +264/+266/+268 (a contiguous
|
||||
* 6-byte triple, i.e. an x/y/z of 16-bit values) and widens each to a word
|
||||
* with `lh` + `sw`. The second arm reads three `int`s at +20/+24/+28 from a
|
||||
* DIFFERENT object reached through one extra indirection. Declaring one shared
|
||||
* record type for both would be wrong; the widths are the evidence.
|
||||
* 2. THE SECOND ARM'S OBJECT IS REACHED THROUGH TWO LOADS: `*(char **)(a0 + 8)`
|
||||
* then `+12`. So `a0` has a pointer at +8 to an intermediate object whose +12
|
||||
* pointer is the record. Getting the level of indirection wrong changes the
|
||||
* instruction count (worker C's lever 4).
|
||||
* 3. THE LAST STORE IS A NEGATED RELOAD: `a1[1] = -a1[1]` is written AFTER the
|
||||
* `a1[2]` store in the source, and cc1 reloads `4(a1)` rather than keeping the
|
||||
* value, because the intervening store to `8(a1)` may alias it (a3 is an
|
||||
* `int *`, so cc1 cannot prove the offsets disjoint). The order
|
||||
* `a1[2] = ...; a1[1] = -a1[1];` is what produces the reload.
|
||||
* 4. NO FRAME AT ALL: the row makes no calls and uses only t/v registers, so
|
||||
* there is no prologue and no epilogue beyond `jr ra`.
|
||||
*
|
||||
* LIMITS: the record layouts (only the offsets above are observed), the meaning
|
||||
* of the three components and of the negation are hypotheses read from the
|
||||
* instruction shapes. Only the compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
void func_80041610(int a0, int *a1)
|
||||
{
|
||||
char *p = *(char **)(a0 + 12);
|
||||
|
||||
if (p != 0) {
|
||||
a1[0] = *(short *)(p + 264);
|
||||
a1[1] = *(short *)(p + 266);
|
||||
a1[2] = *(short *)(p + 268);
|
||||
} else {
|
||||
char *q = *(char **)(*(char **)(a0 + 8) + 12);
|
||||
|
||||
a1[0] = *(int *)(q + 20);
|
||||
a1[1] = *(int *)(q + 24);
|
||||
a1[2] = *(int *)(q + 28);
|
||||
a1[1] = -a1[1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* func_8006B270 — 100 bytes at 0x8006B270..0x8006B2D4
|
||||
*
|
||||
* Walks a singly-linked list from a global head, calling one routine per node and
|
||||
* clearing one bit of a field of the intermediate record.
|
||||
*
|
||||
* Original words:
|
||||
* 8F840550 lw a0,1360(gp) ; a0 = D_80121E88 (hoisted above the frame)
|
||||
* 27BDFFE0 addiu sp,sp,-32
|
||||
* AFBF001C sw ra,28(sp)
|
||||
* AFB20018 sw s2,24(sp)
|
||||
* AFB10014 sw s1,20(sp)
|
||||
* 1080000C beq a0,zero,0x8006B2B8 ; empty -> done
|
||||
* AFB00010 sw s0,16(sp) ; (delay) save s0
|
||||
* 2412FBFF addiu s2,zero,-1025 ; s2 = ~0x400 (hoisted loop invariant)
|
||||
* 8C90000C lw s0,12(a0) ; (L) s0 = node->field_0c
|
||||
* 00000000 nop
|
||||
* 8E11018C lw s1,396(s0) ; s1 = s0->field_18c (next)
|
||||
* 0C01A4E6 jal 0x80069398
|
||||
* 00000000 nop ; (delay) func_80069398(node)
|
||||
* 8E020104 lw v0,260(s0) ; v0 = s0->field_104
|
||||
* 02202021 addu a0,s1,zero ; node = next
|
||||
* 00521024 and v0,v0,s2 ; v0 &= ~0x400
|
||||
* 1480FFF7 bne a0,zero,0x8006B290 ; more -> loop
|
||||
* AE020104 sw v0,260(s0) ; (delay) s0->field_104 = v0
|
||||
* 8FBF001C lw ra,28(sp) ; (END)
|
||||
* 8FB20018 lw s2,24(sp)
|
||||
* 8FB10014 lw s1,20(sp)
|
||||
* 8FB00010 lw s0,16(sp)
|
||||
* 27BD0020 addiu sp,sp,32
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* Same list shape as src/func_8006B184.c and src/func_8006ADB0.c — the same head global
|
||||
* and the same two link offsets (12 for the hop, 396 for the next) — with a bit clear
|
||||
* on a third field (260) of the intermediate record. The head load is hoisted above
|
||||
* the frame setup.
|
||||
*
|
||||
* `s2` holds `~0x400` for the whole loop and is saved purely for that: cc1 parks the
|
||||
* loop-invariant mask in a callee-saved register instead of rematerialising it after
|
||||
* the call, which is why this 25-instruction body saves three registers in a 32-byte
|
||||
* frame. The same hoisting shows up in src/func_80026A04.c and src/func_800A6B38.c.
|
||||
*
|
||||
* The entry test is a guard and the back edge branches to the BODY start with the
|
||||
* store in its DELAY SLOT, so the store runs on every iteration including the last —
|
||||
* a `do`/`while` under an explicit empty-list guard, not a `while` whose test is
|
||||
* duplicated. The `jal` delay slot is a `nop`, so the callee takes the node already in
|
||||
* `a0`.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the head global, the three field offsets
|
||||
* (12, 260, 396) and the cleared bit 0x400 are hypotheses read from the instruction
|
||||
* shape; only the bytes are evidence. D_80121E88 is gp-relative and carries a registry
|
||||
* `gp` marker; func_80069398 is not registered and is referenced by its address-named
|
||||
* spelling. The function sets no result, so it is `void`.
|
||||
*/
|
||||
|
||||
extern int D_80121E88;
|
||||
extern void func_80069398(int *node);
|
||||
|
||||
void func_8006B270(void)
|
||||
{
|
||||
int *node = (int *)D_80121E88;
|
||||
|
||||
if (node == 0)
|
||||
return;
|
||||
|
||||
do {
|
||||
int *mid = *(int **)((char *)node + 12);
|
||||
int *next = *(int **)((char *)mid + 396);
|
||||
|
||||
func_80069398(node);
|
||||
*(int *)((char *)mid + 260) &= ~0x400;
|
||||
node = next;
|
||||
} while (node != 0);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/* func_8007A2AC — 0x8007A2AC..0x8007A324 (120 bytes).
|
||||
*
|
||||
* Original words (objdump of the validated payload, little-endian):
|
||||
* bltz a0,0x8007A2B8 if (a0 >= 0) D_80122590 = a0
|
||||
* _li a2,-1 (delay slot) v = -1
|
||||
* sw a0,3160(gp)
|
||||
* 0x8007A2B8:
|
||||
* bgez a1,0x8007A2D8 if (a1 < 0) return D_80122590
|
||||
* _nop
|
||||
* lw v0,3160(gp)
|
||||
* j 0x8007A31C
|
||||
* _nop
|
||||
* 0x8007A2CC: <- found: commit the cursor and return the value
|
||||
* sw v0,3160(gp) D_80122590 = i + 1
|
||||
* j 0x8007A31C
|
||||
* _move v0,a2 (delay slot)
|
||||
* 0x8007A2D8:
|
||||
* lw a0,3160(gp) i = D_80122590
|
||||
* nop
|
||||
* slt v0,a0,a1 v0 = (i < a1)
|
||||
* beqz v0,0x8007A318 if (i >= a1) return v
|
||||
* _sll v0,a0,0x2 (delay slot) i * 4
|
||||
* lui v1,0x8014 \ table = D_80138460
|
||||
* addiu v1,v1,-31648 /
|
||||
* addu v1,v0,v1 &table[i]
|
||||
* 0x8007A2F8:
|
||||
* lw a2,0(v1) v = table[i]
|
||||
* nop
|
||||
* bgez a2,0x8007A2CC if (v >= 0) -> commit
|
||||
* _addiu v0,a0,1 (delay slot) v0 = i + 1
|
||||
* addiu a0,a0,1 i++
|
||||
* slt v0,a0,a1 i < a1
|
||||
* bnez v0,0x8007A2F8 loop
|
||||
* _addiu v1,v1,4 (delay slot) &table[i]++
|
||||
* 0x8007A318: move v0,a2 return v
|
||||
* 0x8007A31C: jr ra; _nop
|
||||
*
|
||||
* A cursor-plus-scan: an optional cursor set, then a forward scan for the first
|
||||
* non-negative table entry, leaving the cursor just past the found entry.
|
||||
*
|
||||
* WHAT THE BYTES PIN DOWN:
|
||||
*
|
||||
* 1. THE TWO EARLY GUARDS ARE PLAIN `if`s, AND THE FIRST ONE WRITES THE GLOBAL
|
||||
* ONLY ON THE NON-NEGATIVE PATH. `bltz a0` skips the `sw`, so the store is
|
||||
* inside `if (a0 >= 0)`; the negative case is the fall-through.
|
||||
* 2. THE `a1 < 0` CASE RETURNS THE GLOBAL ITSELF, not a constant: the path is
|
||||
* `lw v0,3160(gp)` then a jump to the shared epilogue. So the function's
|
||||
* return value on that path is the cursor, which is why `v` cannot be folded
|
||||
* into a single result register.
|
||||
* 3. THE FOUND PATH STORES `i + 1`, NOT `i`. `addiu v0,a0,1` sits in the
|
||||
* `bgez` delay slot (so `v0` is `i + 1` on both paths out of the test) and the
|
||||
* found block stores THAT to the global. So the cursor is advanced past the
|
||||
* found entry.
|
||||
* 4. THE FALL-THROUGH RESULT IS THE LAST TABLE VALUE, NOT ZERO. The `-1` is
|
||||
* materialised in the FIRST branch's delay slot (`li a2,-1`), i.e. `v` is
|
||||
* initialised once before the loop; if the loop never runs, or runs to
|
||||
* exhaustion without a non-negative entry, `move v0,a2` returns whatever `v`
|
||||
* holds. That is why the initialisation is a `li` in a delay slot rather than
|
||||
* a `move v0,zero` at the exit.
|
||||
* 5. THE TABLE IS `int` AND SCALED BY 4 (`sll v0,a0,0x2`), and the base is the
|
||||
* two-piece `la` form of 0x80138460 (`lui 0x8014` + `addiu -31648`), so the
|
||||
* address-named symbol `D_80138460` is what reproduces the encoding.
|
||||
* 6. NO FRAME: the row makes no calls, so there is no prologue.
|
||||
*
|
||||
* LIMITS: the meaning of the table, of the negative-entry sentinel and of the
|
||||
* cursor global are hypotheses read from the instruction shapes. Only the
|
||||
* compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
extern int D_80122590;
|
||||
extern int D_80138460[];
|
||||
|
||||
int func_8007A2AC(int a0, int a1)
|
||||
{
|
||||
int i;
|
||||
int v = -1;
|
||||
|
||||
if (a0 >= 0)
|
||||
D_80122590 = a0;
|
||||
if (a1 < 0)
|
||||
return D_80122590;
|
||||
for (i = D_80122590; i < a1; i++) {
|
||||
v = D_80138460[i];
|
||||
if (v >= 0) {
|
||||
D_80122590 = i + 1;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* func_800BC960 — 100 bytes at 0x800BC960..0x800BC9C4
|
||||
*
|
||||
* Countdown tick: decrements a global counter, and when it reaches zero runs one
|
||||
* teardown on a global handle and, if a flag is set, a second one.
|
||||
*
|
||||
* Original words:
|
||||
* 8F820768 lw v0,1896(gp) ; v0 = D_801220A0 (hoisted above the frame)
|
||||
* 27BDFFE8 addiu sp,sp,-24
|
||||
* 10400012 beq v0,zero,0x800BC9B4 ; already zero -> return
|
||||
* AFBF0010 sw ra,16(sp) ; (delay)
|
||||
* 2442FFFF addiu v0,v0,-1 ; v0 -= 1
|
||||
* AF820768 sw v0,1896(gp) ; D_801220A0 = v0
|
||||
* 1C40000E bgtz v0,0x800BC9B4 ; still positive -> return
|
||||
* 00000000 nop
|
||||
* 3C048013 lui a0,0x8013
|
||||
* 8C84D79C lw a0,-10340(a0) ; a0 = D_8012D79C
|
||||
* 0C02F1F6 jal 0x800BC7D8
|
||||
* 00000000 nop ; (delay) func_800BC7D8(handle)
|
||||
* 3C028012 lui v0,0x8012
|
||||
* 90422374 lbu v0,9076(v0) ; v0 = D_80122374 UNSIGNED byte
|
||||
* 00000000 nop
|
||||
* 10400005 beq v0,zero,0x800BC9B4 ; flag clear -> return
|
||||
* 00000000 nop
|
||||
* 3C048013 lui a0,0x8013
|
||||
* 8C84DD30 lw a0,-8912(a0) ; a0 = D_8012DD30
|
||||
* 0C02F1F6 jal 0x800BC7D8
|
||||
* 00000000 nop ; (delay) func_800BC7D8(handle)
|
||||
* 8FBF0010 lw ra,16(sp) ; (END)
|
||||
* 27BD0018 addiu sp,sp,24
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* The counter load is hoisted above the frame setup (the func_80042964 shape) and is
|
||||
* tested BEFORE the decrement, while the sign test uses the DECREMENTED value — so the
|
||||
* source decrements into the global and then tests the new value, and the zero test is
|
||||
* a separate guard on the old one. Both guards branch to the same shared epilogue, so
|
||||
* they are `return;` statements.
|
||||
*
|
||||
* Both handles are loaded from globals and passed in `a0`; the second call's handle is
|
||||
* a DIFFERENT global from the first (0x8012D79C vs 0x8012DD30), and the flag
|
||||
* D_80122374 is the same byte that src/func_800453C0.c tests.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the four globals and the flag's meaning are
|
||||
* hypotheses read from the instruction shape; only the bytes are evidence. D_801220A0
|
||||
* is gp-relative and carries a registry `gp` marker; the other three are loaded
|
||||
* ABSOLUTELY (`lui` + displacement) and are not in the registry, so their address-named
|
||||
* spellings stay absolute as the original does. The flag is read with `lbu`, so it is
|
||||
* an UNSIGNED byte. The function sets no result, so it is `void`.
|
||||
*/
|
||||
|
||||
extern int D_801220A0;
|
||||
extern int D_8012D79C;
|
||||
extern unsigned char D_80122374;
|
||||
extern int D_8012DD30;
|
||||
extern void func_800BC7D8(int a0);
|
||||
|
||||
void func_800BC960(void)
|
||||
{
|
||||
int v = D_801220A0;
|
||||
|
||||
if (v == 0)
|
||||
return;
|
||||
|
||||
D_801220A0 = --v;
|
||||
|
||||
if (v > 0)
|
||||
return;
|
||||
|
||||
func_800BC7D8(D_8012D79C);
|
||||
|
||||
if (D_80122374 != 0)
|
||||
func_800BC7D8(D_8012DD30);
|
||||
}
|
||||
Reference in New Issue
Block a user