From 2713e850428145d5eb4eb8640c498b2ca452f3db Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:38:00 +0900 Subject: [PATCH] [Memory/x64] Add optional inline MMIO range checks for I32 loads/stores MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When emit_inline_mmio_checks is enabled, the x64 backend emits explicit address range checks (0x7FC00000–0x7FFFFFFF) before I32 memory ops and routes hits through MMIOAwareLoad/Store directly, avoiding the cost of trapping access violations for MMIO regions. The MMIO page commit in AddVirtualMappedRange is skipped in this mode since accesses are handled inline rather than via exception. --- src/xenia/cpu/backend/x64/x64_seq_memory.cc | 127 ++++++++++++++++++++ src/xenia/memory.cc | 16 ++- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/xenia/cpu/backend/x64/x64_seq_memory.cc b/src/xenia/cpu/backend/x64/x64_seq_memory.cc index 6931104c7..8b5afbc50 100644 --- a/src/xenia/cpu/backend/x64/x64_seq_memory.cc +++ b/src/xenia/cpu/backend/x64/x64_seq_memory.cc @@ -31,6 +31,7 @@ DEFINE_bool(emit_mmio_aware_stores_for_recorded_exception_addresses, true, "Uses info gathered via record_mmio_access_exceptions to emit " "special stores that are faster than trapping the exception", "CPU"); +DECLARE_bool(emit_inline_mmio_checks); namespace xe { namespace cpu { @@ -1292,6 +1293,35 @@ struct LOAD_OFFSET_I32 e.CallNativeSafe(addrptr); e.mov(i.dest, e.eax); } else { + Xbyak::Label normal_access, done; + bool inline_mmio = cvars::emit_inline_mmio_checks && !IsTracingData(); + if (inline_mmio) { + // Compute guest address (src1 + src2) for range check. + if (i.src1.is_constant) { + e.mov(e.eax, (uint32_t)i.src1.constant()); + } else { + e.mov(e.eax, i.src1.reg().cvt32()); + } + if (i.src2.is_constant) { + e.add(e.eax, (uint32_t)i.src2.constant()); + } else { + e.add(e.eax, i.src2.reg().cvt32()); + } + e.cmp(e.eax, 0x7FC00000); + e.jb(normal_access, e.T_NEAR); + e.cmp(e.eax, 0x7FFFFFFF); + e.ja(normal_access, e.T_NEAR); + // MMIO path + void* mmio_fn = (void*)&MMIOAwareLoad; + if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { + mmio_fn = (void*)&MMIOAwareLoad; + } + e.mov(e.GetNativeParam(0).cvt32(), e.eax); + e.CallNativeSafe(mmio_fn); + e.mov(i.dest, e.eax); + e.jmp(done, e.T_NEAR); + e.L(normal_access); + } auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (e.IsFeatureEnabled(kX64EmitMovbe)) { @@ -1303,6 +1333,9 @@ struct LOAD_OFFSET_I32 } else { e.mov(i.dest, e.dword[addr]); } + if (inline_mmio) { + e.L(done); + } } } }; @@ -1401,6 +1434,39 @@ struct STORE_OFFSET_I32 e.CallNativeSafe(addrptr); } else { + Xbyak::Label normal_access, done; + bool inline_mmio = cvars::emit_inline_mmio_checks && !IsTracingData(); + if (inline_mmio) { + // Compute guest address (src1 + src2) for range check. + if (i.src1.is_constant) { + e.mov(e.eax, (uint32_t)i.src1.constant()); + } else { + e.mov(e.eax, i.src1.reg().cvt32()); + } + if (i.src2.is_constant) { + e.add(e.eax, (uint32_t)i.src2.constant()); + } else { + e.add(e.eax, i.src2.reg().cvt32()); + } + e.cmp(e.eax, 0x7FC00000); + e.jb(normal_access, e.T_NEAR); + e.cmp(e.eax, 0x7FFFFFFF); + e.ja(normal_access, e.T_NEAR); + // MMIO path + void* mmio_fn = (void*)&MMIOAwareStore; + if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { + mmio_fn = (void*)&MMIOAwareStore; + } + e.mov(e.GetNativeParam(0).cvt32(), e.eax); + if (i.src3.is_constant) { + e.mov(e.GetNativeParam(1).cvt32(), i.src3.constant()); + } else { + e.mov(e.GetNativeParam(1).cvt32(), i.src3); + } + e.CallNativeSafe(mmio_fn); + e.jmp(done, e.T_NEAR); + e.L(normal_access); + } auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.src3.is_constant) { @@ -1424,6 +1490,9 @@ struct STORE_OFFSET_I32 e.mov(e.dword[addr], i.src3); } } + if (inline_mmio) { + e.L(done); + } } } }; @@ -1507,6 +1576,30 @@ struct LOAD_I32 : Sequence> { e.CallNativeSafe(addrptr); e.mov(i.dest, e.eax); } else { + Xbyak::Label normal_access, done; + bool inline_mmio = cvars::emit_inline_mmio_checks && !IsTracingData(); + if (inline_mmio) { + // Compute guest address for range check. + if (i.src1.is_constant) { + e.mov(e.eax, (uint32_t)i.src1.constant()); + } else { + e.mov(e.eax, i.src1.reg().cvt32()); + } + e.cmp(e.eax, 0x7FC00000); + e.jb(normal_access, e.T_NEAR); + e.cmp(e.eax, 0x7FFFFFFF); + e.ja(normal_access, e.T_NEAR); + // MMIO path + void* mmio_fn = (void*)&MMIOAwareLoad; + if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { + mmio_fn = (void*)&MMIOAwareLoad; + } + e.mov(e.GetNativeParam(0).cvt32(), e.eax); + e.CallNativeSafe(mmio_fn); + e.mov(i.dest, e.eax); + e.jmp(done, e.T_NEAR); + e.L(normal_access); + } auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (e.IsFeatureEnabled(kX64EmitMovbe)) { @@ -1523,6 +1616,9 @@ struct LOAD_I32 : Sequence> { e.lea(e.GetNativeParam(0), e.ptr[addr]); e.CallNative(reinterpret_cast(TraceMemoryLoadI32)); } + if (inline_mmio) { + e.L(done); + } } } }; @@ -1671,6 +1767,34 @@ struct STORE_I32 : Sequence> { e.CallNativeSafe(addrptr); } else { + Xbyak::Label normal_access, done; + bool inline_mmio = cvars::emit_inline_mmio_checks && !IsTracingData(); + if (inline_mmio) { + // Compute guest address for range check. + if (i.src1.is_constant) { + e.mov(e.eax, (uint32_t)i.src1.constant()); + } else { + e.mov(e.eax, i.src1.reg().cvt32()); + } + e.cmp(e.eax, 0x7FC00000); + e.jb(normal_access, e.T_NEAR); + e.cmp(e.eax, 0x7FFFFFFF); + e.ja(normal_access, e.T_NEAR); + // MMIO path + void* mmio_fn = (void*)&MMIOAwareStore; + if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { + mmio_fn = (void*)&MMIOAwareStore; + } + e.mov(e.GetNativeParam(0).cvt32(), e.eax); + if (i.src2.is_constant) { + e.mov(e.GetNativeParam(1).cvt32(), i.src2.constant()); + } else { + e.mov(e.GetNativeParam(1).cvt32(), i.src2); + } + e.CallNativeSafe(mmio_fn); + e.jmp(done, e.T_NEAR); + e.L(normal_access); + } auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.src2.is_constant) { @@ -1695,6 +1819,9 @@ struct STORE_I32 : Sequence> { e.CallNative(reinterpret_cast(TraceMemoryStoreI32)); } } + if (inline_mmio) { + e.L(done); + } } } }; diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index c45b6f623..a81691d60 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -28,6 +28,10 @@ DEFINE_bool(protect_zero, true, "Protect the zero page from reads and writes.", "Memory"); +DEFINE_bool(emit_inline_mmio_checks, false, + "Emit inline MMIO range checks for all I32 loads/stores instead " + "of relying on exception-based MMIO detection.", + "CPU"); DEFINE_bool(protect_on_release, false, "Protect released memory to prevent accesses.", "Memory"); DEFINE_bool(scribble_heap, false, @@ -550,11 +554,13 @@ bool Memory::AddVirtualMappedRange(uint32_t virtual_address, uint32_t mask, uint32_t size, void* context, cpu::MMIOReadCallback read_callback, cpu::MMIOWriteCallback write_callback) { - if (!xe::memory::AllocFixed(TranslateVirtual(virtual_address), size, - xe::memory::AllocationType::kCommit, - xe::memory::PageAccess::kNoAccess)) { - XELOGE("Unable to map range; commit/protect failed"); - return false; + if (!cvars::emit_inline_mmio_checks) { + if (!xe::memory::AllocFixed(TranslateVirtual(virtual_address), size, + xe::memory::AllocationType::kCommit, + xe::memory::PageAccess::kNoAccess)) { + XELOGE("Unable to map range; commit/protect failed"); + return false; + } } return mmio_handler_->RegisterRange(virtual_address, mask, size, context, read_callback, write_callback);