Files
psxrecomp/runtime/tests/test_interp_cop0_read_sample.py
Alexandros Mandravillis bdbba2a52f interp: read cop0 before the instruction's own cycle charge
With cycle charging on, the dirty-RAM interpreter charges an instruction's
fetch and base cycle before its body runs. When that charge reaches a
device deadline, the device event runs first. If it raises an interrupt,
CAUSE.IP2 is set before MFC0/CFC0 read CAUSE, so the read sees an IRQ that
belongs to the next instruction boundary. Compiled code batches its
charges to the boundary and never shows this.

Seen in the kernel's syscall handler: `mfc0 a1,Cause` ran interpreted, a
CD IRQ fell due on its own cycle, and the handler saved CAUSE 0x420
instead of 0x20. The IRQ itself was taken at the right boundary; only the
value saved inside the instruction was wrong.

MFC0 and CFC0 now return COP0 as it stood when the instruction began.

interp_cop0_read_sample links the production decoder (cycle charging on)
into a fixture at -O0 and -O2, with a device service that raises IP2 on
the read's own cycle. It reads 0x420 before this change and 0x20 after.
Controls cover no event and an IRQ raised before the instruction.
fixture_link.py stubs every other runtime seam with abort(), so an
unexpected call fails the test.
2026-09-30 22:18:36 +03:00

24 lines
898 B
Python

"""MFC0/CFC0 read COP0 from before their own cycle charge.
Links the production decoder (interp_cop0_decoder.c, cycle charging on) into
test_interp_cop0_read_sample.c at -O0 and -O2; every other runtime seam is an
abort() stub (fixture_link.py).
"""
import argparse
import tempfile
from pathlib import Path
from fixture_link import build_and_run
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--cc', default='gcc')
args = parser.parse_args()
here = Path(__file__).resolve().parent
with tempfile.TemporaryDirectory() as root:
for opt in ('-O0', '-O2'):
build_and_run(args.cc, here, here.parent, opt, Path(root),
[str(here / 'interp_cop0_decoder.c')],
'test_interp_cop0_read_sample.c')
print('PASS: MFC0/CFC0 read CAUSE from before their own cycle charge (O0/O2)')