Interpreter: Add correct alignment checks to loads/stores, cleanup

This commit is contained in:
Henrik Rydgård
2026-08-11 10:28:48 +02:00
parent 5dfeaed63b
commit d8edeb7649
6 changed files with 183 additions and 102 deletions
+4 -4
View File
@@ -124,7 +124,7 @@ void WebSocketMemoryReadU8(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
AutoDisabledReplacements memLock = LockMemory(true);
JsonWriter &json = req.Respond();
json.writeUint("value", Memory::Read_U8(addr));
json.writeUint("value", Memory::ReadUnchecked_U8(addr));
});
}
@@ -145,7 +145,7 @@ void WebSocketMemoryReadU16(DebuggerRequest &req) {
return req.Fail("CPU not started");
// This only depends on addr, not on anything CPU-thread-owned, so fail fast here rather than
// making a round trip through the queue for a request we already know is invalid.
if (!Memory::IsValidAddress(addr))
if (!Memory::IsValidRange(addr, 2))
return req.Fail("Invalid address");
// Route the actual memory read to the CPU thread instead of poking at it directly
@@ -153,7 +153,7 @@ void WebSocketMemoryReadU16(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
AutoDisabledReplacements memLock = LockMemory(true);
JsonWriter &json = req.Respond();
json.writeUint("value", Memory::Read_U16(addr));
json.writeUint("value", Memory::ReadUnchecked_U16(addr));
});
}
@@ -174,7 +174,7 @@ void WebSocketMemoryReadU32(DebuggerRequest &req) {
return req.Fail("CPU not started");
// This only depends on addr, not on anything CPU-thread-owned, so fail fast here rather than
// making a round trip through the queue for a request we already know is invalid.
if (!Memory::IsValidAddress(addr))
if (!Memory::IsValidRange(addr, 4))
return req.Fail("Invalid address");
// Route the actual memory read to the CPU thread instead of poking at it directly
+117 -39
View File
@@ -293,8 +293,7 @@ namespace MIPSInt
void Int_JumpRegType(MIPSOpcode op)
{
if (mipsr4k.inDelaySlot)
{
if (mipsr4k.inDelaySlot) {
// There's one of these in Star Soldier at 0881808c, which seems benign.
ERROR_LOG(Log::CPU, "Jump in delay slot :(");
}
@@ -318,8 +317,7 @@ namespace MIPSInt
}
}
void Int_IType(MIPSOpcode op)
{
void Int_IType(MIPSOpcode op) {
u32 uimm = op & 0xFFFF;
u32 suimm = SignExtend16ToU32(op);
s32 simm = SignExtend16ToS32(op);
@@ -349,9 +347,8 @@ namespace MIPSInt
PC += 4;
}
void Int_StoreSync(MIPSOpcode op)
{
int imm = (signed short)(op&0xFFFF);
void Int_StoreSync(MIPSOpcode op) {
int imm = (signed short)(op & 0xFFFF);
int rt = _RT;
int rs = _RS;
u32 addr = R(rs) + imm;
@@ -360,12 +357,20 @@ namespace MIPSInt
{
case 48: // ll
if (rt != 0) {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "ll");
return;
}
R(rt) = Memory::Read_U32(addr);
}
currentMIPS->llBit = 1;
break;
case 56: // sc
if (currentMIPS->llBit) {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sc");
return;
}
Memory::Write_U32(R(rt), addr);
if (rt != 0) {
R(rt) = 1;
@@ -382,21 +387,18 @@ namespace MIPSInt
}
void Int_RType3(MIPSOpcode op)
{
void Int_RType3(MIPSOpcode op) {
int rt = _RT;
int rs = _RS;
int rd = _RD;
// Don't change $zr.
if (rd == 0)
{
if (rd == 0) {
PC += 4;
return;
}
switch (op & 63)
{
switch (op & 63) {
case 10: if (R(rt) == 0) R(rd) = R(rs); break; //movz
case 11: if (R(rt) != 0) R(rd) = R(rs); break; //movn
case 32: R(rd) = R(rs) + R(rt); break; //add (exception on overflow)
@@ -419,12 +421,11 @@ namespace MIPSInt
}
void Int_ITypeMem(MIPSOpcode op)
{
void Int_ITypeMem(MIPSOpcode op) {
int imm = (signed short)(op&0xFFFF);
int rt = _RT;
int rs = _RS;
u32 addr = R(rs) + imm;
const u32 addr = R(rs) + imm;
if (((op >> 29) & 1) == 0 && rt == 0) {
// Don't load anything into $zr
@@ -432,23 +433,75 @@ namespace MIPSInt
return;
}
switch (op >> 26)
{
case 32: R(rt) = SignExtend8ToU32(Memory::Read_U8(addr)); break; //lb
case 33: R(rt) = SignExtend16ToU32(Memory::Read_U16(addr)); break; //lh
case 35: R(rt) = Memory::Read_U32(addr); break; //lw
case 36: R(rt) = Memory::Read_U8 (addr); break; //lbu
case 37: R(rt) = Memory::Read_U16(addr); break; //lhu
case 40: Memory::Write_U8(R(rt), addr); break; //sb
case 41: Memory::Write_U16(R(rt), addr); break; //sh
case 43: Memory::Write_U32(R(rt), addr); break; //sw
switch (op >> 26) {
case 32:
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lb");
return;
}
R(rt) = SignExtend8ToU32(Memory::ReadUnchecked_U8(addr));
break; //lb
case 33:
if (!Memory::IsValid2AlignedAddress(addr)) {
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lh");
return;
}
R(rt) = SignExtend16ToU32(Memory::ReadUnchecked_U16(addr));
break; //lh
case 35:
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lw");
return;
}
R(rt) = Memory::ReadUnchecked_U32(addr);
break; //lw
case 36:
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 1, PC, MemoryExceptionType::READ_WORD, "lbu");
return;
}
R(rt) = Memory::Read_U8 (addr);
break; //lbu
case 37:
if (!Memory::IsValid2AlignedAddress(addr)) {
Core_MemoryException(addr, 2, PC, MemoryExceptionType::READ_WORD, "lhu");
return;
}
R(rt) = Memory::Read_U16(addr);
break; //lhu
case 40:
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 1, PC, MemoryExceptionType::WRITE_WORD, "sb");
return;
}
Memory::Write_U8(R(rt), addr);
break; //sb
case 41:
if (!Memory::IsValid2AlignedAddress(addr)) {
Core_MemoryException(addr, 2, PC, MemoryExceptionType::WRITE_WORD, "sh");
return;
}
Memory::Write_U16(R(rt), addr);
break; //sh
case 43:
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "sw");
return;
}
Memory::Write_U32(R(rt), addr);
break; //sw
// When there's an LWL and an LWR together, we should be able to peephole optimize that
// into a single non-alignment-checking LW.
case 34: //lwl
{
// Not checking for alignment here - the actual read will be aligned.
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwl");
return;
}
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
u32 result = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) );
R(rt) = result;
}
@@ -456,8 +509,13 @@ namespace MIPSInt
case 38: //lwr
{
// Not checking for alignment here - the actual read will be aligned.
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwr");
return;
}
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
u32 regval = R(rt);
u32 result = ( regval & (0xffffff00 << (24 - shift)) ) | ( mem >> shift );
R(rt) = result;
@@ -466,19 +524,29 @@ namespace MIPSInt
case 42: //swl
{
// Not checking for alignment here - the actual read/write will be aligned.
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swl");
return;
}
u32 shift = (addr & 3) * 8;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
u32 result = ( ( u32(R(rt)) >> (24 - shift) ) ) | ( mem & (0xffffff00 << shift) );
Memory::Write_U32(result, (addr & 0xfffffffc));
Memory::WriteUnchecked_U32(result, (addr & 0xfffffffc));
}
break;
case 46: //swr
{
// Not checking for alignment here - the actual read/write will be aligned.
if (!Memory::IsValidAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swr");
return;
}
u32 shift = (addr & 3) << 3;
u32 mem = Memory::Read_U32(addr & 0xfffffffc);
u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc);
u32 result = ( ( u32(R(rt)) << shift ) | (mem & (0x00ffffff >> (24 - shift)) ) );
Memory::Write_U32(result, (addr & 0xfffffffc));
Memory::WriteUnchecked_U32(result, (addr & 0xfffffffc));
}
break;
@@ -489,17 +557,27 @@ namespace MIPSInt
PC += 4;
}
void Int_FPULS(MIPSOpcode op)
{
s32 offset = (s16)(op&0xFFFF);
void Int_FPULS(MIPSOpcode op) {
s32 offset = (s16)(op & 0xFFFF);
int ft = _FT;
int rs = _RS;
u32 addr = R(rs) + offset;
switch(op >> 26)
{
case 49: FI(ft) = Memory::Read_U32(addr); break; //lwc1
case 57: Memory::Write_U32(FI(ft), addr); break; //swc1
switch (op >> 26) {
case 49:
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::READ_WORD, "lwc1");
return;
}
FI(ft) = Memory::Read_U32(addr);
break; //lwc1
case 57:
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 4, PC, MemoryExceptionType::WRITE_WORD, "swc1");
return;
}
Memory::Write_U32(FI(ft), addr);
break; //swc1
default:
_dbg_assert_msg_(false,"Trying to interpret FPULS instruction that can't be interpreted");
break;
+46 -49
View File
@@ -200,8 +200,7 @@ namespace MIPSInt
PC += 4;
}
void Int_SVQ(MIPSOpcode op)
{
void Int_SVQ(MIPSOpcode op) {
int imm = SignExtend16ToS32(op & 0xFFFC);
int rs = _RS;
int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
@@ -210,31 +209,29 @@ namespace MIPSInt
float *f;
const float *cf;
switch (op >> 26)
{
switch (op >> 26) {
case 53: //lvl.q/lvr.q
{
if (addr & 0x3)
{
_dbg_assert_msg_( 0, "Misaligned lvX.q at %08x (pc = %08x)", addr, PC);
}
float d[4];
ReadVector(d, V_Quad, vt);
int offset = (addr >> 2) & 3;
if ((op & 2) == 0)
{
if ((op & 2) == 0) {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lvl.q");
return;
}
// It's an LVL
for (int i = 0; i < offset + 1; i++)
{
d[3 - i] = Memory::Read_Float(addr - 4 * i);
for (int i = 0; i < offset + 1; i++) {
d[3 - i] = Memory::ReadUnchecked_Float(addr - 4 * i);
}
} else {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lvr.q");
return;
}
}
else
{
// It's an LVR
for (int i = 0; i < (3 - offset) + 1; i++)
{
d[i] = Memory::Read_Float(addr + 4 * i);
for (int i = 0; i < (3 - offset) + 1; i++) {
d[i] = Memory::ReadUnchecked_Float(addr + 4 * i);
}
}
WriteVector(d, V_Quad, vt);
@@ -242,21 +239,21 @@ namespace MIPSInt
break;
case 54: //lv.q
if (addr & 0xF)
{
_dbg_assert_msg_( 0, "Misaligned lv.q at %08x (pc = %08x)", addr, PC);
if ((addr & 0xF) || !Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::READ_WORD, "lv.q");
}
#ifndef COMMON_BIG_ENDIAN
cf = reinterpret_cast<const float *>(Memory::GetPointerRange(addr, 16));
cf = reinterpret_cast<const float *>(Memory::GetPointerUnchecked(addr));
if (cf)
WriteVector(cf, V_Quad, vt);
#else
float lvqd[4];
lvqd[0] = Memory::Read_Float(addr);
lvqd[1] = Memory::Read_Float(addr + 4);
lvqd[2] = Memory::Read_Float(addr + 8);
lvqd[3] = Memory::Read_Float(addr + 12);
lvqd[0] = Memory::ReadUnchecked_Float(addr);
lvqd[1] = Memory::ReadUnchecked_Float(addr + 4);
lvqd[2] = Memory::ReadUnchecked_Float(addr + 8);
lvqd[3] = Memory::ReadUnchecked_Float(addr + 12);
WriteVector(lvqd, V_Quad, vt);
#endif
@@ -264,49 +261,49 @@ namespace MIPSInt
case 61: // svl.q/svr.q
{
if (addr & 0x3)
{
_dbg_assert_msg_( 0, "Misaligned svX.q at %08x (pc = %08x)", addr, PC);
}
float d[4];
ReadVector(d, V_Quad, vt);
int offset = (addr >> 2) & 3;
if ((op&2) == 0)
{
if ((op & 2) == 0) {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "svl.q");
return;
}
// It's an SVL
for (int i = 0; i < offset + 1; i++)
{
Memory::Write_Float(d[3 - i], addr - i * 4);
Memory::WriteUnchecked_Float(d[3 - i], addr - i * 4);
}
} else {
if (!Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "svr.q");
return;
}
}
else
{
// It's an SVR
for (int i = 0; i < (3 - offset) + 1; i++)
{
Memory::Write_Float(d[i], addr + 4 * i);
for (int i = 0; i < (3 - offset) + 1; i++) {
Memory::WriteUnchecked_Float(d[i], addr + 4 * i);
}
}
break;
}
case 62: //sv.q
if (addr & 0xF)
{
_dbg_assert_msg_( 0, "Misaligned sv.q at %08x (pc = %08x)", addr, PC);
if ((addr & 0xF) || !Memory::IsValid4AlignedAddress(addr)) {
Core_MemoryException(addr, 16, PC, MemoryExceptionType::WRITE_WORD, "sv.q");
}
#ifndef COMMON_BIG_ENDIAN
f = reinterpret_cast<float *>(Memory::GetPointerWriteRange(addr, 16));
if (f)
f = reinterpret_cast<float *>(Memory::GetPointerWriteUnchecked(addr));
if (f) {
ReadVector(f, V_Quad, vt);
}
#else
float svqd[4];
ReadVector(svqd, V_Quad, vt);
Memory::Write_Float(svqd[0], addr);
Memory::Write_Float(svqd[1], addr + 4);
Memory::Write_Float(svqd[2], addr + 8);
Memory::Write_Float(svqd[3], addr + 12);
Memory::WriteUnchecked_Float(svqd[0], addr);
Memory::WriteUnchecked_Float(svqd[1], addr + 4);
Memory::WriteUnchecked_Float(svqd[2], addr + 8);
Memory::WriteUnchecked_Float(svqd[3], addr + 12);
#endif
break;
+14 -8
View File
@@ -238,14 +238,6 @@ inline void WriteUnchecked_U8(u8 data, u32 address) {
#endif
}
inline float Read_Float(u32 address)
{
u32 ifloat = Read_U32(address);
float f;
memcpy(&f, &ifloat, sizeof(float));
return f;
}
// used by JIT. Return zero-extended 32bit values
u32 Read_U8_ZX(const u32 address);
u32 Read_U16_ZX(const u32 address);
@@ -325,6 +317,20 @@ inline bool IsValidAddress(const u32 address) {
}
}
inline bool IsValid2AlignedAddress(const u32 address) {
if ((address & 0x3E000001) == 0x08000000) {
return true;
} else if ((address & 0x3F800001) == 0x04000000) {
return address < 0x80000000; // Let's disallow kernel-flagged VRAM. We don't have it mapped and I am not sure if it's accessible.
} else if ((address & 0xBFFFC001) == 0x00010000) {
return true;
} else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) {
return (address & 1) == 0;
} else {
return false;
}
}
inline bool IsValid4AlignedAddress(const u32 address) {
if ((address & 0x3E000003) == 0x08000000) {
return true;
+1 -1
View File
@@ -516,7 +516,7 @@ void ImMemView::PopupMenu() {
if (ImGui::MenuItem("Copy value (float32)")) {
char temp[64];
snprintf(temp, sizeof(temp), "%f", Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN);
snprintf(temp, sizeof(temp), "%f", Memory::IsValid4AlignedAddress(curAddress_) ? Memory::ReadUnchecked_Float(curAddress_) : NAN);
System_CopyStringToClipboard(temp);
}
/*
+1 -1
View File
@@ -597,7 +597,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) {
{
auto memLock = Memory::Lock();
std::ostringstream stream;
stream << (Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN);
stream << (Memory::IsValid4AlignedAddress(curAddress_) ? Memory::ReadUnchecked_Float(curAddress_) : NAN);
auto temp_string = stream.str();
W32Util::CopyTextToClipboard(wnd, temp_string);
}