Files
psxrecomp/docs/audit_inventory.md
T
Matthew Stanley 2f709ee542 Phase B: config-driven unified runtime + BIOS shell boot restored
- 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>
2026-05-13 01:31:27 -07:00

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.