mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
2f709ee542
- Config loader (recompiler/src/config_loader.{h,cpp}): single source of
truth for both BIOS and game runtime settings (debug_port, window_title,
memcard_dir, disc path). psxrecomp-bios and psxrecomp-game both accept
--config <toml>; runtime takes --game <toml> (no more CLI overrides).
- runtime/src/main.cpp: replaced --disc/--memcard-dir/--game-root CLI
overrides with --game <toml>; game.toml is the single source of truth.
Discless launch (no args) goes to BIOS shell.
- runtime/src/dma.c: fixed update_master_irq() re-asserting I_STAT bit 3
on every DICR read. BIOS shell DMA handler at 0x80058628 only clears
DICR bit 27 (ch3 flag), so level-sensitive re-assert pinned the kernel
in the exception loop. Bit 3 is now edge-triggered (one assert per DMA
completion). Discless boot now reaches MAIN MENU / MEMORY CARD / CD
PLAYER. With-disc Tomba boots through to Whoopee Camp splash.
- Audit tooling: codegen_audit.py (BIOS), codegen_audit_game.py (game),
audit_config.py (config loader for tools), verify_audit_schema.py.
Replaces older tools/_codegen_audit.py probe.
- Docs: CURRENT_STATE.md (project state), UNIFICATION_PLAN.md (Phase C
translator merge plan), docs/config_schema.md (TOML schema reference),
docs/audit_inventory.md (audit tier inventory).
- .gitignore: tools/_*.py convention for ad-hoc scratch probes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
253 lines
12 KiB
Markdown
253 lines
12 KiB
Markdown
# Audit Inventory — psxrecomp-v4
|
|
|
|
Baseline as of 2026-05-12. Captures what audit / test tooling exists, what
|
|
scope it covers, what each tool currently reports, and where the gaps are.
|
|
|
|
This document is the input for Phase B (codegen-audit expansion) and
|
|
Phase C (L2-semantic execute tier).
|
|
|
|
## Tier structure
|
|
|
|
```
|
|
Layer 5 Runtime behavioral differential vs Beetle (continuous RAM diff) NONE
|
|
Layer 4 Hardware-sim conformance (SIO/DMA/MDEC/GTE vs no$psx) PARTIAL
|
|
Layer 3 Codegen / discovery audits (static analysis of generated C) PARTIAL
|
|
Layer 2b L2-semantic — execute emitted code and verify register state NONE
|
|
Layer 2a L2-structural — emit pattern matching on generated C HAVE
|
|
Layer 1 Per-instruction decode correctness vs Rabbitizer HAVE
|
|
Layer 0 Build & link integrity HAVE
|
|
```
|
|
|
|
## Tools inventory
|
|
|
|
### Layer 1 — Decode conformance
|
|
|
|
**`recompiler/tests/l1_decoder_test.cpp`** (built: `recompiler/build/l1_decoder_test.exe`)
|
|
- Scope: BIOS only. Code regions Boot `0x00000-0x0DC60`, Kernel
|
|
`0x10000-0x16760`, Shell `0x18000-0x42800`.
|
|
- Compares `MipsDecoder::decode()` mnemonic vs Rabbitizer for every word
|
|
in those regions.
|
|
- Buckets mismatches by `oracle_mnem -> our_mnem`.
|
|
|
|
**Baseline result:** widespread mismatches. Notable buckets:
|
|
| Count | Rabbitizer says | We say | First addr | Comment |
|
|
|------:|-----------------|------------|--------------|---------|
|
|
| 181 | `cache` | `???` | 0xBFC04300 | **Real gap.** CACHE (op 0x2F) is an R3000A instruction we don't decode. |
|
|
| 31 | `mvmva` | `gte` | 0xBFC34FF8 | Granularity. We map all GTE cmds to generic `gte`. |
|
|
| 13 | `rtpt` | `gte` | 0xBFC35DF0 | Same. |
|
|
| 13 | `nclip` | `gte` | 0xBFC361B4 | Same. |
|
|
| 11 | `tge` | `special?` | 0xBFC10190 | R4000 trap — likely data-in-code or kernel trap encoding. |
|
|
| 10 | `dadd` | `special?` | 0xBFC101D4 | 64-bit (R4000+), likely data. |
|
|
| 9 | `rtps` | `gte` | 0xBFC35DAC | GTE granularity. |
|
|
| ~ | `mov.d, mtc1, lwc1, add.d` | `???` | 0xBFC02358 | FP — R3000A has no FPU; likely data-in-code in Boot region. |
|
|
|
|
Two distinct classes:
|
|
- **Real decode gap:** `cache` (op 0x2F). 181 sites. Decoder needs a case.
|
|
- **Granularity gap:** GTE sub-mnemonics. Not a correctness bug — our
|
|
emit treats all GTE cmds via `gte_execute`. But audit clarity benefits
|
|
from matching Rabbitizer's specificity.
|
|
- **False positives (data-in-code):** dadd, dmult, dsll32, mov.d, mtc1,
|
|
c.eq.d, etc. These are R4000+ / FPU instructions that don't exist on
|
|
R3000A. They appear because we scan the entire region without skipping
|
|
inline data or jump tables. Filter improvement, not a decoder bug.
|
|
|
|
### Layer 2a — Structural emit conformance
|
|
|
|
**`recompiler/tests/l2_structural_test.cpp`** (built: `l2_structural_test.exe`)
|
|
- For each MIPS instruction form, build word, translate via
|
|
`StrictTranslator`, verify emitted C contains expected patterns
|
|
(e.g., `gpr[2]`, `gpr[4]`, `<< 1u`).
|
|
- Snesrecomp-style. Note in source: "does NOT verify semantic
|
|
correctness (that requires L2-semantic with actual execution)."
|
|
|
|
**Baseline result:** `42/42 ok`.
|
|
|
|
**Gap:** 42 forms / ~150 R3000A instructions ≈ 28% coverage. No
|
|
coverage for: load-delay-slot semantics, mult/div HI-LO timing, LWL/
|
|
LWR/SWL/SWR unaligned partial-word, JALR same-source/dest, shift-by-32
|
|
edge case, COP0 RFE / SR-bit semantics.
|
|
|
|
### Layer 2b — Semantic execute conformance
|
|
|
|
**Status:** does not exist.
|
|
|
|
The gap. To know "does our emit *do* the right thing when run", we'd:
|
|
1. Build a tiny test harness that takes MIPS bytes + initial CPUState,
|
|
runs the recompiler → C → execute, returns final CPUState.
|
|
2. For each instruction, supply known input registers and assert output
|
|
matches the R3000A spec.
|
|
3. Cross-check against mednafen-PSX's CPU as a reference impl.
|
|
|
|
This is Phase C.
|
|
|
|
### Layer 3 — Codegen / discovery audits
|
|
|
|
**`tools/_codegen_audit.py`** (BIOS only)
|
|
- 6 passes against `generated/SCPH1001_full.c` + `_dispatch.c`:
|
|
1. Predicate-init audit (verifies 2026-05-04 fix).
|
|
2. Direct `psx_dispatch(cpu, 0xXXXX)` targets in dispatch table.
|
|
3. Indirect `psx_dispatch(cpu, gpr[N])` distribution.
|
|
4. Tail-call `cpu->pc = ...; return;` targets in dispatch table.
|
|
5. Chained-branch-with-delay-slot-label population.
|
|
6. Highest-frequency missing dispatch targets.
|
|
|
|
**Baseline result:**
|
|
- Predicate decl audit: `buggy=0 zero_init=6104 assigns=6104` ✓
|
|
- Direct psx_dispatch: 796 unique, 0 missing ✓
|
|
- Indirect sites: 88 (across many registers)
|
|
- Tail-call missing: **1 site → `0x00000CF0`** — the install-at-runtime
|
|
SIO data-byte handler stub. Expected, handled by dirty-RAM
|
|
interpreter. Not a regression.
|
|
- Chained-branch with delay-slot label: 27 sites, all protected ✓
|
|
|
|
**`tools/gte_audit.py`** (BIOS only)
|
|
- Scans ROM for COP2/GTE instructions, compares against generated code.
|
|
|
|
**Baseline result:** "missing" hits in `0xBFC75xxx..0xBFC7Fxxx`. These
|
|
are in the BIOS DATA region (code ends at `0xBFC42800`). All false
|
|
positives from scanning data bytes that happen to decode as COP2 ops.
|
|
Need to filter by code region.
|
|
|
|
**`tools/scan_branch_delay_hazards.py`** (BIOS only)
|
|
- For every conditional branch in walked code, checks whether the
|
|
delay slot writes to the branch's rs/rt operand.
|
|
- Verifies `strict_translator.cpp` uses the snapshot pattern.
|
|
|
|
**Baseline result:**
|
|
- Branches scanned: 4617
|
|
- Delay slot writes branch operand: 441 (intrinsic, informational)
|
|
- **Incorrect in current translator: 0 ✓**
|
|
- Delay-slot unreachable in walk: 0 ✓
|
|
|
|
**`tools/check_dispatch.py`** — one-off probe with hardcoded address
|
|
list. Not generally useful. **Skip from active inventory.**
|
|
|
|
### Layer 4 — Hardware-sim conformance
|
|
|
|
**`runtime/tests/test_sio_card_protocol.c`**
|
|
- Drives `sio.c` TX/RX state machine through a 137-byte memcard READ
|
|
command, asserts each RX byte matches no$psx.
|
|
- Not run as part of this Phase A pass (built but not invoked here).
|
|
|
|
**Gap:** other MMIO regions — DMA, MDEC, GPU command stream, timers,
|
|
IRQ controller — have no protocol conformance tests.
|
|
|
|
### Layer 5 — Runtime differential
|
|
|
|
Does not exist. Both psx-runtime (port 4370) and psx-beetle (port 4380)
|
|
speak the same wire protocol, so the infrastructure for paired wtrace
|
|
queries is present; what's missing is the diff-and-cluster tool itself.
|
|
|
|
## Tool generalization status
|
|
|
|
**All five Layer 1-3 tools are hardcoded to BIOS paths.** They were
|
|
written during Phase 1-3 BIOS bring-up and have not been generalized
|
|
to game code. For Phase B, generalize them via:
|
|
- Argument: `--full-c PATH --dispatch-c PATH --rom PATH` (or read from
|
|
`game.toml`-style config).
|
|
- Run each tool once for BIOS, once for Tomba.
|
|
|
|
## Phase A findings — what to do with them
|
|
|
|
| Finding | Class | Priority | Phase |
|
|
|---------|-------|---------:|-------|
|
|
| L1: `cache` (op 0x2F) not decoded — 181 sites | Real decoder gap | High | Phase B |
|
|
| L1: GTE sub-mnemonic granularity | Audit clarity | Low | Phase B |
|
|
| L1: FPU / 64-bit FPs in data regions | Audit filter | Low | Phase B |
|
|
| L2: 42 forms / ~150 instructions = 28% coverage | Test gap | High | Phase C |
|
|
| L2-semantic tier does not exist | Architectural | High | Phase C |
|
|
| All audits BIOS-only | Tool scope | High | Phase B |
|
|
| gte_audit.py false-positives in data region | Audit filter | Low | Phase B |
|
|
| Tail-call miss at 0x00000CF0 | Expected stub | None | — |
|
|
|
|
## Progress log
|
|
|
|
### Phase B2 (2026-05-12) — First audit pass against Tomba
|
|
|
|
Ran all three Python audits against Tomba's `SCUS_942.36`. Mixed
|
|
outcome — tool gaps surfaced and real bugs surfaced.
|
|
|
|
**Initial run findings:**
|
|
- `codegen_audit.py` returned all zeros — its regexes are BIOS-emit-
|
|
specific and don't match the game `full_function_emitter` shape
|
|
(direct `func_X(cpu)` calls, switch-based dispatch, `_bc_X` branch
|
|
conds). **Required a sibling tool.**
|
|
- `gte_audit.py` reported **29 findings** in Tomba text segment
|
|
(8 GTE + 21 LWC2/SWC2). See TombaRecomp ISSUES Issue #2.
|
|
- `scan_branch_delay_hazards.py` correctly skipped walker (no
|
|
function_starts file for Tomba) and ran translator-source check.
|
|
|
|
**`codegen_audit_game.py` created** (per the principle: separate tool
|
|
sharing patterns only at the margins). Audits the 6 passes that
|
|
matter for game-emit style. After fixing my dispatch-table regex
|
|
(Tomba uses `case 0xADDRu: func_X(cpu); return 1;` not BIOS's array
|
|
form) and adding code-region filtering for `call_by_address` literals
|
|
(331 of 333 are RAM-loaded overlays handled by `dirty_ram_dispatch`,
|
|
expected):
|
|
|
|
| Pass | Result |
|
|
|------|--------|
|
|
| 1. Direct func_X(cpu) calls | 2,973 calls, 840 unique → **all resolve** ✓ |
|
|
| 2. Literal call_by_address | 333 unique → **2 in-code misses** ⚠ |
|
|
| 3. Indirect call_by_address | 360 sites (informational) |
|
|
| 4. Tail-call cpu->pc=X | **0 sites** (game emit doesn't use this idiom) ✓ |
|
|
| 5. Branch-cond declarations | 6,708 (informational) |
|
|
| 6. goto block_X resolution | 20,172 sites, **all resolve** ✓ |
|
|
|
|
**Real bug surfaced: Issue #5 — mid-function split targets not
|
|
registered in dispatch table.** Three emit sites in
|
|
`code_generator.cpp` (lines 970, 981, 998) emit `call_by_address` for
|
|
mid-func continuations without calling `register_cross_function_target`.
|
|
This is the same bug class as today's jump-table fix. Tomba has 2
|
|
manifestations (`0x800905E4`, `0x80090600` inside `func_800905DC`).
|
|
**B2's first pass against Tomba found a real recompiler bug.**
|
|
|
|
### Phase B0 (2026-05-12) — Audit-config schema
|
|
|
|
Defined a TOML schema for audit configs. Both BIOS and games consume
|
|
the same shape; field names are tolerant (`[program]` or `[game]`,
|
|
`rom` or `exe`).
|
|
|
|
Artifacts:
|
|
- `tools/audit_config.py` — shared loader.
|
|
- `tools/verify_audit_schema.py` — schema validator.
|
|
- `bios/SCPH1001.toml` — BIOS config (3 regions, 2 address remaps).
|
|
- `TombaRecomp/game.toml` — extended with `[audit]` block (1 region,
|
|
0 remaps).
|
|
|
|
### Phase B1 (2026-05-12) — Python audit generalization
|
|
|
|
Migrated three Python audits to consume the audit-config schema. All
|
|
three pass BIOS regression with output matching the Phase A baseline.
|
|
|
|
| Tool | Status | BIOS regression |
|
|
|-------------------------------|----------|----------------------------------------------------------------|
|
|
| `tools/codegen_audit.py` | migrated | identical to Phase A (CLEAN, 0 real findings) |
|
|
| `tools/gte_audit.py` | migrated | 73 false-positives eliminated by code-region filter; 7 real findings surfaced |
|
|
| `tools/scan_branch_delay_hazards.py` | migrated | identical to Phase A (CLEAN); also runs translator-source check for non-BIOS configs that lack a function_starts file |
|
|
| `recompiler/tests/l1_decoder_test.cpp` | **deferred** | C++ TOML parsing not yet wired; the test still runs against hardcoded BIOS `kRegions[]`. Generalize in a later sub-phase. |
|
|
| `recompiler/tests/l2_structural_test.cpp` | not needed | no per-program input; already generic. |
|
|
|
|
Side effects of generalization:
|
|
- `gte_audit.py` surfaced 7 previously-masked findings in Shell code:
|
|
4 missing `gte_execute` emits at `0xBFC34FF8`, `0xBFC3502C`,
|
|
`0xBFC35064`, `0xBFC350C4` (all `0x4A480012` = MVMVA), plus
|
|
3 missing LWC2/SWC2 emits. None of these PCs appear in
|
|
`generated/SCPH1001_full.c` — either a discovery gap (function
|
|
containing them not recompiled) or an emit gap. Investigate in
|
|
Phase B3 or B4.
|
|
|
|
## Methodology going forward
|
|
|
|
Every new bug class encountered should follow:
|
|
1. Reproduce / characterize the class (not the single instance).
|
|
2. **Build the audit FIRST** — find every instance of the class in the
|
|
codebase via tooling, not by manual repro of each.
|
|
3. Fix the underlying recompiler / runtime issue.
|
|
4. Re-run the audit, confirm class is now empty.
|
|
5. Add the audit to the standing audit suite so the class can't
|
|
regress.
|
|
|
|
The audit suite forms the regression net: "0 findings, audited N sites"
|
|
is the only acceptable state for a known class.
|