phase10: merge 21 — 468 distinct bodies / 477 regions (7 from the milestone)
+4 bodies (worker A claims 19-22, all first-attempt). Candidate gate MATCH before
promotion. make check green: regions=477 AGREE, differing_bytes=0 MATCH.
THE STACK-SWITCH IDIOM IS NOW PROVEN TWICE, BYTE-EXACT, WITH NO OVERRIDE.
0x8006B9E0 matches on the first attempt with no maspsx override, no clobbers and no
operand declarations -- the three-macro form reproduces it exactly, including the
bare filler nop between the second restore and the epilogue's lw ra. Contrasting it
with A's 0x800BC658 near-match (same macros, off by 2 nops) isolates the variable:
0x8006B9E0's two calls have EMPTY delay slots while 0x800BC658's non-fast-path calls
set up an argument cc1 schedules into the slot. So the asm is not the variable -- an
ARGUMENT MOVE AT THE CALL SITE is. That narrows the 0x800BC658 remainder.
TWO NAMED-LOCAL FINDINGS, BOTH DIRECTIONS NOW OBSERVED (the family has seven
instances across the team):
- 0x8005E538: the HANDLE must be a named local loaded before the first call.
Inline, cc1 keeps only the object pointer in a callee-saved register and
RELOADS the handle after the first call (76 vs 88 bytes). Generalisation: a value
that must survive an intervening call has to be a named local.
- 0x800F66B8 is the MIRROR CASE and a useful NEGATIVE: d[0] and d[1] are each
loaded twice with neither held in a register, and the two loads of d[0] go to
DIFFERENT registers. Naming a local would have been WRONG. So naming a value can
be byte-required and NOT naming it can be byte-required; the diagnostic is the
load count, not a rule about locals.
Inline asm in src/func_8006B9E0.c is documented per the Phase 10 convention
(header states the observed sequence, the reason and the limits).
This commit is contained in:
+1025
-1029
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,7 @@
|
||||
0x800262EC 0x800262F8 src/func_800262E0.c
|
||||
0x800262F8 0x80026304 src/func_800262F8.c
|
||||
0x800263A8 0x800263E8 src/func_800263A8.c
|
||||
0x80026650 0x800266A8 src/func_80026650.c
|
||||
0x800267C0 0x800267CC src/func_800267C0.c
|
||||
0x800268C4 0x800268F4 src/func_800268C4.c
|
||||
0x80026C2C 0x80026C7C src/func_80026C2C.c
|
||||
@@ -197,6 +198,7 @@
|
||||
0x80058288 0x800582AC src/func_80058288.c
|
||||
0x8005E29C 0x8005E340 src/func_8005E29C.c
|
||||
0x8005E3D0 0x8005E3F4 src/func_8005E3D0.c
|
||||
0x8005E538 0x8005E590 src/func_8005E538.c
|
||||
0x8005E79C 0x8005E820 src/func_8005E79C.c
|
||||
0x8005ED6C 0x8005EDBC src/func_8005ED6C.c
|
||||
0x800645CC 0x80064664 src/func_800645CC.c
|
||||
@@ -220,6 +222,7 @@
|
||||
0x8006B1CC 0x8006B214 src/func_8006B1CC.c
|
||||
0x8006B66C 0x8006B6BC src/func_8006B66C.c
|
||||
0x8006B778 0x8006B7C0 src/func_8006B778.c
|
||||
0x8006B9E0 0x8006BA3C src/func_8006B9E0.c
|
||||
0x8006BC08 0x8006BC34 src/func_8006BC08.c
|
||||
0x8006BC34 0x8006BC74 src/func_8006BC34.c
|
||||
0x8006EC94 0x8006ECD4 src/func_8006EC94.c
|
||||
@@ -385,6 +388,7 @@
|
||||
0x800F5B40 0x800F5B70 src/func_800F5B40.c
|
||||
0x800F6330 0x800F6364 src/func_800F6330.c
|
||||
0x800F6570 0x800F6594 src/func_800F6570.c
|
||||
0x800F66B8 0x800F6710 src/func_800F66B8.c
|
||||
0x800F75D0 0x800F760C src/func_800F75D0.c
|
||||
0x800F7990 0x800F79C0 src/func_800F7990.c
|
||||
0x800F79C0 0x800F79F0 src/func_800F79C0.c
|
||||
|
||||
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* func_80026650 — 88 bytes at 0x80026650..0x800266A8
|
||||
*
|
||||
* Drains a singly-linked list held in a field of the argument, calling one routine
|
||||
* per node, then clears the head.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFE0 addiu sp,sp,-32
|
||||
* AFB10014 sw s1,20(sp)
|
||||
* 00808821 addu s1,a0,zero ; s1 = p
|
||||
* AFBF0018 sw ra,24(sp)
|
||||
* AFB00010 sw s0,16(sp)
|
||||
* 8E250000 lw a1,0(s1) ; a1 = *p (head)
|
||||
* 00000000 nop
|
||||
* 10A00007 beq a1,zero,0x8002668C ; empty -> clear and return
|
||||
* 00000000 nop
|
||||
* 8CB00008 lw s0,8(a1) ; (L2) s0 = node->field_8
|
||||
* 0C009958 jal 0x80026560
|
||||
* 02202021 addu a0,s1,zero ; (delay) func_80026560(p, node)
|
||||
* 02002821 addu a1,s0,zero ; node = next
|
||||
* 14A0FFFB bne a1,zero,0x80026674 ; more -> body
|
||||
* 00000000 nop
|
||||
* AE200000 sw zero,0(s1) ; (END) *p = 0
|
||||
* 8FBF0018 lw ra,24(sp)
|
||||
* 8FB10014 lw s1,20(sp)
|
||||
* 8FB00010 lw s0,16(sp)
|
||||
* 27BD0020 addiu sp,sp,32
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* Same family callee and same node layout as src/func_800A6B38.c, but with no payload
|
||||
* store and a `*p = 0` at the single exit. The head is read ONCE before the loop and
|
||||
* the loop's back edge branches to the BODY start with the next node moved into `a1`
|
||||
* immediately before it, so this is a `while` whose condition is re-tested on the
|
||||
* loop variable rather than a re-read of `*p`.
|
||||
*
|
||||
* The call's second argument is the CURRENT NODE: `a1` holds it at the `jal` (it was
|
||||
* loaded into `a1` at the loop head and is only reassigned AFTER the call). Note that
|
||||
* src/func_80012E84.c passes an int through the same register, so the second
|
||||
* parameter's TYPE is not recoverable — only its register and its width are.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the node offsets (0 for the head, 8 for the
|
||||
* link) and the list direction are hypotheses read from the instruction shape; only
|
||||
* the bytes are evidence. `p` is a pointer to the head pointer. func_80026560 is this
|
||||
* family's unregistered callee; its map is (pointer, one more register) and the
|
||||
* second slot's type is unknown.
|
||||
*/
|
||||
|
||||
extern void func_80026560(void *p, void *value);
|
||||
|
||||
void func_80026650(int **p)
|
||||
{
|
||||
int *node = *p;
|
||||
|
||||
while (node != 0) {
|
||||
int *next = *(int **)((char *)node + 8);
|
||||
|
||||
func_80026560(p, node);
|
||||
node = next;
|
||||
}
|
||||
*p = 0;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* func_8005E538 — 88 bytes at 0x8005E538..0x8005E590
|
||||
*
|
||||
* Two-call update: fills a 16-byte stack record from two fields of one sub-object,
|
||||
* passes it with a cached handle to a second routine, and writes that result back
|
||||
* into the handle field.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFD0 addiu sp,sp,-48
|
||||
* AFB10024 sw s1,36(sp)
|
||||
* 00808821 addu s1,a0,zero ; s1 = a0
|
||||
* AFBF0028 sw ra,40(sp)
|
||||
* AFB00020 sw s0,32(sp)
|
||||
* 8E220020 lw v0,32(s1) ; v0 = a0->field_20
|
||||
* 00000000 nop
|
||||
* 8C440000 lw a0,0(v0) ; a0 = v0[0]
|
||||
* 8C4500E4 lw a1,228(v0) ; a1 = v0[57]
|
||||
* 8E300B20 lw s0,2848(s1) ; s0 = a0->field_b20 (the cached handle)
|
||||
* 0C01A01E jal 0x80068078
|
||||
* 27A60010 addiu a2,sp,16 ; (delay) a2 = &rec
|
||||
* 02002021 addu a0,s0,zero ; a0 = the cached handle
|
||||
* 0C02AB27 jal 0x800AAC9C
|
||||
* 27A50010 addiu a1,sp,16 ; (delay) a1 = &rec
|
||||
* AE220B20 sw v0,2848(s1) ; a0->field_b20 = result
|
||||
* 8FBF0028 lw ra,40(sp)
|
||||
* 8FB10024 lw s1,36(sp)
|
||||
* 8FB00020 lw s0,32(sp)
|
||||
* 27BD0030 addiu sp,sp,48
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* Frame arithmetic fixes the record at 16 bytes: `s0` is at 0x20 and `ra` at 0x28, so
|
||||
* the local between the outgoing argument area and the saves is exactly 0x10 bytes.
|
||||
* The second call's result is both stored back into the handle field AND left in `v0`
|
||||
* at the epilogue, so the function RETURNS it.
|
||||
*
|
||||
* THE HANDLE MUST BE A NAMED LOCAL LOADED BEFORE THE FIRST CALL. Written inline in
|
||||
* the second call (`func_800AAC9C(*(int *)((char *)a0 + 0xB20), rec)`) cc1 keeps only
|
||||
* the OBJECT POINTER in a callee-saved register and RELOADS the handle after the first
|
||||
* call, giving a 40-byte frame with one save (76 bytes vs 88). Naming it —
|
||||
* `int handle = *(int *)((char *)a0 + 0xB20);` before the first call — makes cc1 load
|
||||
* it early and keep it across the call, which is why the original saves TWO registers
|
||||
* in a 48-byte frame: `s1` for the object pointer and `s0` for the handle. This is
|
||||
* the same "an intermediate named local is byte-required" family as the branchless
|
||||
* boolean in src/func_80099D14.c.
|
||||
*
|
||||
* Both calls take three and two arguments respectively: the first is
|
||||
* `f(v0[0], v0[57], &rec)` and the second `g(handle, &rec)`.
|
||||
*
|
||||
* LIMITS: the function name, both callees, the offsets 0x20, 0, 0xE4 and 0xB20, and
|
||||
* the record's size are hypotheses read from the instruction shape; only the bytes are
|
||||
* evidence. Neither callee is registered and both are referenced by address-named
|
||||
* spellings. The record's fields are never read here, so its layout is not recoverable
|
||||
* from this body.
|
||||
*/
|
||||
|
||||
extern int func_80068078(int a0, int a1, void *a2);
|
||||
extern int func_800AAC9C(int a0, void *a1);
|
||||
|
||||
int func_8005E538(int a0)
|
||||
{
|
||||
char rec[16];
|
||||
int *sub = *(int **)((char *)a0 + 0x20);
|
||||
int handle = *(int *)((char *)a0 + 0xB20);
|
||||
int r;
|
||||
|
||||
func_80068078(*(int *)sub, *(int *)((char *)sub + 0xE4), rec);
|
||||
r = func_800AAC9C(handle, rec);
|
||||
*(int *)((char *)a0 + 0xB20) = r;
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* func_8006B9E0 — 92 bytes at 0x8006B9E0..0x8006BA3C
|
||||
*
|
||||
* Runs two routines, each on the alternate scratchpad stack, in sequence.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFE8 addiu sp,sp,-24
|
||||
* AFBF0010 sw ra,16(sp)
|
||||
* 3C011F80 lui at,0x1F80
|
||||
* AC3D03FC sw sp,1020(at) ; save sp at 0x1F8003FC
|
||||
* 3C1D1F80 lui sp,0x1F80
|
||||
* 37BD03DC ori sp,sp,0x3DC ; sp = 0x1F8003DC
|
||||
* 0C01AB81 jal 0x8006AE04
|
||||
* 00000000 nop ; (delay)
|
||||
* 3C1D1F80 lui sp,0x1F80
|
||||
* 8FBD03FC lw sp,1020(sp) ; restore sp
|
||||
* 3C011F80 lui at,0x1F80
|
||||
* AC3D03FC sw sp,1020(at)
|
||||
* 3C1D1F80 lui sp,0x1F80
|
||||
* 37BD03DC ori sp,sp,0x3DC
|
||||
* 0C01AB95 jal 0x8006AE54
|
||||
* 00000000 nop ; (delay)
|
||||
* 3C1D1F80 lui sp,0x1F80
|
||||
* 8FBD03FC lw sp,1020(sp)
|
||||
* 00000000 nop
|
||||
* 8FBF0010 lw ra,16(sp)
|
||||
* 27BD0018 addiu sp,sp,24
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* WHY THIS FILE CONTAINS INLINE ASSEMBLY. The two stack-switch idioms write the
|
||||
* STACK POINTER itself, and the register the C would have to reassign is one cc1
|
||||
* controls for its own purposes — the inexpressibility is structural, not a
|
||||
* scheduling or allocation tie-break. This is the developer-sanctioned extension of
|
||||
* the Phase 8 convention (inline asm for shapes C provably cannot express), recorded
|
||||
* in docs/MATCHING_CONVENTIONS.md; the integer logic and all scheduling still come
|
||||
* from cc1 and maspsx. The sibling src/func_800BC658.c uses the same three macros.
|
||||
*
|
||||
* The alternate stack is 0x1F8003DC and the saved pointer lives at 0x1F8003FC; the
|
||||
* restore is `lui sp,0x1F80` / `lw sp,1020(sp)`, i.e. it uses `sp` as the base after
|
||||
* pointing it at 0x1F800000. Both calls have an empty delay slot filled with a `nop`,
|
||||
* so neither callee receives any argument this body sets up.
|
||||
*
|
||||
* LIMITS: the function name, the two callees, the scratchpad addresses 0x1F8003DC and
|
||||
* 0x1F8003FC and the sequence are hypotheses read from the instruction shape; only
|
||||
* the bytes are evidence. Neither callee is registered and both are referenced by
|
||||
* address-named spellings. The bare `nop` after the second restore is a filler between
|
||||
* the asm and the epilogue's `lw ra` and is reproduced by the statement layout, not
|
||||
* by an explicit instruction.
|
||||
*/
|
||||
|
||||
#define SF3_SAVE_SP() \
|
||||
__asm__ __volatile__("lui $at,0x1f80\n\t" \
|
||||
"sw $sp,1020($at)")
|
||||
#define SF3_SET_SP() \
|
||||
__asm__ __volatile__("lui $sp,0x1f80\n\t" \
|
||||
"ori $sp,$sp,0x3dc")
|
||||
#define SF3_REST_SP() \
|
||||
__asm__ __volatile__("lui $sp,0x1f80\n\t" \
|
||||
"lw $sp,1020($sp)")
|
||||
|
||||
extern void func_8006AE04(void);
|
||||
extern void func_8006AE54(void);
|
||||
|
||||
void func_8006B9E0(void)
|
||||
{
|
||||
SF3_SAVE_SP();
|
||||
SF3_SET_SP();
|
||||
func_8006AE04();
|
||||
SF3_REST_SP();
|
||||
|
||||
SF3_SAVE_SP();
|
||||
SF3_SET_SP();
|
||||
func_8006AE54();
|
||||
SF3_REST_SP();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* func_800F66B8 — 88 bytes at 0x800F66B8..0x800F6710
|
||||
*
|
||||
* Fills three words of a descriptor from two 16-bit arguments and two shift
|
||||
* expressions over the descriptor's own first two words, then calls on with the
|
||||
* second shift.
|
||||
*
|
||||
* Original words:
|
||||
* 27BDFFE8 addiu sp,sp,-24
|
||||
* 3084FFFF andi a0,a0,0xffff ; a0 &= 0xffff
|
||||
* 30A5FFFF andi a1,a1,0xffff ; a1 &= 0xffff
|
||||
* AFBF0010 sw ra,16(sp)
|
||||
* ACC40008 sw a0,8(a2) ; d[2] = a0
|
||||
* 8CC40000 lw a0,0(a2) ; a0 = d[0]
|
||||
* 8CC20004 lw v0,4(a2) ; v0 = d[1]
|
||||
* 24030004 addiu v1,zero,4 ; v1 = 4
|
||||
* ACC5000C sw a1,12(a2) ; d[3] = a1
|
||||
* 8CC50000 lw a1,0(a2) ; a1 = d[0] (RELOADED)
|
||||
* 00831804 sllv v1,v1,a0 ; v1 = 4 << d[0]
|
||||
* 00431021 addu v0,v0,v1 ; v0 = d[1] + v1
|
||||
* 2442FFFC addiu v0,v0,-4 ; v0 -= 4
|
||||
* ACC20010 sw v0,16(a2) ; d[4] = v0
|
||||
* 24020001 addiu v0,zero,1 ; v0 = 1
|
||||
* 8CC40004 lw a0,4(a2) ; a0 = d[1] (RELOADED)
|
||||
* 0C03D109 jal 0x800F4424
|
||||
* 00A22804 sllv a1,v0,a1 ; (delay) a1 = 1 << d[0]
|
||||
* 8FBF0010 lw ra,16(sp)
|
||||
* 27BD0018 addiu sp,sp,24
|
||||
* 03E00008 jr ra
|
||||
* 00000000 nop
|
||||
*
|
||||
* `d[0]` and `d[1]` are each loaded TWICE and neither value is held in a register
|
||||
* across the second use, so the source reads the fields again rather than storing
|
||||
* them in locals: `d[4] = d[1] + (4 << d[0]) - 4;` and `f(d[1], 1 << d[0]);`. The two
|
||||
* loads of `d[0]` go to different registers (`a0` for the first shift, `a1` for the
|
||||
* second), which is why the second shift's amount arrives in `a1` — a named local
|
||||
* would have collapsed both loads into one.
|
||||
*
|
||||
* The store order is d[2], d[3], d[4], and the two `andi`s are hoisted above the
|
||||
* frame's `sw ra`.
|
||||
*
|
||||
* LIMITS: the function name, the callee, the descriptor offsets (0, 1, 2, 3, 4), the
|
||||
* 16-bit masks and the constants 4 and 1 are hypotheses read from the instruction
|
||||
* shape; only the bytes are evidence. Both arguments are masked to 16 bits with `andi`
|
||||
* so both are narrower than int; `sllv` takes the shift amount from a register, so
|
||||
* neither shift is by a constant. func_800F4424 is not registered and is referenced by
|
||||
* its address-named spelling. The function sets no result, so it is `void`.
|
||||
*/
|
||||
|
||||
extern void func_800F4424(int a0, int a1);
|
||||
|
||||
void func_800F66B8(int a0, int a1, int *d)
|
||||
{
|
||||
a0 &= 0xffff;
|
||||
a1 &= 0xffff;
|
||||
d[2] = a0;
|
||||
d[3] = a1;
|
||||
d[4] = d[1] + (4 << d[0]) - 4;
|
||||
func_800F4424(d[1], 1 << d[0]);
|
||||
}
|
||||
Reference in New Issue
Block a user