[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:
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user