Yabause-PSP's me.prx ORs 0xA0000000 onto its own addresses to share a
buffer with the ME uncached, which is 0xA8xxxxxx for a module in kernel
RAM. The main CPU's space has no views there, so it crashed.
All of kernel segment 1 (0xA0000000-0xBFFFFFFF) now counts as MMIO, and a
device below the hardware registers maps RAM and VRAM onto their uncached
mirrors at 0x40000000. The main CPU's native kernel loads and stores send
the whole segment to that path; the ME keeps 0xBC000000 as the boundary,
since its space maps the rest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The native dispatcher generated for the ME leaves when its downcount
runs out instead of calling CoreTiming::Advance, and IRNativeJit gets
RunDowncount. Pointerify is decided from the CPU's own memory base. ARM32,
RISC-V and LoongArch64 still give the ME the IR interpreter.
LoadKernel/StoreKernel are now compiled on ARM64 and x64 rather than
going through the IR interpreter one op at a time: memory directly, or a
call to fixed code (which saves the caller-saved registers guest values
live in) for 0xBC000000-0xBFFFFFFF. ARM64's block size estimate counts
them as such. The 16-bit ones stay generic while the software renderer's
depth mirrors are active, as Load16/Store16 do. That matters for the main
CPU's kernel code too, and for all code while the ME is emulated, which
runs in kernel mode everywhere. mcidclan's me-soft-interrupt went from
476 billion to 6.4 billion host instructions for 3 emulated seconds,
ending with the same ME counter as the IR interpreter.
The ME unit tests run on the native JIT too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ll into $zero mapped $zero for writing, which asserts. On ARM64 and
x86-64, sc wrote its 0/1 result into rt without marking it dirty, so it
was never stored (RISC-V and LoongArch already did this right).
See docs/cpu-equivalence.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a Load32, Store32, LoadFloat or StoreFloat is followed by the same op
on the next or previous word through the same base, and the base can be
mapped as a pointer, emit one LDP/STP for both. A struct-copying loop runs
about 24% faster; code without such pairs is unaffected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ARM64 IR JIT crashed on any load/store to a constant address with the
top bit set and an offset too large for an immediate - the kernel RAM mirror
at 0x88000000, for instance.
PrepareSrc1Address is careful about this: a constant address like 0x89100010
arrives sign-extended as a negative int64_t, and the (imm & 0xC0000000) ==
0x80000000 check turns it back into the positive value it should be. But when
the offset doesn't fit an immediate we fall back to loading it into a register
and using the register-offset addressing mode, which only takes the W half of
that register plus an extend - and we asked for SXTW, undoing the fix and
pointing the access ~2GB below the memory view.
Sign extension is still right when the offset is genuinely negative, which
happens when the base is a pointerified register and the displacement is a
negative one from the MIPS instruction. So extend based on the sign of imm.
Found by the Jit unit test, which stores to 0x89100000 and segfaults in the
JIT_IR phase - only reproducible on an actual ARM64 CPU, which is why it never
showed up on CI. The RISC-V and LoongArch backends get this case right already.