[A64] Add MMIO-aware memory sequences

This commit is contained in:
Reality
2026-03-23 00:55:10 +09:00
committed by Herman S.
parent 3906ff11ef
commit ecd7501029
6 changed files with 333 additions and 84 deletions

View File

@@ -11,7 +11,6 @@
#include "xenia/base/clock.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/memory.h"
#include "xenia/cpu/backend/a64/a64_backend.h"
#include "xenia/cpu/backend/a64/a64_emitter.h"
@@ -21,7 +20,9 @@
#include "xenia/cpu/hir/instr.h"
#include "xenia/cpu/ppc/ppc_context.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/xex_module.h"
DECLARE_bool(emit_mmio_aware_stores_for_recorded_exception_addresses);
DECLARE_bool(emit_inline_mmio_checks);
namespace xe {
@@ -31,6 +32,23 @@ namespace a64 {
volatile int anchor_memory = 0;
static bool IsPossibleMMIOInstruction(A64Emitter& e, const hir::Instr* i) {
if (!cvars::emit_mmio_aware_stores_for_recorded_exception_addresses) {
return false;
}
uint32_t guest_address = i->GuestAddressFor();
if (!guest_address) {
return false;
}
auto* guest_module = e.GuestModule();
if (!guest_module) {
return false;
}
auto* flags = guest_module->GetInstructionAddressFlags(guest_address);
return flags && flags->accessed_mmio;
}
// ============================================================================
// OPCODE_DELAY_EXECUTION
// ============================================================================
@@ -151,6 +169,21 @@ struct LOAD_I16 : Sequence<LOAD_I16, I<OPCODE_LOAD, I16Op, I64Op>> {
};
struct LOAD_I32 : Sequence<LOAD_I32, I<OPCODE_LOAD, I32Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
if (IsPossibleMMIOInstruction(e, i.instr)) {
void* mmio_fn = (void*)&MMIOAwareLoad<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareLoad<uint32_t, true>;
}
if (i.src1.is_constant) {
e.mov(e.w1,
static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant())));
} else {
e.mov(e.w1, WReg(i.src1.reg().getIdx()));
}
e.CallNativeSafe(mmio_fn);
e.mov(i.dest, e.w0);
return;
}
if (cvars::emit_inline_mmio_checks) {
if (i.src1.is_constant) {
e.mov(e.w17,
@@ -277,6 +310,26 @@ struct STORE_I16 : Sequence<STORE_I16, I<OPCODE_STORE, VoidOp, I64Op, I16Op>> {
};
struct STORE_I32 : Sequence<STORE_I32, I<OPCODE_STORE, VoidOp, I64Op, I32Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
if (IsPossibleMMIOInstruction(e, i.instr)) {
void* mmio_fn = (void*)&MMIOAwareStore<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareStore<uint32_t, true>;
}
if (i.src1.is_constant) {
e.mov(e.w1,
static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant())));
} else {
e.mov(e.w1, WReg(i.src1.reg().getIdx()));
}
if (i.src2.is_constant) {
e.mov(e.w2,
static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant())));
} else {
e.mov(e.w2, i.src2);
}
e.CallNativeSafe(mmio_fn);
return;
}
if (cvars::emit_inline_mmio_checks) {
if (i.src1.is_constant) {
e.mov(e.w17,
@@ -473,26 +526,14 @@ EMITTER_OPCODE_TABLE(OPCODE_LOAD_CLOCK, LOAD_CLOCK);
struct LOAD_OFFSET_I8
: Sequence<LOAD_OFFSET_I8, I<OPCODE_LOAD_OFFSET, I8Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
e.ldrb(i.dest, ptr(e.GetMembaseReg(), e.x0));
}
};
struct LOAD_OFFSET_I16
: Sequence<LOAD_OFFSET_I16, I<OPCODE_LOAD_OFFSET, I16Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
e.ldrh(i.dest, ptr(e.GetMembaseReg(), e.x0));
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
e.rev16(i.dest, i.dest);
@@ -502,6 +543,28 @@ struct LOAD_OFFSET_I16
struct LOAD_OFFSET_I32
: Sequence<LOAD_OFFSET_I32, I<OPCODE_LOAD_OFFSET, I32Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
if (IsPossibleMMIOInstruction(e, i.instr)) {
void* mmio_fn = (void*)&MMIOAwareLoad<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareLoad<uint32_t, true>;
}
if (i.src1.is_constant) {
e.mov(e.w1,
static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant())));
} else {
e.mov(e.w1, WReg(i.src1.reg().getIdx()));
}
if (i.src2.is_constant) {
e.mov(e.w17,
static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant())));
} else {
e.mov(e.w17, WReg(i.src2.reg().getIdx()));
}
e.add(e.w1, e.w1, e.w17);
e.CallNativeSafe(mmio_fn);
e.mov(i.dest, e.w0);
return;
}
if (cvars::emit_inline_mmio_checks) {
// Compute raw guest address (src1 + src2) in w17 for range check.
if (i.src1.is_constant) {
@@ -538,13 +601,7 @@ struct LOAD_OFFSET_I32
e.b(done);
e.L(normal_access);
{
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
e.ldr(i.dest, ptr(e.GetMembaseReg(), e.x0));
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
e.rev(i.dest, i.dest);
@@ -552,13 +609,7 @@ struct LOAD_OFFSET_I32
}
e.L(done);
} else {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
e.ldr(i.dest, ptr(e.GetMembaseReg(), e.x0));
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
e.rev(i.dest, i.dest);
@@ -569,13 +620,7 @@ struct LOAD_OFFSET_I32
struct LOAD_OFFSET_I64
: Sequence<LOAD_OFFSET_I64, I<OPCODE_LOAD_OFFSET, I64Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
e.ldr(i.dest, ptr(e.GetMembaseReg(), e.x0));
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
e.rev(i.dest, i.dest);
@@ -589,13 +634,7 @@ struct STORE_OFFSET_I8
: Sequence<STORE_OFFSET_I8,
I<OPCODE_STORE_OFFSET, VoidOp, I64Op, I64Op, I8Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
if (i.src3.is_constant) {
e.mov(e.w17, static_cast<uint64_t>(i.src3.constant() & 0xFF));
e.strb(e.w17, ptr(e.GetMembaseReg(), e.x0));
@@ -608,13 +647,7 @@ struct STORE_OFFSET_I16
: Sequence<STORE_OFFSET_I16,
I<OPCODE_STORE_OFFSET, VoidOp, I64Op, I64Op, I16Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src3.is_constant) {
uint16_t val = xe::byte_swap(static_cast<uint16_t>(i.src3.constant()));
@@ -637,6 +670,33 @@ struct STORE_OFFSET_I32
: Sequence<STORE_OFFSET_I32,
I<OPCODE_STORE_OFFSET, VoidOp, I64Op, I64Op, I32Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
if (IsPossibleMMIOInstruction(e, i.instr)) {
void* mmio_fn = (void*)&MMIOAwareStore<uint32_t, false>;
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
mmio_fn = (void*)&MMIOAwareStore<uint32_t, true>;
}
if (i.src1.is_constant) {
e.mov(e.w1,
static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant())));
} else {
e.mov(e.w1, WReg(i.src1.reg().getIdx()));
}
if (i.src2.is_constant) {
e.mov(e.w17,
static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant())));
} else {
e.mov(e.w17, WReg(i.src2.reg().getIdx()));
}
e.add(e.w1, e.w1, e.w17);
if (i.src3.is_constant) {
e.mov(e.w2,
static_cast<uint64_t>(static_cast<uint32_t>(i.src3.constant())));
} else {
e.mov(e.w2, i.src3);
}
e.CallNativeSafe(mmio_fn);
return;
}
if (cvars::emit_inline_mmio_checks) {
// Compute raw guest address (src1 + src2) in w17 for range check.
if (i.src1.is_constant) {
@@ -678,13 +738,7 @@ struct STORE_OFFSET_I32
e.b(done);
e.L(normal_access);
{
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src3.is_constant) {
uint32_t val =
@@ -706,13 +760,7 @@ struct STORE_OFFSET_I32
}
e.L(done);
} else {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src3.is_constant) {
uint32_t val =
@@ -738,13 +786,7 @@ struct STORE_OFFSET_I64
: Sequence<STORE_OFFSET_I64,
I<OPCODE_STORE_OFFSET, VoidOp, I64Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
auto addr_reg = ComputeMemoryAddress(e, i.src1);
if (i.src2.is_constant) {
e.mov(e.x17, static_cast<uint64_t>(i.src2.constant()));
e.add(e.x0, addr_reg, e.x17);
} else {
e.add(e.x0, addr_reg, i.src2.reg());
}
AddGuestMemoryOffset(e, ComputeMemoryAddress(e, i.src1), i.src2);
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
if (i.src3.is_constant) {
uint64_t val = xe::byte_swap(static_cast<uint64_t>(i.src3.constant()));

View File

@@ -85,9 +85,11 @@ inline XReg ComputeMemoryAddress(A64Emitter& e, const I64Op& guest) {
e.mov(e.x0, static_cast<uint64_t>(address));
return e.x0;
} else {
auto src = guest.reg();
// Guest addresses are always 32-bit. Clear any stale upper bits before
// applying the host membase so guest pointers can't escape above 4 GB.
e.mov(e.w0, WReg(src.getIdx()));
if (xe::memory::allocation_granularity() > 0x1000) {
auto src = guest.reg();
e.mov(e.w0, WReg(src.getIdx()));
e.mov(e.w17, 0xE0000000u);
e.cmp(e.w0, e.w17);
auto& skip = e.NewCachedLabel();
@@ -96,12 +98,28 @@ inline XReg ComputeMemoryAddress(A64Emitter& e, const I64Op& guest) {
e.mov(e.w17, 0x1000u);
e.add(e.w0, e.w0, e.w17);
e.L(skip);
return e.x0;
}
return guest.reg();
return e.x0;
}
}
template <typename OffsetOp>
inline XReg AddGuestMemoryOffset(A64Emitter& e, const XReg& base,
const OffsetOp& offset) {
// Guest address arithmetic wraps at 32 bits before the host membase is
// applied. Keep the add in W registers so stale high bits can't escape into
// the final host pointer.
e.mov(e.w0, WReg(base.getIdx()));
if (offset.is_constant) {
e.mov(e.w17,
static_cast<uint64_t>(static_cast<uint32_t>(offset.constant())));
e.add(e.w0, e.w0, e.w17);
} else {
e.add(e.w0, e.w0, WReg(offset.reg().getIdx()));
}
return e.x0;
}
// Flush denormal float32 lanes to zero in a NEON register (in-place).
// A float32 is denormal when 0 < abs(val) < 0x00800000.
// vreg must not equal sa or sb.

View File

@@ -26,11 +26,7 @@
#include "xenia/cpu/stack_walker.h"
#include "xenia/cpu/xex_module.h"
DEFINE_bool(record_mmio_access_exceptions, true,
"For guest addresses records whether we caught any mmio accesses "
"for them. This info can then be used on a subsequent run to "
"instruct the recompiler to emit checks",
"x64");
DECLARE_bool(record_mmio_access_exceptions);
DEFINE_int64(max_stackpoints, 65536,
"Max number of host->guest stack mappings we can record.", "x64");

View File

@@ -27,10 +27,7 @@ DEFINE_bool(enable_rmw_context_merging, false,
"Permit merging read-modify-write HIR instr sequences together "
"into x86 instructions that use a memory operand.",
"x64");
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_mmio_aware_stores_for_recorded_exception_addresses);
DECLARE_bool(emit_inline_mmio_checks);
namespace xe {

View File

@@ -0,0 +1,187 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/testing/util.h"
#include <cstring>
using namespace xe;
using namespace xe::cpu;
using namespace xe::cpu::hir;
using namespace xe::cpu::testing;
using xe::cpu::ppc::PPCContext;
// =============================================================================
// Guest addresses are 32-bit. If a GPR holding a guest address has stale
// upper 32 bits, the backend must mask them before adding the host membase.
// Otherwise the final host pointer escapes the guest address space.
// =============================================================================
TEST_CASE("LOAD_I32_STALE_UPPER_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
auto addr = LoadGPR(b, 4);
StoreGPR(b, 3, b.ZeroExtend(b.Load(addr, INT32_TYPE), INT64_TYPE));
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t addr = test.memory->SystemHeapAlloc(4, 4);
auto* host = test.memory->TranslateVirtual(addr);
uint32_t sentinel = 0xCAFEBABE;
std::memcpy(host, &sentinel, 4);
// Set the GPR to the valid address with garbage upper bits.
ctx->r[4] = 0xDEAD000000000000ULL | addr;
},
[&test](PPCContext* ctx) {
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xCAFEBABE);
test.memory->SystemHeapFree(
static_cast<uint32_t>(ctx->r[4] & 0xFFFFFFFF));
});
}
TEST_CASE("STORE_I32_STALE_UPPER_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
auto addr = LoadGPR(b, 4);
auto val = b.Truncate(LoadGPR(b, 5), INT32_TYPE);
b.Store(addr, val);
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t addr = test.memory->SystemHeapAlloc(4, 4);
std::memset(test.memory->TranslateVirtual(addr), 0, 4);
ctx->r[4] = 0xDEAD000000000000ULL | addr;
ctx->r[5] = 0x12345678;
},
[&test](PPCContext* ctx) {
uint32_t addr = static_cast<uint32_t>(ctx->r[4] & 0xFFFFFFFF);
auto* host = test.memory->TranslateVirtual(addr);
uint32_t result;
std::memcpy(&result, host, 4);
REQUIRE(result == 0x12345678);
test.memory->SystemHeapFree(addr);
});
}
// =============================================================================
// Guest address arithmetic must wrap at 32 bits. Test by computing
// (base + offset) in HIR where the 32-bit sum wraps, then loading/storing.
// =============================================================================
TEST_CASE("LOAD_I32_ADDRESS_WRAPS_AT_32_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
// Compute guest address as (r4 + r5) truncated to 32 bits, then load.
auto base = b.Truncate(LoadGPR(b, 4), INT32_TYPE);
auto offset = b.Truncate(LoadGPR(b, 5), INT32_TYPE);
auto addr = b.ZeroExtend(b.Add(base, offset), INT64_TYPE);
StoreGPR(b, 3, b.ZeroExtend(b.Load(addr, INT32_TYPE), INT64_TYPE));
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t target_addr = test.memory->SystemHeapAlloc(4, 4);
auto* host = test.memory->TranslateVirtual(target_addr);
uint32_t sentinel = 0xDEADF00D;
std::memcpy(host, &sentinel, 4);
// base + offset overflows 32 bits and wraps to target_addr.
ctx->r[4] = 0xFFFF0000u;
ctx->r[5] = static_cast<uint64_t>(target_addr) + 0x10000u;
},
[&test](PPCContext* ctx) {
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xDEADF00D);
uint32_t target_addr = static_cast<uint32_t>(
0xFFFF0000u + static_cast<uint32_t>(ctx->r[5]));
test.memory->SystemHeapFree(target_addr);
});
}
TEST_CASE("STORE_I32_ADDRESS_WRAPS_AT_32_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
auto base = b.Truncate(LoadGPR(b, 4), INT32_TYPE);
auto offset = b.Truncate(LoadGPR(b, 5), INT32_TYPE);
auto addr = b.ZeroExtend(b.Add(base, offset), INT64_TYPE);
auto val = b.Truncate(LoadGPR(b, 6), INT32_TYPE);
b.Store(addr, val);
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t target_addr = test.memory->SystemHeapAlloc(4, 4);
std::memset(test.memory->TranslateVirtual(target_addr), 0, 4);
ctx->r[4] = 0xFFFF0000u;
ctx->r[5] = static_cast<uint64_t>(target_addr) + 0x10000u;
ctx->r[6] = 0xBAADF00D;
},
[&test](PPCContext* ctx) {
uint32_t target_addr = static_cast<uint32_t>(
0xFFFF0000u + static_cast<uint32_t>(ctx->r[5]));
auto* host = test.memory->TranslateVirtual(target_addr);
uint32_t result;
std::memcpy(&result, host, 4);
REQUIRE(result == 0xBAADF00D);
test.memory->SystemHeapFree(target_addr);
});
}
// =============================================================================
// LOAD_OFFSET with constant offset and stale upper bits in base.
// =============================================================================
TEST_CASE("LOAD_OFFSET_I32_STALE_UPPER_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
auto base = LoadGPR(b, 4);
auto offset = b.LoadConstantInt64(4);
StoreGPR(b, 3,
b.ZeroExtend(b.LoadOffset(base, offset, INT32_TYPE), INT64_TYPE));
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t addr = test.memory->SystemHeapAlloc(8, 4);
auto* host = test.memory->TranslateVirtual(addr + 4);
uint32_t sentinel = 0x87654321;
std::memcpy(host, &sentinel, 4);
// Garbage upper bits in the base register.
ctx->r[4] = 0xBEEF000000000000ULL | addr;
},
[&test](PPCContext* ctx) {
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x87654321);
uint32_t addr = static_cast<uint32_t>(ctx->r[4] & 0xFFFFFFFF);
test.memory->SystemHeapFree(addr);
});
}
TEST_CASE("STORE_OFFSET_I32_STALE_UPPER_BITS", "[instr]") {
TestFunction test([](HIRBuilder& b) {
auto base = LoadGPR(b, 4);
auto offset = b.LoadConstantInt64(4);
auto val = b.Truncate(LoadGPR(b, 5), INT32_TYPE);
b.StoreOffset(base, offset, val);
b.Return();
});
test.Run(
[&test](PPCContext* ctx) {
uint32_t addr = test.memory->SystemHeapAlloc(8, 4);
std::memset(test.memory->TranslateVirtual(addr), 0, 8);
ctx->r[4] = 0xBEEF000000000000ULL | addr;
ctx->r[5] = 0xFEEDFACE;
},
[&test](PPCContext* ctx) {
uint32_t addr = static_cast<uint32_t>(ctx->r[4] & 0xFFFFFFFF);
auto* host = test.memory->TranslateVirtual(addr + 4);
uint32_t result;
std::memcpy(&result, host, 4);
REQUIRE(result == 0xFEEDFACE);
test.memory->SystemHeapFree(addr);
});
}

View File

@@ -32,6 +32,15 @@ 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(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");
DEFINE_bool(record_mmio_access_exceptions, true,
"For guest addresses records whether we caught any mmio accesses "
"for them. This info can then be used on a subsequent run to "
"instruct the recompiler to emit checks",
"CPU");
DEFINE_bool(protect_on_release, false,
"Protect released memory to prevent accesses.", "Memory");
DEFINE_bool(scribble_heap, false,