diff --git a/README.md b/README.md index 40b34b4..815e4b5 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,27 @@ baseline, not a recovery of the original program structure. ## Toolchain (pinned by byte evidence) -The original compiler was identified in Phase 5 by fingerprinting candidate compilers against the -executable and confirming the result with the SDK's own binary: +The original compiler was re-identified in Phase 6 by comparing candidate compilers against the +SDK's own binaries and the executable: | Role | Component | Evidence | |---|---|---| -| Compiler | **`egcs-2.91.66 19990314` (egcs-1.1.2), target `mips-sony-psx`** — the `CC1PSX.EXE` of PsyQ SDK 4.5 | SDK README banner; the real binary executed and compared; the open `gcc-2.91.66-psx` build is instruction-identical to it across ~990 probe instructions | -| Assembler | **`ASPSX` 2.81** (Sony) | SDK banner; `maspsx` is the open emulator candidate | +| Compiler | **`GNU C 2.7.2.SN32.3.7.0002`** — the `CC1PSX.EXE` of PsyQ SDK 4.0 | the real binary executed and compared; the open `gcc-2.7.2-psx` build is instruction-identical to it across all 21 probe files | +| Assembler | **`ASPSX` 2.56** (Sony) | SDK 4.0 banner; `maspsx` is the open emulator | | Linker / binary tools | GNU `mipsel-none-elf` binutils | Phase 3 local build, recorded in `docs/SETUP.md` | | Splitter | `splat` (+ `spimdisasm`, `rabbitizer`) | Phase 3 | Working compiler invocation (input must be **preprocessed**; `cc1` rejects comments and directives): ``` -gcc-2.91.66-psx/cc1 -quiet -O2 -G0 -mno-split-addresses # then assemble with GNU as +gcc-2.7.2-psx/cc1 -quiet -O2 -G0 # then maspsx, then GNU as ``` -`-mno-split-addresses` is required: the game's code uses the ASPSX-style symbolic-address form that -the assembler expands through `$at`, rather than the compiler's own default split form. The compiler -itself is **open**: `decompals/old-gcc` release 0.17 publishes `gcc-2.91.66-psx`, and no proprietary -SDK is needed for the matching build. The full derivation, including a corrected earlier conclusion, -is in [`docs/PHASE5_TOOLCHAIN_FINGERPRINT.md`](docs/PHASE5_TOOLCHAIN_FINGERPRINT.md). +The macro address form is this compiler's default. Phase 5 had selected `egcs-2.91.66` (PsyQ 4.5), +which matches simple functions but emits a different framed epilogue; the executable's framed code +identifies PsyQ 4.0. The compiler is **open**: `decompals/old-gcc` release 0.17 publishes +`gcc-2.7.2-psx`, and no proprietary SDK is needed for the matching build. The full derivation is in +[`docs/PHASE6_TOOLCHAIN_CORRECTION.md`](docs/PHASE6_TOOLCHAIN_CORRECTION.md). ## Build it from your own disc @@ -151,7 +151,7 @@ own licenses: [`ghidra_psx_ldr`](https://github.com/lab313ru/ghidra_psx_ldr) (lab313ru) — the static oracle and its PSX loader - [PCSX-Redux](https://github.com/grumpycoders/pcsx-redux) — the runtime oracle - GNU binutils — the MIPS assembler, linker and `objcopy` -- The Sony PsyQ SDK — proprietary; **never distributed**, only referenced by checksum. The PsyQ 4.4/4.5/4.6 +- The Sony PsyQ SDK — proprietary; **never distributed**, only referenced by checksum. The PsyQ 4.0/4.1/4.4/4.5/4.6 compiler binaries were obtained from [`mkst/esa`](https://github.com/mkst/esa)'s `psyq-binaries` release and are used only as a verification reference diff --git a/config/regions.tsv b/config/regions.tsv index 6b0eea3..92bc9c3 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -13,8 +13,12 @@ 0x80012780 0x8001278C src/func_80012780.c 0x80017AD4 0x80017AE8 src/func_80017AD4.c 0x80017AE8 0x80017AF8 src/func_80017AE8.c +0x80017DD0 0x80017DF0 src/func_80017DD0.c +0x80024C14 0x80024C34 src/func_80024C14.c 0x80026264 0x80026274 src/func_80026264.c 0x800262E0 0x800262EC src/func_800262E0.c 0x800262EC 0x800262F8 src/func_800262E0.c 0x8002D2A0 0x8002D2BC src/func_8002D2A0.c 0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c +0x80036308 0x80036328 src/func_80036308.c +0x800697A4 0x800697C4 src/func_800697A4.c diff --git a/config/symbols.tsv b/config/symbols.tsv index fd33449..9d7c49d 100644 --- a/config/symbols.tsv +++ b/config/symbols.tsv @@ -18,3 +18,7 @@ _gp 0x80121938 D_80121974 0x80121974 gp D_80121B18 0x80121B18 gp D_80121B14 0x80121B14 gp +func_80024668 0x80024668 +func_800F4098 0x800F4098 +func_800695D8 0x800695D8 +func_8002F404 0x8002F404 diff --git a/docs/MATCHING_CONVENTIONS.md b/docs/MATCHING_CONVENTIONS.md index 5b27bd9..0d900fa 100644 --- a/docs/MATCHING_CONVENTIONS.md +++ b/docs/MATCHING_CONVENTIONS.md @@ -101,7 +101,7 @@ symbol **address** (`la`) reproduce the original's `addiu` form (cookbook findin ## Toolchain stages A C region is built `cpp` → `cc1` → **`maspsx`** → GNU `as` → `ld` → `objcopy`. The maspsx stage -emulates ASPSX 2.81 (the version on the PsyQ 4.5 SDK banner): it emits `.set noreorder` and explicit +emulates ASPSX 2.56 (the version on the PsyQ 4.0 SDK banner): it emits `.set noreorder` and explicit `nop`s so delay-slot scheduling matches the original. `--no-maspsx` disables it for comparison; `--aspsx-version` overrides the pinned version. diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index 96a3d8b..4c8c8cc 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -8,29 +8,34 @@ project; each claim was observed here first. | Role | Identity | Basis | |---|---|---| -| Compiler | `egcs-2.91.66 19990314` (egcs-1.1.2), target `mips-sony-psx` — PsyQ 4.5 `CC1PSX` | SDK README banner; the real binary executed and compared | -| Open substitute | `decompals/old-gcc` 0.17 `gcc-2.91.66-psx` | Instruction-identical to the real `CC1PSX` across ~990 probe instructions | -| Assembler | `ASPSX` 2.81 (Sony) | SDK banner | -| Working invocation | `cc1 -quiet -O2 -G0 -mno-split-addresses`, then GNU `as -march=r3000 -G0` | Byte-identical ranges; `make gate` | +| Compiler | `GNU C 2.7.2.SN32.3.7.0002` — PsyQ 4.0 `CC1PSX` | the real binary executed and compared; its banner | +| Open substitute | `decompals/old-gcc` 0.17 `gcc-2.7.2-psx` | instruction-identical to the real `CC1PSX` 4.0 across 21 probe files | +| Assembler | `ASPSX` 2.56 (Sony) | SDK 4.0 banner (`Psy-Q ASPSX version 2.56`) | +| Working invocation | `cc1 -quiet -O2 -G0`, then maspsx `--aspsx-version=2.56`, then GNU `as -march=r3000 -G0` | Byte-identical ranges; `make gate` | -> **Under review (Phase 6).** This identification does not explain the whole executable. Framed -> functions use a reorder-mode epilogue that `egcs-2.91.66` + maspsx does not produce, and two -> verified functions require opposite assembler scheduling. See finding 11 and -> [PHASE6_FRAMED_BLOCKER.md](PHASE6_FRAMED_BLOCKER.md). +> **Corrected in Phase 6.** Phase 5 selected `egcs-2.91.66` (PsyQ 4.5). That compiler matches simple +> functions but emits a different framed epilogue, and it does not match the executable. The real +> PsyQ 4.0 `CC1PSX` is 2.7.2-based, and `gcc-2.7.2-psx` is instruction-identical to it across all 21 +> probe files. See [PHASE6_TOOLCHAIN_CORRECTION.md](PHASE6_TOOLCHAIN_CORRECTION.md). ## Findings -### 1. `-mno-split-addresses` is mandatory +### 1. The macro address form is this compiler's default -`cc1` has two address-materialisation modes, and only one of them matches. +`cc1` has two address-materialisation modes: -- **`-msplit-addresses`** (the real `CC1PSX` default) emits explicit sequences such as - `lui $3,%hi(sym)` / `lw $2,%lo(sym)($3)`. **Does not match.** -- **`-mno-split-addresses`** emits assembler **macros** (`lw $2,sym`, `sw $4,sym`, `la $2,sym`) that - the assembler expands. **Matches.** +- explicit `lui $3,%hi(sym)` / `lw $2,%lo(sym)($3)` sequences (PsyQ 4.1 and later default), and +- assembler **macros** (`lw $2,sym`, `sw $4,sym`, `la $2,sym`) that the assembler expands. -*Basis:* oracle ranges `0x80085B80`, `0x800F3160` — byte-identical only under `-mno-split-addresses`. -*Limit:* proven for symbol loads, symbol stores and `la`; not yet exhaustively for every construct. +The executable uses the **macro** form. PsyQ 4.0's `CC1PSX` (`gcc-2.7.2-psx`) emits it by default; +later compilers (2.8+, egcs) emit the explicit form and must be forced into macros with +`-mno-split-addresses`. Phase 5 recorded that flag as mandatory only because it was using the wrong +compiler. + +*Basis:* the real PsyQ 4.0 `CC1PSX` and `gcc-2.7.2-psx` emit `sw $4,g_store` / `la $2,sym` by +default and match the executable; the real PsyQ 4.1 `CC1PSX` emits the explicit `$2`-scratch form, +which does not match `0x800F3160`. +*Limit:* proven for symbol loads, symbol stores and `la`. ### 2. Symbol load — the macro expands into the destination register @@ -50,17 +55,17 @@ project; each claim was observed here first. ### 4. `la` differs between assemblers — resolved by maspsx + link-time symbols -GNU `as` and ASPSX 2.81 expand the `la` macro differently: +GNU `as` and ASPSX 2.56 expand the `la` macro differently: - **GNU `as` in reorder mode** expands `la rt,sym` with **`ori`** and an unadjusted `%hi` (`lui rt,0x8010` / `ori rt,rt,0xf354`), and it also **reorders** the following store into a jump's delay slot. -- **ASPSX 2.81** uses **`addiu`** with a sign-adjusted `%hi` (`lui rt,0x8011` / `addiu rt,rt,-3244`) +- **ASPSX 2.56** uses **`addiu`** with a sign-adjusted `%hi` (`lui rt,0x8011` / `addiu rt,rt,-3244`) and does not reorder. The fix has two parts, both now in the harness: -1. Run `maspsx --aspsx-version=2.81` between `cc1` and GNU `as`. maspsx emits `.set noreorder` and +1. Run `maspsx --aspsx-version=2.56` between `cc1` and GNU `as`. maspsx emits `.set noreorder` and explicit `nop`s, so the delay-slot scheduling matches ASPSX. 2. Leave symbols **undefined** at assembly time and resolve them with the **linker** (`ld --defsym`). GNU `as` then emits `lui %hi` + `addiu %lo` relocations, and the linker applies the HI16 carry @@ -133,27 +138,23 @@ over 1,286 distinct addresses. See [PHASE6_SMALL_DATA.md](PHASE6_SMALL_DATA.md). *Limit:* the numeric `-G` threshold is **not recoverable** from the code (object sizes are unknown); `-mgpopt`/`-mno-gpopt` produce identical `cc1` output for these functions. -### 11. Framed epilogues use reorder-mode scheduling — unresolved +### 11. Framed epilogues use reorder-mode scheduling -- **322** framed functions end `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / `nop` (shape A). -- `egcs-2.91.66` + `-mno-split-addresses` + maspsx produces `lw ra,off(sp)` / `nop` / `jr ra` / - `addiu sp,sp,N` (shape B). The **real** PsyQ 4.5 `CC1PSX` and the **real** `ASPSX` 2.81 also - produce shape B. -- The `gcc-2.6.0-psx` / `2.6.3-psx` / `2.7.2-psx` / `2.7.2-cdk` builds emit the epilogue in reorder - mode and reproduce shape A byte-for-byte (test case `0x80024C14`). -- Separately, `q_sym_store` (`0x800F3160`) and `func_8002D2BC` (`0x8002D2BC`) require **opposite** - assembler scheduling, so no single assembler/maspsx configuration reproduces both. +Framed functions end `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / `nop` (322 of them; only 13 sites +in the CRT region use the other order). PsyQ 4.0's `CC1PSX` emits this epilogue in **reorder** mode +and the assembler schedules it; `egcs-2.91.66` emits `.set noreorder` with the stack restore in the +jump delay slot and does not match. This was the discriminator that corrected the compiler +identification. -*Basis:* the epilogue census (322 vs 13), the `0x80024C14` compiler matrix, and real-`ASPSX` -assembly of the cc1 output. -*Limit:* **open** — the compiler identity and the assembler model need revision before framed -functions can be matched. See [PHASE6_FRAMED_BLOCKER.md](PHASE6_FRAMED_BLOCKER.md). +*Basis:* the epilogue census (322 vs 13) and the `0x80024C14` compiler matrix; four framed functions +are now registered and byte-identical. +*Limit:* one bounded anomaly remains — `0x800F3160`, a 12-byte symbol store whose store-in-delay-slot +scheduling no tested assembler reproduces. See [PHASE6_FRAMED_BLOCKER.md](PHASE6_FRAMED_BLOCKER.md). ## Open questions -- **Framed-function epilogue and assembler scheduling are unresolved** (finding 11): the original - compiler is a 2.6/2.7-family build, or egcs-2.91.66 with an unidentified flag; and - `q_sym_store`/`func_8002D2BC` conflict under every tested assembler configuration. +- One bounded scheduling anomaly: `0x800F3160` (`lui at` / `jr ra` / `sw a0,off(at)`) is 12 bytes, + but PsyQ 4.0 `CC1PSX` + ASPSX 2.56 gives 16 (`sw` before the jump). It is not registered. - The exact `-G` small-data threshold is not recoverable from the code; the per-symbol `gp` form is reconstructed from the original's accesses instead (finding 10). - Whether `-mgpopt` was passed is not observable: it does not change `cc1` output for the cases diff --git a/docs/PHASE6_FRAMED_BLOCKER.md b/docs/PHASE6_FRAMED_BLOCKER.md index e33edf3..c04be13 100644 --- a/docs/PHASE6_FRAMED_BLOCKER.md +++ b/docs/PHASE6_FRAMED_BLOCKER.md @@ -1,6 +1,11 @@ # Phase 6 — Framed-Function and Assembler-Scheduling Blocker -**Scope:** P6-T6 (the matching batch). **Status: OPEN — recorded, not resolved.** This blocks the +> **RESOLVED (2026-09-23).** The compiler was re-identified as **PsyQ 4.0** (`gcc-2.7.2-psx`), which +> reproduces the framed epilogue; the framed shape is now matched. See +> [PHASE6_TOOLCHAIN_CORRECTION.md](PHASE6_TOOLCHAIN_CORRECTION.md). The record below is kept as the +> evidence that drove the correction; one bounded anomaly (`0x800F3160`) remains open. + +**Scope:** P6-T6 (the matching batch). **Original status: OPEN — recorded, not resolved.** This blocks the "call with a frame" shape and any framed function, and it puts the Phase 5 compiler identification back in question. It does **not** affect the 8 already-registered matches. diff --git a/docs/PHASE6_TOOLCHAIN_CORRECTION.md b/docs/PHASE6_TOOLCHAIN_CORRECTION.md new file mode 100644 index 0000000..7e2ad6d --- /dev/null +++ b/docs/PHASE6_TOOLCHAIN_CORRECTION.md @@ -0,0 +1,76 @@ +# Phase 6 — Toolchain Correction: PsyQ 4.0, not PsyQ 4.5 + +**Scope:** P6-T6 (option A — re-identify the compiler). **Status: complete.** This supersedes the +Phase 5 compiler identification in `docs/PHASE5_TOOLCHAIN_FINGERPRINT.md`. + +## Result + +| Role | Identity | Basis | +|---|---|---| +| Compiler | **PsyQ 4.0 `CC1PSX`**, banner `GNU C 2.7.2.SN32.3.7.0002` | the real binary executed and compared | +| Open substitute | **`gcc-2.7.2-psx`** | instruction-identical to the real `CC1PSX` 4.0 across **all 21** probe files | +| Assembler | **ASPSX 2.56** (SDK 4.0 banner) | `Psy-Q ASPSX version 2.56` | +| maspsx pin | `--aspsx-version=2.56` | matches the SDK 4.0 assembler version | +| Flags | `-quiet -O2 -G0` | the macro address form is this compiler's default | + +The loader's `PsyQ Version = 4.5.0` describes the **runtime library** version, not the compiler that +built the code. + +## How Phase 5 reached the wrong answer + +- Phase 5 compared the **real PsyQ 4.5 `CC1PSX`** against the open `gcc-2.91.66-psx` and found them + instruction-identical. That is true — for SDK 4.5. It does not show that 4.5 built the game. +- Phase 5's byte-identical oracles (struct load/store, symbol load/store) are reproduced by **all ten** + candidates; the record explicitly says so. They cannot discriminate. +- Phase 5's `-mno-split-addresses` conclusion was an artifact of using a compiler (2.8+) whose default + is the explicit split form. The correct compiler emits the macro form by default and has no such flag. + +## Evidence + +1. **Framed epilogue census.** 322 game functions end `lw ra,off(sp)` / `addiu sp,sp,N` / `jr ra` / + `nop`; only 13 sites (CRT region) use the other order. `egcs-2.91.66` emits `.set noreorder` with + the stack restore in the jump delay slot, i.e. the minority order. + +2. **Compiler matrix on the framed wrapper `0x80024C14`:** + + | Compiler | Result | + |---|---| + | `gcc-2.6.0-psx`, `gcc-2.6.3-psx`, `gcc-2.7.2-psx`, `gcc-2.7.2-cdk` | **MATCH** | + | `gcc-2.8.0-psx`, `gcc-2.8.1-psx`, `gcc-2.91.66-psx`, `gcc-2.95.2-psx` | DIFF | + | real PsyQ 4.0 `CC1PSX` | emits the same shape as the 2.7.2 family | + +3. **Real SDK comparison.** With normalization, the real `CC1PSX` 4.0 output was compared to every + open candidate across 21 probe files: + + | Candidate | Probe files identical to real `CC1PSX` 4.0 | + |---|---| + | **`gcc-2.7.2-psx`** | **21 / 21** | + | `gcc-2.6.0-psx`, `gcc-2.6.3-psx` | 6 / 21 | + | `gcc-2.7.2-cdk`, `gcc-2.8.0-psx`, `gcc-2.8.1-psx` | 1 / 21 | + | `gcc-2.91.66-psx`, `gcc-2.95.2-psx` | 0 / 21 | + +4. **Symbol form.** Real `CC1PSX` 4.0 emits the macro form by default (`sw $4,g_store`, `la $2,sym`); + real `CC1PSX` 4.1 emits the explicit `$2`-scratch form. The executable uses the macro form, so the + compiler is 4.0 (or 4.5), and the framed epilogue rules out 4.5. + +5. **Assembler versions.** SDK 4.0 ships ASPSX **2.56**, SDK 4.1 ships 2.67, SDK 4.5 ships 2.81. + +## Corrected harness + +`tools/sf3_match` now defaults to `tools/old-gcc/gcc-2.7.2-psx/cc1` with `-quiet -O2 -G0` and +`--aspsx-version=2.56`. All **12 registered regions** still pass `sf3_match range` and `make gate` +(`c_regions=12`, 0 differing bytes, SHA-1 `e173426c157384ebf1b6caf8c6fea18a85a14af9`), and the framed +shape is now matchable. + +## Residual anomaly (bounded, unresolved) + +`0x800F3160` is a 12-byte symbol store whose original bytes are `lui at,0x8014` / `jr ra` / +`sw a0,11996(at)` — the store is in the jump delay slot. PsyQ 4.0 `CC1PSX` + ASPSX 2.56 produces the +16-byte form with the store **before** the jump. No tested (compiler, assembler) pair reproduces it: +ASPSX 4.1 schedules the store but uses `$2` rather than `$at`; GNU `as` reorder schedules it with +`$at` but breaks other functions. It is **not registered** and is recorded as a bounded open item in +the cookbook. + +## Rules + +No rules were added. diff --git a/docs/SETUP.md b/docs/SETUP.md index 23b3935..59b3210 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -165,3 +165,25 @@ Both components stay ignored (`tools/psyq/` and `tools/wibo/`). instruction output across every probe tested (~990 instructions, twelve probe files). The open `gcc-2.91.66-psx` is therefore the working compiler; the proprietary binary is only a verification reference and is not required for the matching build. + +## Phase 6 toolchain correction and the PsyQ 4.0/4.1 compilers (2026-09-23) + +The framed-function work in P6-T6 showed the Phase 5 compiler identification did not explain the +executable: framed functions use a reorder-mode epilogue that `egcs-2.91.66` (PsyQ 4.5) does not +emit. P6-T6 therefore obtained the **PsyQ 4.0 and 4.1** compiler sets to compare compiler and +assembler versions directly. + +| Component | Identity | sha256 | +|---|---|---| +| PsyQ SDK 4.0 compilers | `CC1PSX.EXE` (banner `GNU C 2.7.2.SN32.3.7.0002`), `ASPSX.EXE` (banner `Psy-Q ASPSX version 2.56`) | `f25a4f6f044eb1b344bbbd3291aa9a4fc1a1124fc637eae9522c0a117d940e28` | +| PsyQ SDK 4.1 compilers | `CC1PSX.EXE` (`SN32.3.7.0004`), `ASPSX.EXE` (`SDevTC ASPSX version 2.67`) | `2a2650ceb5eaa73fdc581bec4a85ccaa6ff9eeea8a4810be25a638f3cd2ebac4` | + +- Source: `https://github.com/mkst/esa/releases/download/psyq-binaries/psyq4.0.tar.gz` and + `psyq4.1.tar.gz`, fetched 2026-09-23. Proprietary Sony material; kept ignored under `tools/psyq/`. +- **Result:** the executable's framed epilogues and symbol forms identify **PsyQ 4.0**. The real + `CC1PSX` 4.0 is instruction-identical to the open `gcc-2.7.2-psx` across **all 21 probe files**; + `gcc-2.7.2-cdk`, `gcc-2.8.x`, `gcc-2.91.66-psx` and `gcc-2.95.2-psx` match far fewer. The working + toolchain is `gcc-2.7.2-psx` + maspsx `--aspsx-version=2.56` + GNU `as`. +- Invocation: `tools/wibo/wibo tools/psyq/psyq4.0/psyq4.0/CC1PSX.EXE -quiet -O2 -G0 -o `. +- The SDK 4.5 banner (`egcs-2.91.66`, ASPSX 2.81) remains recorded above; it is a different SDK from + the one that built the game. See [PHASE6_TOOLCHAIN_CORRECTION.md](PHASE6_TOOLCHAIN_CORRECTION.md). diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index 7bb1866..fae139c 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -12,7 +12,7 @@ - [x] **P6-T4 — `-G` small-data threshold from byte evidence** (complete) - [x] **Rules check** — re-read `AGENTS.md` mandatory behavior after P6-T4 and stated the required continuation notice. - [x] **P6-T5 — Evidence-graded function-boundary inventory** (complete) -- [~] **P6-T6 — First matching batch, with duplicate sharing** — **partial**: 8 regions registered and duplicate sharing demonstrated; the "call with a frame" shape is blocked by `docs/PHASE6_FRAMED_BLOCKER.md` +- [x] **P6-T6 — First matching batch, with duplicate sharing** (complete; the framed shape was unblocked by correcting the compiler identification) - [ ] P6-T7 — Cookbook, conventions, verification record, and phase gate ## P6-T1 — Baseline revalidation (2026-09-23) @@ -219,3 +219,35 @@ function). The blocker is recorded in `docs/PHASE6_FRAMED_BLOCKER.md`: **Not claimed:** no framed function is matched; the Phase 5 compiler identification is recorded as insufficient pending developer input. + +## P6-T6 (continued) — Toolchain correction and the framed batch (2026-09-23) + +On developer direction ("attempt A"), the compiler was re-identified. **The original compiler is +PsyQ 4.0, not PsyQ 4.5.** + +- The real PsyQ 4.0 `CC1PSX` reports `GNU C 2.7.2.SN32.3.7.0002`; the open `gcc-2.7.2-psx` is + instruction-identical to it across **all 21 probe files** (`gcc-2.6.x` match 6, `gcc-2.7.2-cdk`/2.8.x + match 1, `egcs-2.91.66`/2.95.2 match 0). +- SDK 4.0 ships **ASPSX 2.56** (4.1 → 2.67, 4.5 → 2.81); the harness now pins `--aspsx-version=2.56`. +- The `-mno-split-addresses` requirement recorded in Phase 5 was an artifact of the wrong compiler; + PsyQ 4.0 emits the macro address form by default and has no such flag. +- Full evidence: `docs/PHASE6_TOOLCHAIN_CORRECTION.md`; SDK provenance and checksums in `docs/SETUP.md`. + +**Delivered:** four framed functions registered — `func_80017DD0`, `func_80024C14`, `func_80036308`, +`func_800697A4` — taking the registry to **12 regions / 11 distinct functions** across three shapes +(leaf getter/setter, `la`/`gp`-relative, and call with a frame). + +**Verification:** + +| Check | Result | +|---|---| +| `sf3_match range` for all 12 regions with the corrected compiler | all `MATCH` | +| `make gate` | `c_regions=12`, 0 differing bytes, SHA-1 `e173426c…`, exit 0 | +| Synthetic suite | 86 tests pass | +| Real-compiler agreement | `gcc-2.7.2-psx` = real PsyQ 4.0 `CC1PSX` on 21/21 probe files | + +**Residual open item:** `0x800F3160` (12-byte symbol store with the store in the delay slot) is not +reproduced by any tested (compiler, assembler) pair; it is not registered and is recorded in the +cookbook. + +**Rules check — re-read complete. Continuing with P6-T7.** diff --git a/src/func_80017DD0.c b/src/func_80017DD0.c new file mode 100644 index 0000000..a1fabe5 --- /dev/null +++ b/src/func_80017DD0.c @@ -0,0 +1,25 @@ +/* + * func_80017DD0 — 32 bytes at 0x80017DD0..0x80017DF0 + * + * Byte-identical reconstruction of a non-leaf wrapper that calls one function + * with a constant zero argument. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw ra,16(sp) + * jal func_800F4098 + * move a0,zero (argument set in the call delay slot) + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * LIMITS: the function name and the callee's name are address placeholders + * reconstructed from the disassembly; only the compiled bytes are evidence. + */ + +extern void func_800F4098(int); + +void func_80017DD0(void) { + func_800F4098(0); +} diff --git a/src/func_80024C14.c b/src/func_80024C14.c new file mode 100644 index 0000000..ab16bbb --- /dev/null +++ b/src/func_80024C14.c @@ -0,0 +1,31 @@ +/* + * func_80024C14 — 32 bytes at 0x80024C14..0x80024C34 + * + * Byte-identical reconstruction of a non-leaf wrapper: it builds a frame, calls + * one function with no arguments, and returns. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw ra,16(sp) + * jal func_80024668 + * nop + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * This is one of the framed functions that established the corrected + * toolchain: PsyQ 4.0's `CC1PSX` (`GNU C 2.7.2.SN32.3.7.0002`, open equivalent + * `gcc-2.7.2-psx`) emits this epilogue in reorder mode, which the assembler + * schedules to `lw ra` / `addiu sp,sp,24` / `jr ra` / `nop`. `egcs-2.91.66` + * (Phase 5's earlier selection) emits a different epilogue and does not match. + * + * LIMITS: the function name and the callee's name are address placeholders + * reconstructed from the disassembly; only the compiled bytes are evidence. + */ + +extern void func_80024668(void); + +void func_80024C14(void) { + func_80024668(); +} diff --git a/src/func_80036308.c b/src/func_80036308.c new file mode 100644 index 0000000..f84efa5 --- /dev/null +++ b/src/func_80036308.c @@ -0,0 +1,26 @@ +/* + * func_80036308 — 32 bytes at 0x80036308..0x80036328 + * + * Byte-identical reconstruction of a non-leaf wrapper that forwards two + * arguments, masking the second to an unsigned byte in the call delay slot. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw ra,16(sp) + * jal func_8002F404 + * andi a1,a1,0x00ff (unsigned char argument, call delay slot) + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * LIMITS: the function name, the callee's name and the parameter types are + * hypotheses reconstructed from the disassembly; only the compiled bytes are + * evidence. The `andi` mask is the target's default unsigned-`char` ABI. + */ + +extern void func_8002F404(int, unsigned char); + +void func_80036308(int x, unsigned char y) { + func_8002F404(x, y); +} diff --git a/src/func_800697A4.c b/src/func_800697A4.c new file mode 100644 index 0000000..4d1a2ef --- /dev/null +++ b/src/func_800697A4.c @@ -0,0 +1,26 @@ +/* + * func_800697A4 — 32 bytes at 0x800697A4..0x800697C4 + * + * Byte-identical reconstruction of a non-leaf wrapper that forwards its first + * argument and passes zero as the second. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw ra,16(sp) + * jal func_800695D8 + * move a1,zero (second argument set in the call delay slot) + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * LIMITS: the function name, the callee's name and the parameter types are + * hypotheses reconstructed from the disassembly; only the compiled bytes are + * evidence. + */ + +extern void func_800695D8(int, int); + +void func_800697A4(int x) { + func_800695D8(x, 0); +} diff --git a/tools/sf3_match b/tools/sf3_match index 5cae6b8..d0749b8 100755 --- a/tools/sf3_match +++ b/tools/sf3_match @@ -32,10 +32,12 @@ in its own source or output and refuses to write into an existing directory. Exit codes: 0 success/match, 1 mismatch, 2 usage or environment error. -Toolchain (identified in Phase 5): egcs-2.91.66 (egcs-1.1.2), target -mips-sony-psx -- PsyQ 4.5's CC1PSX. The open decompals/old-gcc -`gcc-2.91.66-psx` build produces instruction-identical output. Required flags: --O2 -G0 -mno-split-addresses. Input to cc1 must be preprocessed. +Toolchain (identified in Phase 6, correcting Phase 5): PsyQ 4.0's `CC1PSX` reports +`GNU C 2.7.2.SN32.3.7.0002`. The open decompals/old-gcc `gcc-2.7.2-psx` build is +instruction-identical to it across 21 probe files. Flags: `-O2 -G0` (the macro +address form is the default; this compiler has no `-mno-split-addresses`). +Input to cc1 must be preprocessed. The SDK 4.0 assembler is ASPSX 2.56, which +maspsx emulates. """ from __future__ import annotations @@ -53,18 +55,18 @@ from typing import Sequence REPO_ROOT = Path(__file__).resolve().parent.parent -DEFAULT_CC1 = REPO_ROOT / "tools/old-gcc/gcc-2.91.66-psx/cc1" +DEFAULT_CC1 = REPO_ROOT / "tools/old-gcc/gcc-2.7.2-psx/cc1" _BINUTILS = REPO_ROOT / "tools/mipsel-none-elf-binutils/prefix/usr/bin" DEFAULT_AS = _BINUTILS / "mipsel-none-elf-as" DEFAULT_LD = _BINUTILS / "mipsel-none-elf-ld" DEFAULT_OBJCOPY = _BINUTILS / "mipsel-none-elf-objcopy" DEFAULT_CPP_FLAGS = ["-E", "-P", "-undef"] -DEFAULT_CC1_FLAGS = ["-quiet", "-O2", "-G0", "-mno-split-addresses"] +DEFAULT_CC1_FLAGS = ["-quiet", "-O2", "-G0"] DEFAULT_AS_FLAGS = ["-march=r3000", "-G0"] DEFAULT_MASPSX = REPO_ROOT / "tools/maspsx/maspsx.py" -# The PsyQ 4.5 SDK banner reports `SDevTC ASPSX version 2.81`. -DEFAULT_ASPSX_VERSION = "2.81" +# The PsyQ 4.0 SDK banner reports `Psy-Q ASPSX version 2.56`. +DEFAULT_ASPSX_VERSION = "2.56" EXE_MAGIC = b"PS-X EXE" HEADER_SIZE = 0x800 diff --git a/tools/tests/test_sf3_match.py b/tools/tests/test_sf3_match.py index 1415f0d..46cd178 100644 --- a/tools/tests/test_sf3_match.py +++ b/tools/tests/test_sf3_match.py @@ -433,7 +433,7 @@ class EndToEndTests(unittest.TestCase): src.write_text(source, encoding="ascii") # Reference: the source compiled directly with the full -O0 flag set. ref_tools = self._toolchain( - cc1_flags=["-quiet", "-O0", "-G0", "-mno-split-addresses"] + cc1_flags=["-quiet", "-O0", "-G0"] ) ref_obj = root / "ref.o" sf3_match.compile_c(src, ref_obj, root / "refwork", ref_tools)