phase8: merge cycle 6 and record the conventions the phase produced
5 new matches from worker A (func_800F75D0, func_80026180, func_8003768C, func_80089D14, func_800681A4) -> 158 regions / 149 distinct bodies, candidate gate 0 differing bytes, make check green. MATCHING_CONVENTIONS.md now records the four per-region override keys with the byte-level measurement behind each (cc1=, as=, gp=-NAME, maspsx=off), the inline-assembly convention as the developer decided it (accepted for coprocessor and kernel instructions, documented per file, integer logic in C, no .word lumps, and a register-name binding is not inline assembly), the rule that an unmatched draft does not stay in src/, and the worklist classes that are excluded outright with counted reasons.
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
0x80022A60 0x80022A98 src/func_80022A60.c
|
||||
0x80022FB8 0x80022FCC src/func_80022FB8.c
|
||||
0x80024C14 0x80024C34 src/func_80024C14.c
|
||||
0x80026180 0x800261C0 src/func_80026180.c
|
||||
0x80026258 0x80026264 src/func_80026258.c
|
||||
0x80026264 0x80026274 src/func_80026264.c
|
||||
0x800262E0 0x800262EC src/func_800262E0.c
|
||||
@@ -57,6 +58,7 @@
|
||||
0x80036308 0x80036328 src/func_80036308.c
|
||||
0x8003636C 0x80036378 src/func_8003636C.c
|
||||
0x80036378 0x80036380 src/func_80036378.c
|
||||
0x8003768C 0x800376CC src/func_8003768C.c
|
||||
0x80038788 0x80038790 src/func_80038788.c
|
||||
0x80038790 0x8003879C src/func_80038790.c
|
||||
0x8003B2F0 0x8003B320 src/func_8003B2F0.c
|
||||
@@ -67,6 +69,7 @@
|
||||
0x80057DFC 0x80057E04 src/func_80057DFC.c
|
||||
0x80058288 0x800582AC src/func_80058288.c
|
||||
0x80065B6C 0x80065B8C src/func_80065B6C.c
|
||||
0x800681A4 0x800681E0 src/func_800681A4.c
|
||||
0x80068470 0x8006848C src/func_80068470.c
|
||||
0x80068F40 0x80068F6C src/func_80068F40.c
|
||||
0x80068F98 0x80068FA8 src/func_80068F98.c
|
||||
@@ -87,6 +90,7 @@
|
||||
0x80089C4C 0x80089C54 src/func_80042088.c
|
||||
0x80089C54 0x80089C64 src/func_80089C54.c
|
||||
0x80089C64 0x80089C74 src/func_80089C64.c
|
||||
0x80089D14 0x80089D54 src/func_80089D14.c
|
||||
0x80089E74 0x80089E7C src/func_80042088.c
|
||||
0x8008B8D4 0x8008B8E4 src/func_8008B8D4.c
|
||||
0x8008B8E4 0x8008B8F4 src/func_8008B8E4.c
|
||||
@@ -114,6 +118,7 @@
|
||||
0x800F3160 0x800F316C src/func_800F3160.c maspsx=off
|
||||
0x800F3E70 0x800F3E88 src/func_800F3E70.c
|
||||
0x800F5B40 0x800F5B70 src/func_800F5B40.c
|
||||
0x800F75D0 0x800F760C src/func_800F75D0.c
|
||||
0x800F7A84 0x800F7A94 src/func_800F7A84.c
|
||||
0x800F7FB4 0x800F7FD8 src/func_800F7FB4.c
|
||||
0x800F8294 0x800F82A4 src/func_800F8294.c
|
||||
|
||||
|
@@ -68,7 +68,28 @@ Rules the harness enforces:
|
||||
|
||||
Unmatched code remains raw data taken from the original. It does not appear as `NON_MATCHING` C, and
|
||||
it never enters the default build. A failed candidate is recorded as a bounded negative, not hidden
|
||||
behind a guard.
|
||||
behind a guard — **and it does not stay in `src/`**: a source that does not match is moved to an ignored
|
||||
staging path (or deleted) once its evidence is written down. A tracked `src/` file is a claim.
|
||||
|
||||
Some classes are excluded from the worklist outright, each with a counted reason, so that no session
|
||||
spends budget on them: all-zero bodies (a zero-filled region the walk ran through), and
|
||||
**trapping-arithmetic bodies** — 50 of the 1,937 `exact` extents contain an `add`/`sub` that no available
|
||||
compiler emits (cookbook finding 26). Note the limit recorded with it: that check is *sufficient but not
|
||||
complete*, so `excluded_trapping_arith=0` does not mean a row is matchable.
|
||||
|
||||
## Inline assembly (coprocessor and kernel instructions)
|
||||
|
||||
**Accepted, with per-file documentation** (Phase 8, developer decision). A region may contain a
|
||||
`__asm__ volatile` statement when C cannot name the instruction — a COP2/GTE operation or a BIOS
|
||||
`syscall`. The boundary is narrow and enforced by convention:
|
||||
|
||||
- the **integer logic stays in C**, and the epilogue and all scheduling come from `cc1`/`maspsx`;
|
||||
- no `.word` lumps and no whole-function assembly;
|
||||
- the statement is written in the re-derived macro form, and the file's header says what it is and why;
|
||||
- `include/gtemac.h` holds the re-derived COP2 macros and the register map as the provenance record.
|
||||
|
||||
A **register-name binding is not inline assembly** and needs no exemption: `register int sp __asm__("$29")`
|
||||
is a documented GNU C extension and reproduces the stack accessor exactly.
|
||||
|
||||
## Duplicate bodies
|
||||
|
||||
@@ -154,6 +175,21 @@ emulates ASPSX 2.56 (the version on the PsyQ 4.0 SDK banner): it emits `.set nor
|
||||
`nop`s so delay-slot scheduling matches the original. `--no-maspsx` disables it for comparison;
|
||||
`--aspsx-version` overrides the pinned version.
|
||||
|
||||
### Per-region overrides
|
||||
|
||||
The registry's optional fourth field carries space-separated `key=value` overrides. Four keys exist, and
|
||||
each one exists because a byte-level measurement demanded it:
|
||||
|
||||
| Override | Meaning | Why it exists |
|
||||
|---|---|---|
|
||||
| `cc1=-O0` | append compiler flags for this region | some translation units were compiled with other `-O`/`-G` values |
|
||||
| `as=-G8` | append assembler flags for this region | as above |
|
||||
| `gp=-NAME[,NAME]` | do **not** treat `NAME` as gp-relative here, despite its registry marker | the access form is per **site**: `0x80121F84` is read gp-relative at `0x800A80BC` and written absolutely at `0x8002D288` (cookbook finding 16) |
|
||||
| `maspsx=off` | run this region without the ASPSX emulation stage | maspsx's unconditional `nop` for a jump destroys the delay-slot fill GNU `as` reorder mode performs on an expanded symbol store — six regions need this, including the store-in-delay-slot shape that stood unresolved for three phases (cookbook finding 17) |
|
||||
|
||||
`range` accepts the equivalents `--cc1-flag`, `--as-flag`, `--no-gp NAME` and `--no-maspsx` so a worker can
|
||||
reproduce a region's exact build while iterating.
|
||||
|
||||
## Drafting a function
|
||||
|
||||
For anything larger than a few instructions, draft from Ghidra rather than raw disassembly:
|
||||
@@ -173,6 +209,15 @@ make test # synthetic-only suite; needs no game input
|
||||
make check # make test + make extents-verify + the full-binary byte gate (needs the executable)
|
||||
```
|
||||
|
||||
### Working in parallel with other sessions
|
||||
|
||||
When more than one session works in this repository at once, `docs/PHASE8_PROTOCOL.md` governs: who may
|
||||
write which file, the staging formats for claims and symbol requests, and the merge workflow. The load-bearing
|
||||
rule is that the tracked registries, `build/` and all writing git commands belong to a single coordinating
|
||||
session, and that a claim is only ever merged to a **candidate** registry which is gated before it is promoted.
|
||||
`tools/sf3_merge` enforces the structural half of that (extent exactness, overlap, payload bounds, source
|
||||
existence) and `make gate` the byte half.
|
||||
|
||||
`make gate` prints `c_regions=<n>` and, when the registry is empty, an explicit note that the build
|
||||
contains no C. A `MATCH` with `c_regions=0` means the data baseline was reproduced, not that anything
|
||||
was matched.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* func_80026180 — 64 bytes at 0x80026180..0x800261C0
|
||||
*
|
||||
* Byte-identical reconstruction of a guarded two-halfword sum written through an
|
||||
* out-parameter, returning a status. `lhu` fixes zero-extended 16-bit reads
|
||||
* (cookbook finding 7), each of which is followed by a load-delay `nop`. The two
|
||||
* addends 0x80142EA8/0x80142EAA are adjacent halfwords loaded with the macro
|
||||
* form that expands into the destination register (cookbook finding 2). The
|
||||
* `clear v0` before the second store is the `return 0` value hoisted, and the
|
||||
* `li v0,0x16` in the guard's delay slot is the `return 0x16` value.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* beq a0,zero,exit if (source == 0) return 0x16
|
||||
* li v0,0x16 (delay slot) v0 = 22
|
||||
* lhu v0,0x4(a0) v0 = source[2]
|
||||
* lui v1,0x8014
|
||||
* lhu v1,0x2ea8(v1) v1 = D_80142EA8
|
||||
* nop (load delay)
|
||||
* addu v0,v0,v1 v0 += v1
|
||||
* sh v0,0x0(a1) out[0] = v0
|
||||
* lhu v1,0x6(a0) v1 = source[3]
|
||||
* lui a0,0x8014
|
||||
* lhu a0,0x2eaa(a0) a0 = D_80142EAA
|
||||
* clear v0 v0 = 0 (the return value)
|
||||
* addu v1,v1,a0 v1 += a0
|
||||
* sh v1,0x2(a1) out[1] = v1
|
||||
* exit:
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* LIMITS: the offsets, the widths and the constant 0x16 are evidence; the
|
||||
* globals' names, types and meanings and the parameters' meanings are
|
||||
* hypotheses, as is whether 0x16 is a status code or a count.
|
||||
*/
|
||||
|
||||
extern unsigned short D_80142EA8;
|
||||
extern unsigned short D_80142EAA;
|
||||
|
||||
int func_80026180(unsigned short *source, unsigned short *out) {
|
||||
if (source == 0) {
|
||||
return 0x16;
|
||||
}
|
||||
out[0] = source[2] + D_80142EA8;
|
||||
out[1] = source[3] + D_80142EAA;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* func_8003768C — 64 bytes at 0x8003768C..0x800376CC
|
||||
*
|
||||
* Byte-identical reconstruction of a two-level linked-list search. The head
|
||||
* pointer is a field of the argument object, each node points at an entry at
|
||||
* offset 0 and at the next node at offset 8, and the search key is the entry's
|
||||
* first word. The not-found value 0 is materialised with `clear v0` in the
|
||||
* guard's delay slot and again in the loop branch's delay slot, while the found
|
||||
* value moves `a0` into `v0` in the `beq`'s delay slot — two different return
|
||||
* values, so the epilogue is shared and each path sets `v0` itself.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lw v1,0x14ec(a0) v1 = *(char **)(object + 0x14ec)
|
||||
* nop (load delay)
|
||||
* beq v1,zero,exit if (node == 0) return 0
|
||||
* clear v0 (delay slot) v0 = 0
|
||||
* loop:
|
||||
* lw a0,0x0(v1) a0 = *(char **)node (the entry)
|
||||
* nop (load delay)
|
||||
* lw v0,0x0(a0) v0 = *(int *)entry
|
||||
* nop (load delay)
|
||||
* beq v0,a1,exit if (v0 == key) return the entry
|
||||
* move v0,a0 (delay slot) v0 = entry
|
||||
* lw v1,0x8(v1) v1 = *(char **)(node + 8)
|
||||
* nop (load delay)
|
||||
* bne v1,zero,loop while (node != 0)
|
||||
* clear v0 (delay slot) v0 = 0
|
||||
* exit:
|
||||
* jr ra
|
||||
* nop
|
||||
*
|
||||
* LIMITS: the offsets, the widths and the two return values are evidence; the
|
||||
* list interpretation, the node layout and the key's meaning are hypotheses.
|
||||
*/
|
||||
|
||||
char *func_8003768C(char *object, int key) {
|
||||
char *node = *(char **)(object + 0x14ec);
|
||||
while (node != 0) {
|
||||
char *entry = *(char **)node;
|
||||
if (*(int *)entry == key) {
|
||||
return entry;
|
||||
}
|
||||
node = *(char **)(node + 8);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* func_800681A4 — 60 bytes at 0x800681A4..0x800681E0
|
||||
*
|
||||
* Byte-identical reconstruction of a table lookup whose index is scaled by 36
|
||||
* with index 2 special-cased to the same offset (0x24 = 36, so the special case
|
||||
* is "index 2 maps to element 1"). The multiply is strength-reduced (`sll 3` /
|
||||
* `addu` / `sll 2` = `(i*8 + i)*4` = `i * 36`), so 36 was a literal (cookbook
|
||||
* finding 12). The base is a symbol combined with a runtime offset, so it is
|
||||
* materialised `lui` + `addu` with the low half as the load displacement
|
||||
* (cookbook finding 5). `srl` fixes an UNSIGNED shift and `andi …,0x1f` the
|
||||
* 5-bit field, both at the end of the body (cookbook finding 8).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* li v0,0x2 v0 = 2
|
||||
* beq a0,v0,special if (index == 2) goto the special case
|
||||
* nop
|
||||
* sll v0,a0,0x3
|
||||
* addu v0,v0,a0
|
||||
* j join
|
||||
* sll v0,v0,0x2 (delay slot) v0 = index * 36
|
||||
* special:
|
||||
* li v0,0x24 v0 = 36
|
||||
* join:
|
||||
* lui at,0x8013
|
||||
* addu at,at,v0 at = D_80132B88 + offset
|
||||
* lw v0,0x2b88(at) v0 = *(unsigned int *)at
|
||||
* nop (load delay)
|
||||
* srl v0,v0,0x1a v0 >>= 26
|
||||
* jr ra
|
||||
* andi v0,v0,0x1f (delay slot) return v0 & 0x1f
|
||||
*
|
||||
* The special case must be written as the ELSE arm (`if (index != 2) ... else
|
||||
* ...`). Written the other way round the compiler lays the blocks out mirrored
|
||||
* -- it makes the special case the fall-through and puts the multiply in the
|
||||
* `j`'s delay slot, giving `bne` instead of the original's `beq` (19 differing
|
||||
* bytes). With the inverted condition the region matches with maspsx ON.
|
||||
*
|
||||
* LIMITS: the stride 36, the special index 2, the shift 26 and the mask 0x1f are
|
||||
* evidence; the table's name, type and meaning and the field's meaning are
|
||||
* hypotheses.
|
||||
*/
|
||||
|
||||
extern char D_80132B88[];
|
||||
|
||||
int func_800681A4(int index) {
|
||||
int offset;
|
||||
if (index != 2) {
|
||||
offset = index * 36;
|
||||
} else {
|
||||
offset = 0x24;
|
||||
}
|
||||
return (*(unsigned int *)(D_80132B88 + offset) >> 26) & 0x1f;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* func_80089D14 — 64 bytes at 0x80089D14..0x80089D54
|
||||
*
|
||||
* Byte-identical reconstruction of a hardware-register bit update. The register
|
||||
* is reached through the pointer field at +0x1c. The OR 0x08800000 is a single
|
||||
* `lui a0,0x880` and the clear mask 0xcffcffff is `lui 0xcffc` + `ori 0xffff`.
|
||||
* The store `*field = masked` is scheduled into the `beq`'s delay slot, which
|
||||
* makes it UNCONDITIONAL — the source therefore assigns it before the test, not
|
||||
* inside the `if` (that is what the bytes prove). The final byte store uses the
|
||||
* `$at`-free register form and lands in the `jr` delay slot (cookbook
|
||||
* finding 8).
|
||||
*
|
||||
* The observed instructions are:
|
||||
* lui v0,0xcffc
|
||||
* lw a2,0x1c(a0) a2 = *(int **)(object + 0x1c)
|
||||
* ori v0,v0,0xffff v0 = 0xcffcffff
|
||||
* lw v1,0x0(a2) v1 = *field
|
||||
* lui a0,0x880 a0 = 0x08800000
|
||||
* or v1,v1,a0 v1 |= 0x08800000
|
||||
* and v0,v1,v0 v0 = v1 & 0xcffcffff
|
||||
* andi v1,v1,0x100 v1 &= 0x100
|
||||
* beq v1,zero,exit if (!(v1 & 0x100)) skip the second update
|
||||
* sw v0,0x0(a2) (delay slot) *field = v0
|
||||
* lw v0,0x4(a2) v0 = *(int *)(field + 1)
|
||||
* nop (load delay)
|
||||
* ori v0,v0,0x2000 v0 |= 0x2000
|
||||
* sw v0,0x4(a2) *(int *)(field + 1) = v0
|
||||
* exit:
|
||||
* jr ra
|
||||
* sb a1,0x15(a2) (delay slot) *(char *)(field + 0x15) = value
|
||||
*
|
||||
* LIMITS: the masks, the offsets and the widths are evidence; the object's and
|
||||
* the register's meanings are hypotheses. Neither access is marked `volatile`
|
||||
* because that is not needed to reproduce these bytes.
|
||||
*/
|
||||
|
||||
void func_80089D14(char *object, char value) {
|
||||
int *field = *(int **)(object + 0x1c);
|
||||
int bits = *field | 0x08800000;
|
||||
int masked = bits & 0xcffcffff;
|
||||
*field = masked;
|
||||
if ((bits & 0x100) != 0) {
|
||||
*(int *)(field + 1) |= 0x2000;
|
||||
}
|
||||
*(char *)((char *)field + 0x15) = value;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* func_800F75D0 — 60 bytes at 0x800F75D0..0x800F760C
|
||||
*
|
||||
* Byte-identical reconstruction of a five-field bit pack. Every source is
|
||||
* masked and shifted into place and the five pieces are OR'd in the order the
|
||||
* bytes prove. `sra` (not `srl`) fixes ARITHMETIC right shifts, so the shifted
|
||||
* operands are signed — that is why the parameters are declared `int`; the masks
|
||||
* make the values non-negative at run time, but the compiler only sees the type.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* andi v0,a0,0x3 first & 3
|
||||
* sll v0,v0,0x7 << 7
|
||||
* andi a1,a1,0x3 second & 3
|
||||
* sll a1,a1,0x5 << 5
|
||||
* or v0,v0,a1
|
||||
* andi v1,a3,0x100 fourth & 0x100
|
||||
* sra v1,v1,0x4 >> 4
|
||||
* or v0,v0,v1
|
||||
* andi a2,a2,0x3ff third & 0x3ff
|
||||
* sra a2,a2,0x6 >> 6
|
||||
* or v0,v0,a2
|
||||
* andi a3,a3,0x200 fourth & 0x200
|
||||
* sll a3,a3,0x2 << 2
|
||||
* jr ra
|
||||
* or v0,v0,a3 (delay slot) v0 |= a3
|
||||
*
|
||||
* LIMITS: the masks, the shift amounts and the OR order are evidence; the
|
||||
* packed word's meaning and the parameters' meanings are hypotheses.
|
||||
*/
|
||||
|
||||
int func_800F75D0(int first, int second, int third, int fourth) {
|
||||
return ((first & 3) << 7) | ((second & 3) << 5) | ((fourth & 0x100) >> 4) |
|
||||
((third & 0x3ff) >> 6) | ((fourth & 0x200) << 2);
|
||||
}
|
||||
Reference in New Issue
Block a user