mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
5b3c7dd08d
Today's session work
- runtime/src/debug_server.c: freeze_check TCP cmd snapshots all liveness
counters (PC, exception entries, exception_reentry_blocks, dirty_ram
blocks/insns, fn_entry total + top-K histogram, SIO IRQ pending +
countdown, sio_stat/i_stat/i_mask, mc_max_state) for tight-loop
diagnosis when the runtime hangs
- runtime/src/debug_server.c: wtrace slot 12 covering [0x72F0..0x72F4]
(data-byte counter); measured peak=1 confirming setup_data_phase resets
the counter mid-cycle
- runtime/src/interrupts.c: exception_entries_total + exception_reentry_blocks
counters + psx_get_freeze_diag accessor
- runtime/src/sio.c: sio_get_freeze_diag accessor for IRQ pacing internals
- memory entries: phase4_card_gate_setup_writes_2026_04_28 (refutes the
prior \"missing [0x7568+slot] clearer\" framing — setup functions write
LSB=0 directly), phase4_72f0_one_byte_per_vblank_2026_04_28 (peak=1
measurement)
Overnight session work bundled in
- runtime/src/dirty_ram_interp.{c,h}: small MIPS interpreter for
install-at-runtime kernel RAM stubs (Rule 18; e.g., SIO data-byte
handler the BIOS writes to RAM 0xCF0)
- recompiler/src/full_function_emitter.{cpp,h}: 3057 jal/jalr continuation
return labels added to dispatch table (continuation fix); branch
fall-through fix for conditional-branch delay slots
- recompiler/src/strict_translator.cpp: matching translator updates
- generated/: regenerated SCPH1001 dispatch + full C output reflecting
the recompiler changes
- runtime/include/debug_server.h, src/memory.c: card_txn ring + SIO IRQ
ring + EvCB walk ring + per-PC dirty_ram counters + always-on wtrace
coverage for [0x755A], [0x7514], [0x7528], [0x7520], [0x74A4],
[0x75C0..C8], [0x74B8..C0]
- recompiler/tests/, runtime/tests/: SIO/card protocol unit tests
- tools/: 60+ Python diagnostic scripts (chain_trace analysis, SIO trace
diff, EvCB walker, wtrace dumpers, etc.)
- docs/: psx_bios_disasm reference + dynamic_handler_install rationale
- OVERNIGHT_2026_04_28.md, SUMMARY_20260412_2.md: session logs
Phase 4 status (still blocked, root cause better understood)
- BIOS card chain runs through R-handlers correctly (chain_trace shows
R1..R11 cycling 26x); shell IS retriggering _card_read
- Install handler at ROM 0xBFC15F1C increments [0x72F0] correctly per
SIO IRQ; gate at <0x80 / <0x7F is correct
- Bottleneck: SIO IRQ countdown is access-paced, fires ~1× per VBlank
vs ~128× needed; setup_data_phase (called from outer coord on VBlank)
zeroes [0x72F0] before counter reaches 128
- Both attempted fixes today caused freezes:
* VBlank deferral on [0x75C0]==1 — pending VBlank already in i_stat
delivered anyway; tighter dispatch gate also locked up
* Cycle-paced sio_tick at rates 50, 200 (and gated on [0x75C0]==1) —
runtime freezes once data phase activates, root cause unidentified
- delay=5 narrows the threshold (peak=2 vs peak=1, no freeze) but
doesn't get to 0x80
- Real fix likely requires deeper instrumentation of the freeze (call
stack / dispatch state at the moment of TCP starvation)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
4.8 KiB
Markdown
146 lines
4.8 KiB
Markdown
# Dynamic handler install — RAM 0xCF0 (and friends)
|
|
|
|
This document describes a class of BIOS behavior that requires special
|
|
runtime handling: **the BIOS writes MIPS instructions into kernel RAM
|
|
at runtime and then transfers control to those addresses.** A pure
|
|
static recompiler cannot follow this — only the runtime-installed
|
|
instructions do the dispatch — so PSXRecomp v4 maintains a small MIPS
|
|
interpreter for "dirty" RAM pages.
|
|
|
|
## The concrete case: SIO data-byte handler
|
|
|
|
When a memcard read command is in flight, the BIOS needs to read 128
|
|
bytes of sector data byte-by-byte over SIO. It does this not via the
|
|
chain dispatcher (which only has 13 handler slots), but via a stub
|
|
installed at RAM 0xCF0 that runs once per SIO IRQ.
|
|
|
|
### Install path
|
|
|
|
The function at ROM 0xBFC15EBC is the install entry. It:
|
|
|
|
1. Clears `mem[0x75c0] = 0` (the "data-mode active" flag).
|
|
2. Copies four words from RAM 0x6408 (= ROM 0xBFC15F08) to
|
|
RAM 0xCF0..0xCFC. Those four words are:
|
|
|
|
```
|
|
lui v0, 0x0
|
|
addiu v0, v0, 0x641C
|
|
jalr v0
|
|
nop
|
|
```
|
|
|
|
At runtime this resolves to `v0 = 0x641C` (the data-byte handler in
|
|
RAM, which is ROM 0xBFC15F1C reached via the kernel's part-2 RAM
|
|
image), then `jalr v0`. The `nop` is the delay slot.
|
|
|
|
3. Tail-jumps to a B0 vector trampoline at RAM 0x6A70.
|
|
|
|
The companion entry at ROM 0xBFC15E80 is the "set [0x75c0] = 1"
|
|
function (B0:0x80), which R11 calls on success at the end of the
|
|
read-protocol setup phase.
|
|
|
|
### Dispatch path
|
|
|
|
When an SIO IRQ fires, the kernel exception handler at RAM 0xC80
|
|
(= ROM 0xBFC10780) runs. It saves registers, calls a helper at
|
|
RAM 0xEA0, masks the cause register, and (for non-syscall causes)
|
|
falls through to:
|
|
|
|
```
|
|
RAM 0xCEC: sw v1, 0x80(k0) ; save scratch
|
|
RAM 0xCF0: lui v0, 0x0 ; ←—— installed stub starts here
|
|
RAM 0xCF4: addiu v0, v0, 0x641C
|
|
RAM 0xCF8: jalr v0
|
|
RAM 0xCFC: nop
|
|
```
|
|
|
|
The installed stub `jalr`s to RAM 0x641C (= ROM 0xBFC15F1C), the
|
|
data-byte handler. That handler:
|
|
|
|
1. Reads `[0x75c0]`. If 0, returns immediately.
|
|
2. Reads SIO_DATA (the data byte from the card).
|
|
3. Stores it at `*mem[0x75c4]` (the running buffer pointer).
|
|
4. Increments `mem[0x75c4]`.
|
|
5. Acks SIO IRQ.
|
|
6. Returns.
|
|
|
|
After 128 such invocations, the BIOS clears `[0x75c0]` and the chain
|
|
dispatcher resumes for R12 (checksum) and R13 (end-byte 'G').
|
|
|
|
## Why static recompilation can't follow this
|
|
|
|
`generated/SCPH1001_full.c` was emitted from the ROM image. At ROM
|
|
addresses 0xBFC107F0..0xBFC107FC the bytes are NOPs — that's all the
|
|
recompiler sees. It emits:
|
|
|
|
```c
|
|
/* 0xBFC107F0: 00000000 nop */
|
|
/* nop */
|
|
/* 0xBFC107F4: 00000000 nop */
|
|
/* nop */
|
|
...
|
|
```
|
|
|
|
The recompiler has no way to know that the BIOS overwrites these
|
|
runtime addresses with a working dispatch stub. Even if it did, it
|
|
couldn't statically know the destination — `addiu v0, v0, 0x641C` is
|
|
a runtime computation against `lui v0, 0x0`, which by itself is
|
|
underspecified.
|
|
|
|
## The interpreter (runtime fix)
|
|
|
|
### Dirty-page tracking
|
|
|
|
The runtime maintains a bitmap covering all writable RAM pages
|
|
(typically 64 KB at 4 KB granularity = 16 bits). Any write into the
|
|
kernel code region (RAM 0x000..0xFFFF) sets the bit for that page.
|
|
|
|
### Dispatch interception
|
|
|
|
`psx_dispatch(addr)` checks the dirty bit for `addr`. If clean,
|
|
calls the static-recompiled function as before. If dirty, hands
|
|
control to the interpreter.
|
|
|
|
### The interpreter
|
|
|
|
A small MIPS interpreter (~300 LOC) executes the basic block at
|
|
`addr` against `cpu->gpr[]`, `cpu->cop0[]`, etc. It supports the
|
|
small subset of instructions that BIOS install stubs actually use
|
|
(arithmetic, loads/stores, branches, jr/jalr). Unsupported
|
|
instructions abort fatally — they should never appear in install
|
|
stubs.
|
|
|
|
After the interpreter executes the block (terminating at jr/j/jalr),
|
|
control returns to `psx_dispatch` for the next basic block. The
|
|
combined effect is that BIOS-installed code at RAM 0xCF0 *runs*,
|
|
correctly, on the same CPU register state as static-recompiled C.
|
|
|
|
## Discoverability
|
|
|
|
Other install slots may exist. The tracker should detect them
|
|
mechanically by:
|
|
|
|
- Logging every write into `RAM 0x000..0xFFFF` (kernel code area).
|
|
- Reporting any PC dispatch that hits a written page.
|
|
|
|
Known/suspected slots to verify after the SIO data handler works:
|
|
|
|
- VBlank pad poll fast-path (?)
|
|
- Memcard slot-1 detection
|
|
- CD-ROM ready handler
|
|
- Anything in `RAM 0xC80..0xFFF` (kernel hook region)
|
|
|
|
## NOT HLE
|
|
|
|
This mechanism is explicitly *not* high-level emulation:
|
|
|
|
- We do not synthesize what the handler "would have produced".
|
|
- We do not intercept events, mark them delivered, or short-circuit
|
|
any BIOS state machine.
|
|
- We do not write C reimplementations of `_card_read`, `OpenEvent`,
|
|
or anything else.
|
|
|
|
We let the BIOS run its own code. The only thing we add is the
|
|
ability for that code to live in dirty RAM rather than ROM. That is
|
|
a property of the program; we accommodate it. See CLAUDE.md Rule 18.
|