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

+1 -19
View File
@@ -19,19 +19,12 @@
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Core/Core.h"
#include "Core/HW/GpioMMIO.h"
#include "Core/HW/MMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
namespace MMIO {
// Unhandled reads return a distinctive poison value (not 0) so it's obvious in a live trace when
// some later computation's input traces back to an unimplemented register, rather than looking
// like an ordinary, legitimate zero.
constexpr u32 UNKNOWN_MMIO_POISON = 0x1337BEEF;
static std::vector<MMIODevice *> g_devices;
void Init() {
@@ -65,27 +58,16 @@ static MMIODevice *FindDevice(u32 address) {
return nullptr;
}
static bool CheckKernelMode(MIPSState *mips, u32 address, int size, MemoryExceptionType type) {
if (Memory::IsKernelCodeAddress(mips->pc))
return true;
Core_MemoryException(address, size, mips->pc, type, "Kernel mode only");
return false;
}
u32 Read(MIPSState *mips, u32 address, int size) {
if (!CheckKernelMode(mips, address, size, MemoryExceptionType::READ_WORD))
return UNKNOWN_MMIO_POISON;
MMIODevice *device = FindDevice(address);
u32 value;
if (device && device->Read(mips, address - device->Base(), size, &value))
return value;
WARN_LOG(Log::CPU, "Unhandled MMIO Read%d at %08x%s%s", size * 8, address, device ? " in " : "", device ? device->Name() : "");
return UNKNOWN_MMIO_POISON;
return UNHANDLED_READ;
}
void Write(MIPSState *mips, u32 address, int size, u32 value) {
if (!CheckKernelMode(mips, address, size, MemoryExceptionType::WRITE_WORD))
return;
MMIODevice *device = FindDevice(address);
if (device && device->Write(mips, address - device->Base(), size, value))
return;
+7 -2
View File
@@ -51,13 +51,18 @@ private:
namespace MMIO {
// What an unhandled read returns. Distinctive (not 0) so it's obvious in a live trace when some later
// computation's input traces back to an unimplemented register, rather than looking like an
// ordinary, legitimate zero.
constexpr u32 UNHANDLED_READ = 0x1337BEEF;
// Registers the devices, in their power-on state.
void Init();
void Shutdown();
void DoState(PointerWrap &p);
// For an address Memory::IsMMIOAccess() accepts. size is 1, 2 or 4. An access no device handles is
// logged, and reads as poison.
// For an address Memory::IsMMIOAccess() accepts, from kernel code (the caller checks). size is 1, 2
// or 4. An access no device handles is logged, and reads as UNHANDLED_READ.
u32 Read(MIPSState *mips, u32 address, int size);
void Write(MIPSState *mips, u32 address, int size, u32 value);
+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:
+1
View File
@@ -347,6 +347,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &m
&RemoveLoadStoreLeftRight,
&OptimizeFPMoves,
&PropagateConstants,
&ResolveKernelMemory,
&PurgeTemps,
&ReduceVec4Flush,
&OptimizeLoadsAfterStores,
+9
View File
@@ -95,6 +95,15 @@ static const IRMeta irMeta[] = {
{ IROp::Store32Conditional, "Store32Conditional", "GGC", IRFLAG_SRC3DST },
{ IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 },
{ IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 },
// Barriers: a hardware register access has side effects, and mustn't move or merge.
{ IROp::LoadKernel8, "LoadKernel8", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel8Ext, "LoadKernel8Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16, "LoadKernel16", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel16Ext, "LoadKernel16Ext", "GGC", IRFLAG_BARRIER },
{ IROp::LoadKernel32, "LoadKernel32", "GGC", IRFLAG_BARRIER },
{ IROp::StoreKernel8, "StoreKernel8", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel16, "StoreKernel16", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::StoreKernel32, "StoreKernel32", "GGC", IRFLAG_SRC3 | IRFLAG_BARRIER },
{ IROp::FAdd, "FAdd", "FFF" },
{ IROp::FSub, "FSub", "FFF" },
{ IROp::FMul, "FMul", "FFF" },
+11
View File
@@ -110,6 +110,17 @@ enum class IROp : uint8_t {
StoreFloat,
StoreVec4,
// Kernel code's loads and stores, which may reach hardware registers (MMIO). See
// ResolveKernelMemory.
LoadKernel8,
LoadKernel8Ext,
LoadKernel16,
LoadKernel16Ext,
LoadKernel32,
StoreKernel8,
StoreKernel16,
StoreKernel32,
Ext8to32,
Ext16to32,
ReverseBits,
+79
View File
@@ -15,6 +15,7 @@
#include "Core/Debugger/Breakpoints.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/HW/MMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/MIPSTables.h"
@@ -70,6 +71,51 @@ u32 IRRunMemCheck(u32 pc, u32 addr) {
return coreState != CORE_RUNNING_CPU ? 1 : 0;
}
// LoadKernel* and StoreKernel*: memory, or a hardware register, like the interpreter's loads and stores.
template <int size>
static bool IsValidAlignedAddress(u32 addr) {
switch (size) {
case 1: return Memory::IsValidAddress(addr);
case 2: return Memory::IsValid2AlignedAddress(addr);
default: return Memory::IsValid4AlignedAddress(addr);
}
}
template <int size, bool sign>
static u32 RunKernelLoad(MIPSState *mips, u32 addr) {
u32 value;
if (IsValidAlignedAddress<size>(addr)) {
switch (size) {
case 1: value = Memory::ReadUnchecked_U8(addr); break;
case 2: value = Memory::ReadUnchecked_U16(addr); break;
default: value = Memory::ReadUnchecked_U32(addr); break;
}
} else if (Memory::IsMMIOAccess(addr)) {
value = MMIO::Read(mips, addr, size);
} else {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "kernel load");
return 0;
}
if (sign)
value = size == 1 ? SignExtend8ToU32(value) : SignExtend16ToU32(value);
return value;
}
template <int size>
static void RunKernelStore(MIPSState *mips, u32 addr, u32 value) {
if (IsValidAlignedAddress<size>(addr)) {
switch (size) {
case 1: Memory::WriteUnchecked_U8((u8)value, addr); break;
case 2: Memory::WriteUnchecked_U16((u16)value, addr); break;
default: Memory::WriteUnchecked_U32(value, addr); break;
}
} else if (Memory::IsMMIOAccess(addr)) {
MMIO::Write(mips, addr, size, value);
} else {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "kernel store");
}
}
// With GCC and Clang, each op jumps straight to the next op's handler through a table ("threaded"
// dispatch), which predicts much better than the one shared indirect jump of a switch. Other
// compilers get the switch. Ops missing from the table fall back to the switch, so they still work.
@@ -158,6 +204,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) {
dispatch[(int)IROp::StoreFloat] = &&L_StoreFloat;
dispatch[(int)IROp::LoadVec4] = &&L_LoadVec4;
dispatch[(int)IROp::StoreVec4] = &&L_StoreVec4;
dispatch[(int)IROp::LoadKernel8] = &&L_LoadKernel8;
dispatch[(int)IROp::LoadKernel8Ext] = &&L_LoadKernel8Ext;
dispatch[(int)IROp::LoadKernel16] = &&L_LoadKernel16;
dispatch[(int)IROp::LoadKernel16Ext] = &&L_LoadKernel16Ext;
dispatch[(int)IROp::LoadKernel32] = &&L_LoadKernel32;
dispatch[(int)IROp::StoreKernel8] = &&L_StoreKernel8;
dispatch[(int)IROp::StoreKernel16] = &&L_StoreKernel16;
dispatch[(int)IROp::StoreKernel32] = &&L_StoreKernel32;
dispatch[(int)IROp::Vec4Init] = &&L_Vec4Init;
dispatch[(int)IROp::Vec4Shuffle] = &&L_Vec4Shuffle;
dispatch[(int)IROp::Vec4Blend] = &&L_Vec4Blend;
@@ -467,6 +521,31 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const IRInst *chainArena) {
IR_NEXT;
}
IR_CASE(LoadKernel8)
mips->r[inst->dest] = RunKernelLoad<1, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel8Ext)
mips->r[inst->dest] = RunKernelLoad<1, true>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel16)
mips->r[inst->dest] = RunKernelLoad<2, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel16Ext)
mips->r[inst->dest] = RunKernelLoad<2, true>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(LoadKernel32)
mips->r[inst->dest] = RunKernelLoad<4, false>(mips, mips->r[inst->src1] + inst->constant);
IR_NEXT;
IR_CASE(StoreKernel8)
RunKernelStore<1>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(StoreKernel16)
RunKernelStore<2>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(StoreKernel32)
RunKernelStore<4>(mips, mips->r[inst->src1] + inst->constant, mips->r[inst->src3]);
IR_NEXT;
IR_CASE(Vec4Init)
{
memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float));
+12
View File
@@ -463,6 +463,18 @@ void IRNativeBackend::CompileIRInst(IRInst inst) {
CompIR_Interpret(inst);
break;
case IROp::LoadKernel8:
case IROp::LoadKernel8Ext:
case IROp::LoadKernel16:
case IROp::LoadKernel16Ext:
case IROp::LoadKernel32:
case IROp::StoreKernel8:
case IROp::StoreKernel16:
case IROp::StoreKernel32:
// Kernel code only, and mostly hardware registers once ResolveKernelMemory has run.
CompIR_Generic(inst);
break;
case IROp::Syscall:
case IROp::SyscallUnresolved:
case IROp::CallReplacement:
+57
View File
@@ -691,6 +691,27 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
}
break;
case IROp::LoadKernel8:
case IROp::LoadKernel8Ext:
case IROp::LoadKernel16:
case IROp::LoadKernel16Ext:
case IROp::LoadKernel32:
case IROp::StoreKernel8:
case IROp::StoreKernel16:
case IROp::StoreKernel32:
// Barriers, so everything is written out, but they can't change the GPRs, so the values
// stay known. A constant address goes in the op, for ResolveKernelMemory.
if (gpr.IsImm(inst.src1)) {
const u32 addr = gpr.GetImm(inst.src1) + inst.constant;
gpr.FlushAll(true);
if ((GetIRMeta(inst.op)->flags & IRFLAG_SRC3) == 0)
gpr.MapDirty(inst.dest);
out.Write(inst.op, inst.dest, 0, 0, addr);
break;
}
gpr.FlushAll(true);
goto doDefault;
case IROp::Load8:
case IROp::Load8Ext:
case IROp::Load16:
@@ -2373,6 +2394,42 @@ bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions
return false;
}
// The plain op for a kernel load or store, and its size, or Nop.
static IROp PlainMemoryOp(IROp op, int *size) {
switch (op) {
case IROp::LoadKernel8: *size = 1; return IROp::Load8;
case IROp::LoadKernel8Ext: *size = 1; return IROp::Load8Ext;
case IROp::LoadKernel16: *size = 2; return IROp::Load16;
case IROp::LoadKernel16Ext: *size = 2; return IROp::Load16Ext;
case IROp::LoadKernel32: *size = 4; return IROp::Load32;
case IROp::StoreKernel8: *size = 1; return IROp::Store8;
case IROp::StoreKernel16: *size = 2; return IROp::Store16;
case IROp::StoreKernel32: *size = 4; return IROp::Store32;
default: return IROp::Nop;
}
}
bool ResolveKernelMemory(const IRWriter &in, IRWriter &out, const IROptions &opts) {
CONDITIONAL_DISABLE;
for (IRInst inst : in.GetInstructions()) {
int size = 0;
const IROp plain = PlainMemoryOp(inst.op, &size);
if (plain != IROp::Nop && inst.src1 == MIPS_REG_ZERO) {
const u32 addr = inst.constant;
bool valid;
switch (size) {
case 1: valid = Memory::IsValidAddress(addr); break;
case 2: valid = Memory::IsValid2AlignedAddress(addr); break;
default: valid = Memory::IsValid4AlignedAddress(addr); break;
}
if (valid)
inst.op = plain;
}
out.Write(inst);
}
return false;
}
bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions &opts) {
CONDITIONAL_DISABLE;
// This tells us to skip an AND op that has been optimized out.
+3
View File
@@ -18,4 +18,7 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o
bool ReduceVec4Flush(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool OptimizeLoadsAfterStores(const IRWriter &in, IRWriter &out, const IROptions &opts);
// Kernel code's loads and stores at a constant address become plain ones where that's memory, so only
// hardware registers (and addresses only known when it runs) stay LoadKernel*/StoreKernel*.
bool ResolveKernelMemory(const IRWriter &in, IRWriter &out, const IROptions &opts);
bool OptimizeForInterpreter(const IRWriter &in, IRWriter &out, const IROptions &opts);
+31 -14
View File
@@ -99,6 +99,23 @@ int MIPS_InterpretSingleStep(MIPSState *mips) {
return 1;
}
// Hardware registers, for kernel code only.
static u32 ReadMMIO(MIPSState *mips, u32 addr, int size) {
if (!Memory::IsKernelCodeAddress(mips->pc)) {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::READ_WORD, "Kernel mode only");
return MMIO::UNHANDLED_READ;
}
return MMIO::Read(mips, addr, size);
}
static void WriteMMIO(MIPSState *mips, u32 addr, int size, u32 value) {
if (!Memory::IsKernelCodeAddress(mips->pc)) {
Core_MemoryException(addr, size, mips->pc, MemoryExceptionType::WRITE_WORD, "Kernel mode only");
return;
}
MMIO::Write(mips, addr, size, value);
}
namespace MIPSInt {
void Int_Cache(MIPSState *mips, MIPSOpcode op) {
int imm = SignExtend16ToS32(op);
@@ -356,7 +373,7 @@ namespace MIPSInt {
if (rt != 0) {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
R(rt) = MMIO::Read(mips, addr, 4);
R(rt) = ReadMMIO(mips, addr, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "ll");
R(rt) = 0;
@@ -371,7 +388,7 @@ namespace MIPSInt {
if (mips->llBit) {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr))
MMIO::Write(mips, addr, 4, R(rt));
WriteMMIO(mips, addr, 4, R(rt));
else
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sc");
} else {
@@ -448,7 +465,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = SignExtend8ToU32(MMIO::Read(mips, addr, 1));
R(rt) = SignExtend8ToU32(ReadMMIO(mips, addr, 1));
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lb");
@@ -461,7 +478,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = SignExtend16ToU32(MMIO::Read(mips, addr, 2));
R(rt) = SignExtend16ToU32(ReadMMIO(mips, addr, 2));
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lh");
@@ -474,7 +491,7 @@ namespace MIPSInt {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 4);
R(rt) = ReadMMIO(mips, addr, 4);
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lw");
@@ -487,7 +504,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 1);
R(rt) = ReadMMIO(mips, addr, 1);
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lbu");
@@ -500,7 +517,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a read from MMIO, so we can handle it.
R(rt) = MMIO::Read(mips, addr, 2);
R(rt) = ReadMMIO(mips, addr, 2);
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lhu");
@@ -513,7 +530,7 @@ namespace MIPSInt {
if (!Memory::IsValidAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 1, (u8)R(rt));
WriteMMIO(mips, addr, 1, (u8)R(rt));
break;
}
Core_MemoryException(addr, 1, PC, MemoryExceptionType::WRITE_WORD, "sb");
@@ -525,7 +542,7 @@ namespace MIPSInt {
if (!Memory::IsValid2AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 2, (u16)R(rt));
WriteMMIO(mips, addr, 2, (u16)R(rt));
break;
}
Core_MemoryException(addr, 2, PC, MemoryExceptionType::WRITE_WORD, "sh");
@@ -537,7 +554,7 @@ namespace MIPSInt {
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
// This is a write to MMIO, so we can handle it.
MMIO::Write(mips, addr, 4, R(rt));
WriteMMIO(mips, addr, 4, R(rt));
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sw");
@@ -555,7 +572,7 @@ namespace MIPSInt {
if (Memory::IsValidAddress(addr)) {
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
} else if (Memory::IsMMIOAccess(addr)) {
mem = MMIO::Read(mips, addr & 0xfffffffc, 4);
mem = ReadMMIO(mips, addr & 0xfffffffc, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwl");
}
@@ -572,7 +589,7 @@ namespace MIPSInt {
if (Memory::IsValidAddress(addr)) {
mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
} else if (Memory::IsMMIOAccess(addr)) {
mem = MMIO::Read(mips, addr & 0xfffffffc, 4);
mem = ReadMMIO(mips, addr & 0xfffffffc, 4);
} else {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwr");
}
@@ -628,7 +645,7 @@ namespace MIPSInt {
case 49:
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
FI(ft) = MMIO::Read(mips, addr, 4);
FI(ft) = ReadMMIO(mips, addr, 4);
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwc1");
@@ -640,7 +657,7 @@ namespace MIPSInt {
case 57:
if (!Memory::IsValid4AlignedAddress(addr)) {
if (Memory::IsMMIOAccess(addr)) {
MMIO::Write(mips, addr, 4, FI(ft));
WriteMMIO(mips, addr, 4, FI(ft));
break;
}
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swc1");
+40 -5
View File
@@ -513,8 +513,9 @@ bool TestCPUEret() {
}
// Kernel code reaching the GPIO block's registers on every core: a write, an acknowledge clearing a
// status bit, reads back through lw and lwc1, and poison from a register nothing handles. Then the
// registers through a savestate.
// status bit, reads back through lw and lwc1, and poison from a register nothing handles. Kernel
// RAM too, at a constant address (which the IR makes a plain access) and through a register. Then
// the registers through a savestate.
bool TestMMIO() {
SetupJitHarness();
MMIO::Init();
@@ -523,6 +524,9 @@ bool TestMMIO() {
const u32 base = 0x88000100;
auto sw = [](int rt, int rs, int imm) { return (0x2Bu << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
auto lwc1 = [](int ft, int rs, int imm) { return (0x31u << 26) | (rs << 21) | (ft << 16) | (imm & 0xFFFF); };
auto sb = [](int rt, int rs, int imm) { return (0x28u << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
auto lb = [](int rt, int rs, int imm) { return (0x20u << 26) | (rs << 21) | (rt << 16) | (imm & 0xFFFF); };
const u32 ram = 0x88001000;
const u32 code[] = {
MIPS_MAKE_LUI(MIPS_REG_T0, 0xBE24), // GPIO
MIPS_MAKE_ORI(MIPS_REG_T1, MIPS_REG_ZERO, 0x30),
@@ -533,6 +537,12 @@ bool TestMMIO() {
lwc1(0, MIPS_REG_T0, 0x20),
MIPS_MAKE_LUI(MIPS_REG_T2, 0xBC10),
MIPS_MAKE_LW(MIPS_REG_V1, MIPS_REG_T2, 0),
MIPS_MAKE_LUI(MIPS_REG_T3, ram >> 16),
sw(MIPS_REG_T1, MIPS_REG_T3, ram & 0xFFFF),
MIPS_MAKE_LW(MIPS_REG_A0, MIPS_REG_T3, ram & 0xFFFF),
MIPS_MAKE_ORI(MIPS_REG_T4, MIPS_REG_ZERO, 0x80),
sb(MIPS_REG_T4, MIPS_REG_A1, 4), // a1 is ram, unknown to the IR.
lb(MIPS_REG_A2, MIPS_REG_A1, 4),
MIPS_MAKE_SYSCALL("UnitTestFakeSyscalls", "UnitTestTerminator"),
MIPS_MAKE_BREAK(1),
};
@@ -550,17 +560,42 @@ bool TestMMIO() {
MMIO::Init();
currentMIPS->r[MIPS_REG_V0] = 0;
currentMIPS->r[MIPS_REG_V1] = 0;
currentMIPS->r[MIPS_REG_A0] = 0;
currentMIPS->r[MIPS_REG_A1] = ram;
currentMIPS->r[MIPS_REG_A2] = 0;
currentMIPS->fi[0] = 0;
currentMIPS->pc = base;
coreState = CORE_RUNNING_CPU;
while (coreState == CORE_RUNNING_CPU)
mipsr4k.RunLoopUntil(CoreTiming::GetTicks(currentMIPS) + 1000000);
if (currentMIPS->r[MIPS_REG_V0] != 0x20 || currentMIPS->fi[0] != 0x20 || currentMIPS->r[MIPS_REG_V1] != 0x1337BEEF) {
printf("MMIO on core %d: v0=%08x f0=%08x v1=%08x, expected 00000020, 00000020, 1337beef\n", (int)core,
currentMIPS->r[MIPS_REG_V0], currentMIPS->fi[0], currentMIPS->r[MIPS_REG_V1]);
if (currentMIPS->r[MIPS_REG_V0] != 0x20 || currentMIPS->fi[0] != 0x20 || currentMIPS->r[MIPS_REG_V1] != MMIO::UNHANDLED_READ ||
currentMIPS->r[MIPS_REG_A0] != 0x10 || currentMIPS->r[MIPS_REG_A2] != 0xFFFFFF80) {
printf("MMIO on core %d: v0=%08x f0=%08x v1=%08x a0=%08x a2=%08x, expected 00000020, 00000020, %08x, 00000010, ffffff80\n", (int)core,
currentMIPS->r[MIPS_REG_V0], currentMIPS->fi[0], currentMIPS->r[MIPS_REG_V1], currentMIPS->r[MIPS_REG_A0], currentMIPS->r[MIPS_REG_A2], MMIO::UNHANDLED_READ);
success = false;
}
if (mipsr4k.jit) {
// The registers stay checked accesses, the constant RAM ones become plain.
JitBlockCacheDebugInterface *blocks = mipsr4k.jit->GetBlockCacheDebugInterface();
const JitBlockDebugInfo info = blocks->GetBlockDebugInfo(blocks->GetBlockNumberFromStartAddress(base));
int kernelOps = 0, plainOps = 0;
for (const std::string &line : info.irDisasm) {
if (line.find("LoadKernel") != std::string::npos || line.find("StoreKernel") != std::string::npos)
kernelOps++;
else if (line.find("Load32 ") != std::string::npos || line.find("Store32 ") != std::string::npos)
plainOps++;
}
// sw/sw/lw/lw to registers, sb/lb through a1. lwc1 is left to the interpreter. The RAM store
// is plain, and the load after it plain too, or gone (the value forwarded).
if (kernelOps != 6 || plainOps < 1) {
printf("MMIO on core %d: %d kernel and %d plain loads/stores, expected 6 and at least 1\n", (int)core, kernelOps, plainOps);
for (const std::string &line : info.irDisasm)
printf(" %s\n", line.c_str());
success = false;
}
}
}
mipsr4k.UpdateCore(CPUCore::INTERPRETER);