IR: Compile kernel loads and stores instead of interpreting them

Kernel code's loads and stores may reach hardware registers, which only the
interpreter handled, so the IR frontend sent every one of them (in kernel
blocks, only run by the VSH boot) to it.

They now become LoadKernel*/StoreKernel* IR ops (one per size, like the
plain loads and stores), which check at run time whether the address is
memory or a hardware register (MMIO). After constant
propagation, ResolveKernelMemory turns the ones at a constant address that's
plain memory into ordinary loads and stores, so only the hardware registers,
and addresses only known at run time, stay checked. The native JITs run the
two ops through their IR fallback. They're barriers, since a register access
has side effects, but can't change the GPRs, so constants stay known across.

The kernel mode check for MMIO moves to the interpreter, the only caller that
can run user code there. TestMMIO covers the new paths and which accesses
stay checked. Under --vsh, the JIT and the interpreter now make the same
unhandled register accesses, in the same numbers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-20 13:40:47 +02:00
1 parent 79e88a7e0d
commit 53ac2c7c56
12 files changed
+268 -47

No files matched your search

+17 -7
View File
@@ -53,18 +53,28 @@ namespace MIPSComp {
CheckMemoryBreakpoint(rs, offset);
if (js.kernelMode) {
// Send all memory accesses to the interpreter in kernel mode.
// TODO: Do something faster - but it hardly matters, currently this is VSH-only.
DISABLE;
return;
}
if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) {
// Don't load anything into $zr
return;
}
if (js.kernelMode) {
// Kernel code may reach hardware registers. ResolveKernelMemory turns the plain memory
// accesses back into ordinary ones where it can tell. The rest go to the interpreter.
switch (o) {
case 32: ir.Write(IROp::LoadKernel8Ext, rt, rs, 0, offset); return;
case 33: ir.Write(IROp::LoadKernel16Ext, rt, rs, 0, offset); return;
case 35: ir.Write(IROp::LoadKernel32, rt, rs, 0, offset); return;
case 36: ir.Write(IROp::LoadKernel8, rt, rs, 0, offset); return;
case 37: ir.Write(IROp::LoadKernel16, rt, rs, 0, offset); return;
case 40: ir.Write(IROp::StoreKernel8, rt, rs, 0, offset); return;
case 41: ir.Write(IROp::StoreKernel16, rt, rs, 0, offset); return;
case 43: ir.Write(IROp::StoreKernel32, rt, rs, 0, offset); return;
default:
DISABLE;
}
}
switch (o) {
// Load
case 35: