[Memory/x64] Add optional inline MMIO range checks for I32 loads/stores

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.
This commit is contained in:
Herman S.
2026-03-21 00:38:00 +09:00
parent 1e8f1837cf
commit 2713e85042
2 changed files with 138 additions and 5 deletions

View File

@@ -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 " "Uses info gathered via record_mmio_access_exceptions to emit "
"special stores that are faster than trapping the exception", "special stores that are faster than trapping the exception",
"CPU"); "CPU");
DECLARE_bool(emit_inline_mmio_checks);
namespace xe { namespace xe {
namespace cpu { namespace cpu {
@@ -1292,6 +1293,35 @@ struct LOAD_OFFSET_I32
e.CallNativeSafe(addrptr); e.CallNativeSafe(addrptr);
e.mov(i.dest, e.eax); e.mov(i.dest, e.eax);
} else { } 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<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareLoad<uint32_t, true>;
}
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); auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (e.IsFeatureEnabled(kX64EmitMovbe)) { if (e.IsFeatureEnabled(kX64EmitMovbe)) {
@@ -1303,6 +1333,9 @@ struct LOAD_OFFSET_I32
} else { } else {
e.mov(i.dest, e.dword[addr]); e.mov(i.dest, e.dword[addr]);
} }
if (inline_mmio) {
e.L(done);
}
} }
} }
}; };
@@ -1401,6 +1434,39 @@ struct STORE_OFFSET_I32
e.CallNativeSafe(addrptr); e.CallNativeSafe(addrptr);
} else { } 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<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareStore<uint32_t, true>;
}
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); auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src3.is_constant) { if (i.src3.is_constant) {
@@ -1424,6 +1490,9 @@ struct STORE_OFFSET_I32
e.mov(e.dword[addr], i.src3); e.mov(e.dword[addr], i.src3);
} }
} }
if (inline_mmio) {
e.L(done);
}
} }
} }
}; };
@@ -1507,6 +1576,30 @@ struct LOAD_I32 : Sequence<LOAD_I32, I<OPCODE_LOAD, I32Op, I64Op>> {
e.CallNativeSafe(addrptr); e.CallNativeSafe(addrptr);
e.mov(i.dest, e.eax); e.mov(i.dest, e.eax);
} else { } 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<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareLoad<uint32_t, true>;
}
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); auto addr = ComputeMemoryAddress(e, i.src1);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (e.IsFeatureEnabled(kX64EmitMovbe)) { if (e.IsFeatureEnabled(kX64EmitMovbe)) {
@@ -1523,6 +1616,9 @@ struct LOAD_I32 : Sequence<LOAD_I32, I<OPCODE_LOAD, I32Op, I64Op>> {
e.lea(e.GetNativeParam(0), e.ptr[addr]); e.lea(e.GetNativeParam(0), e.ptr[addr]);
e.CallNative(reinterpret_cast<void*>(TraceMemoryLoadI32)); e.CallNative(reinterpret_cast<void*>(TraceMemoryLoadI32));
} }
if (inline_mmio) {
e.L(done);
}
} }
} }
}; };
@@ -1671,6 +1767,34 @@ struct STORE_I32 : Sequence<STORE_I32, I<OPCODE_STORE, VoidOp, I64Op, I32Op>> {
e.CallNativeSafe(addrptr); e.CallNativeSafe(addrptr);
} else { } 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<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareStore<uint32_t, true>;
}
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); auto addr = ComputeMemoryAddress(e, i.src1);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src2.is_constant) { if (i.src2.is_constant) {
@@ -1695,6 +1819,9 @@ struct STORE_I32 : Sequence<STORE_I32, I<OPCODE_STORE, VoidOp, I64Op, I32Op>> {
e.CallNative(reinterpret_cast<void*>(TraceMemoryStoreI32)); e.CallNative(reinterpret_cast<void*>(TraceMemoryStoreI32));
} }
} }
if (inline_mmio) {
e.L(done);
}
} }
} }
}; };

View File

@@ -28,6 +28,10 @@
DEFINE_bool(protect_zero, true, "Protect the zero page from reads and writes.", DEFINE_bool(protect_zero, true, "Protect the zero page from reads and writes.",
"Memory"); "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, DEFINE_bool(protect_on_release, false,
"Protect released memory to prevent accesses.", "Memory"); "Protect released memory to prevent accesses.", "Memory");
DEFINE_bool(scribble_heap, false, DEFINE_bool(scribble_heap, false,
@@ -550,11 +554,13 @@ bool Memory::AddVirtualMappedRange(uint32_t virtual_address, uint32_t mask,
uint32_t size, void* context, uint32_t size, void* context,
cpu::MMIOReadCallback read_callback, cpu::MMIOReadCallback read_callback,
cpu::MMIOWriteCallback write_callback) { cpu::MMIOWriteCallback write_callback) {
if (!xe::memory::AllocFixed(TranslateVirtual(virtual_address), size, if (!cvars::emit_inline_mmio_checks) {
xe::memory::AllocationType::kCommit, if (!xe::memory::AllocFixed(TranslateVirtual(virtual_address), size,
xe::memory::PageAccess::kNoAccess)) { xe::memory::AllocationType::kCommit,
XELOGE("Unable to map range; commit/protect failed"); xe::memory::PageAccess::kNoAccess)) {
return false; XELOGE("Unable to map range; commit/protect failed");
return false;
}
} }
return mmio_handler_->RegisterRange(virtual_address, mask, size, context, return mmio_handler_->RegisterRange(virtual_address, mask, size, context,
read_callback, write_callback); read_callback, write_callback);