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

This commit is contained in:
Henrik Rydgård committed 2026-08-11 10:28:48 +02:00
1 parent 5dfeaed63b
commit d8edeb7649
6 files changed
+184 -103

No files matched your search

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