mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
arm64: memory placement, VTLB codegen, and TLB-miss handling
SharedMemoryMappingArea::Create() gains a fixed_base_hint so AllocateMemoryMap can pin the JIT arena at a constant VA (kArenaBase=4GB, 256MB-stride fallback) on arm64; the VTLB fastmem backpatch thunk (RecStubs), ArmAddressRecorder relocation hooks (AsmHelpers), and the cpuTlbMiss rec-vs-interp PC split (R5900) round out the arm64 memory layer. Windows arm64 is a no-op stub. Co-Authored-By: Ryan Walklin <ryan@testtoast.com> Co-Authored-By: Brian Degenhardt <bmd@bmdhacks.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Ryan Walklin
Claude Opus 4.8
parent
e0b85251aa
commit
6799ab0d2d
+6
-1
@@ -139,7 +139,12 @@ namespace PageFaultHandler
|
||||
class SharedMemoryMappingArea
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<SharedMemoryMappingArea> Create(size_t size, bool jit = false);
|
||||
// fixed_base_hint: when non-zero, the area is placed at that VA (256MB-stride
|
||||
// fallback slots, then kernel placement) so the reserved region — and any JIT
|
||||
// code mapped inside it — lands at the same address every run, which a caller
|
||||
// can rely on for address-stable code caching. Zero = kernel-chosen placement
|
||||
// (the default).
|
||||
static std::unique_ptr<SharedMemoryMappingArea> Create(size_t size, bool jit = false, uptr fixed_base_hint = 0);
|
||||
|
||||
~SharedMemoryMappingArea();
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ SharedMemoryMappingArea::~SharedMemoryMappingArea()
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size, bool jit)
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size, bool jit, uptr fixed_base_hint)
|
||||
{
|
||||
pxAssertRel(Common::IsAlignedPow2(size, __pagesize), "Size is page aligned");
|
||||
|
||||
@@ -162,7 +162,34 @@ std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t
|
||||
if (jit)
|
||||
flags |= MAP_JIT;
|
||||
#endif
|
||||
void* alloc = mmap(nullptr, size, PROT_NONE, flags, -1, 0);
|
||||
|
||||
void* alloc = MAP_FAILED;
|
||||
|
||||
// Deterministic VA placement for the on-disk VU program cache: when a
|
||||
// fixed base hint is given, try it (plus 256MB-stride fallback slots) with a
|
||||
// hint-and-verify mmap (no MAP_FIXED, so we never clobber an existing
|
||||
// mapping). If none of the slots land exactly, fall through to kernel
|
||||
// placement — the program cache records the arena base and simply misses on
|
||||
// a mismatch, so this trades determinism for booting, never correctness.
|
||||
if (fixed_base_hint != 0)
|
||||
{
|
||||
for (int slot = 0; slot <= 10; slot++)
|
||||
{
|
||||
void* const want = reinterpret_cast<void*>(fixed_base_hint + (static_cast<uptr>(slot) << 28));
|
||||
void* const got = mmap(want, size, PROT_NONE, flags, -1, 0);
|
||||
if (got == MAP_FAILED)
|
||||
continue;
|
||||
if (got == want)
|
||||
{
|
||||
alloc = got;
|
||||
break;
|
||||
}
|
||||
munmap(got, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (alloc == MAP_FAILED)
|
||||
alloc = mmap(nullptr, size, PROT_NONE, flags, -1, 0);
|
||||
if (alloc == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
|
||||
@@ -143,10 +143,15 @@ SharedMemoryMappingArea::PlaceholderMap::iterator SharedMemoryMappingArea::FindP
|
||||
return m_placeholder_ranges.end();
|
||||
}
|
||||
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size, bool jit)
|
||||
std::unique_ptr<SharedMemoryMappingArea> SharedMemoryMappingArea::Create(size_t size, bool jit, uptr fixed_base_hint)
|
||||
{
|
||||
pxAssertRel(Common::IsAlignedPow2(size, __pagesize), "Size is page aligned");
|
||||
|
||||
// Deterministic fixed-base placement is not implemented on Windows; kernel-chosen
|
||||
// placement is used and the program cache simply incurs a miss if the arena base
|
||||
// differs across runs.
|
||||
(void)fixed_base_hint;
|
||||
|
||||
void* alloc = VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
|
||||
if (!alloc)
|
||||
return nullptr;
|
||||
|
||||
+35
-2
@@ -95,7 +95,22 @@ bool SysMemory::AllocateMemoryMap()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(s_memory_mapping_area = SharedMemoryMappingArea::Create(HostMemoryMap::MainSize + HostMemoryMap::CodeSize, true)))
|
||||
// Constant-VA arena for the on-disk VU program cache: on arm64 the
|
||||
// whole data+code reservation must sit at the same VA every run so cached
|
||||
// JIT code (which lives in the code half, at BasePointer() + MainSize)
|
||||
// reloads without repatching its baked addresses. 4GB clears the ASLR brk
|
||||
// window (non-PIE image at 0x400000 + brk randomization < 2GB) and sits far
|
||||
// below the mmap_base / PIE-load regions, so the slot-0 candidate succeeds
|
||||
// deterministically; Create() walks 256MB-stride fallback slots and finally
|
||||
// kernel placement (program-cache misses, never corruption). Other arches
|
||||
// pass 0 and take kernel-chosen placement.
|
||||
#if defined(__aarch64__) || defined(_M_ARM64)
|
||||
constexpr uptr kArenaBase = 0x100000000ull; // 4GB
|
||||
#else
|
||||
constexpr uptr kArenaBase = 0;
|
||||
#endif
|
||||
|
||||
if (!(s_memory_mapping_area = SharedMemoryMappingArea::Create(HostMemoryMap::MainSize + HostMemoryMap::CodeSize, true, kArenaBase)))
|
||||
{
|
||||
Host::ReportErrorAsync("Error", "Failed to map main memory.");
|
||||
ReleaseMemoryMap();
|
||||
@@ -172,11 +187,19 @@ void SysMemory::ReleaseMemoryMap()
|
||||
}
|
||||
}
|
||||
|
||||
void SysMemory::ReserveMemory()
|
||||
{
|
||||
// Claim the host memory map (and the arm64 constant-VA arena) up front, so
|
||||
// the fixed-base placement isn't lost to an intervening heap/mmap. Idempotent.
|
||||
if (!s_data_memory_file_handle)
|
||||
AllocateMemoryMap();
|
||||
}
|
||||
|
||||
bool SysMemory::Allocate()
|
||||
{
|
||||
DevCon.WriteLn(Color_StrongBlue, "Allocating host memory for virtual systems...");
|
||||
|
||||
if (!AllocateMemoryMap())
|
||||
if (!s_data_memory_file_handle && !AllocateMemoryMap())
|
||||
return false;
|
||||
|
||||
memAllocate();
|
||||
@@ -411,6 +434,16 @@ void memMapPhy()
|
||||
// High memory, uninstalled on the configuration we emulate
|
||||
vtlb_MapHandler(null_handler, Ps2MemSize::ExposedRam, 0x10000000 - Ps2MemSize::ExposedRam);
|
||||
|
||||
// Physical RAM mirrors used by BIOS InitRDRAM for RDRAM device configuration.
|
||||
// On real PS2 hardware:
|
||||
// 0x20000000-0x21FFFFFF = uncached mirror of main RAM
|
||||
// 0x30000000-0x31FFFFFF = uncached & accelerated mirror of main RAM
|
||||
// These mirrors must be present in the physical map; without them, BIOS writes
|
||||
// to RDRAM device registers hit UnmappedPhyHandler (bus error).
|
||||
// Requires VTLB_PMAP_SZ >= 1GB to cover these addresses.
|
||||
vtlb_MapBlock(eeMem->Main, 0x20000000, Ps2MemSize::ExposedRam);
|
||||
vtlb_MapBlock(eeMem->Main, 0x30000000, Ps2MemSize::ExposedRam);
|
||||
|
||||
// Various ROMs (all read-only)
|
||||
vtlb_MapBlock(eeMem->ROM, 0x1fc00000, Ps2MemSize::Rom);
|
||||
vtlb_MapBlock(eeMem->ROM1, 0x1e000000, Ps2MemSize::Rom1);
|
||||
|
||||
@@ -98,6 +98,12 @@ namespace HostMemoryMap
|
||||
|
||||
namespace SysMemory
|
||||
{
|
||||
/// Reserve the host memory map (and, on arm64, the constant-VA arena) early,
|
||||
/// before other allocations could squat on the fixed base. Idempotent — a
|
||||
/// later Allocate() reuses the same reservation. Used by headless runners and
|
||||
/// SDL frontends that need the deterministic arena claimed before any
|
||||
/// heap/mmap could squat on the fixed base.
|
||||
void ReserveMemory();
|
||||
bool Allocate();
|
||||
void Reset();
|
||||
void Release();
|
||||
|
||||
+39
-6
@@ -166,8 +166,17 @@ __ri void cpuException(u32 code, u32 bd)
|
||||
|
||||
void cpuTlbMiss(u32 addr, u32 bd, u32 excode)
|
||||
{
|
||||
// Avoid too much spamming on the interpreter
|
||||
if (Cpu != &intCpu || IsDebugBuild) {
|
||||
// Avoid too much spamming. On x86 the recompiler uses CancelInstruction and
|
||||
// seldom reaches here, so logging the rec path is cheap; on arm64 every rec
|
||||
// TLB miss is funneled through this function (see s_recTlbMissOccurred below),
|
||||
// so logging the rec path would spam Release on every miss. Gate the arm64
|
||||
// rec path on debug builds, matching the original "don't spam on interp" intent.
|
||||
#ifdef __aarch64__
|
||||
const bool log_tlb_miss = IsDebugBuild;
|
||||
#else
|
||||
const bool log_tlb_miss = (Cpu != &intCpu) || IsDebugBuild;
|
||||
#endif
|
||||
if (log_tlb_miss) {
|
||||
Console.Error("cpuTlbMiss pc:%x, cycl:%llx, addr: %x, status=%x, code=%x",
|
||||
cpuRegs.pc, cpuRegs.cycle, addr, cpuRegs.CP0.n.Status.val, excode);
|
||||
}
|
||||
@@ -177,8 +186,30 @@ void cpuTlbMiss(u32 addr, u32 bd, u32 excode)
|
||||
cpuRegs.CP0.n.Context |= (addr >> 9) & 0x007FFFF0;
|
||||
cpuRegs.CP0.n.EntryHi = (addr & 0xFFFFE000) | (cpuRegs.CP0.n.EntryHi & 0x1FFF);
|
||||
|
||||
cpuRegs.pc -= 4;
|
||||
// The interpreter advances cpuRegs.pc past the current instruction
|
||||
// before executing it, so pc -= 4 gets back to the faulting instruction.
|
||||
// The recompiler's FLUSH_PC writes the current instruction's PC
|
||||
// (not advanced), so we must NOT subtract 4 in that case.
|
||||
const bool isRec = (Cpu != &intCpu);
|
||||
if (!isRec)
|
||||
cpuRegs.pc -= 4;
|
||||
|
||||
cpuException(excode, bd);
|
||||
|
||||
// For the ARM64 recompiler: set a flag so the JIT block can detect the
|
||||
// exception after the interpreter call returns and dispatch to the
|
||||
// exception vector. We don't use CancelInstruction (longjmp) because
|
||||
// that disrupts cycle counting and timing. The x86 recompiler uses
|
||||
// CancelInstruction instead.
|
||||
#ifdef __aarch64__
|
||||
if (isRec)
|
||||
{
|
||||
// Defined in the arm64 EE recompiler TU; the dispatcher reads it after
|
||||
// this call to route the block to the exception vector.
|
||||
extern u32 s_recTlbMissOccurred;
|
||||
s_recTlbMissOccurred = 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cpuTlbMissR(u32 addr, u32 bd) {
|
||||
@@ -394,7 +425,6 @@ __fi void _cpuEventTest_Shared()
|
||||
// Console.WriteLn( " IOP ahead by: %d cycles", -EEsCycle );
|
||||
|
||||
EEsCycle = psxCpu->ExecuteBlock(EEsCycle);
|
||||
|
||||
iopEventAction = false;
|
||||
}
|
||||
|
||||
@@ -435,7 +465,10 @@ __fi void _cpuEventTest_Shared()
|
||||
|
||||
// ---- Schedule Next Event Test --------------
|
||||
const float mutiplier = static_cast<float>(PS2CLK) / static_cast<float>(PSXCLK);
|
||||
const int nextIopEventDeta = ((psxRegs.iopNextEventCycle - psxRegs.cycle) * mutiplier);
|
||||
// See R3000A.cpp:PSX_INT for the host-divergence rationale: cast the u32
|
||||
// cycle delta to s32 *before* the float multiply.
|
||||
const s32 iopCyclesUntilEvent = static_cast<s32>(psxRegs.iopNextEventCycle - psxRegs.cycle);
|
||||
const int nextIopEventDeta = static_cast<s32>(iopCyclesUntilEvent * mutiplier);
|
||||
// 8 or more cycles behind and there's an event scheduled
|
||||
if (EEsCycle >= nextIopEventDeta)
|
||||
{
|
||||
@@ -448,7 +481,7 @@ __fi void _cpuEventTest_Shared()
|
||||
else
|
||||
{
|
||||
// Otherwise IOP is caught up/not doing anything so we can wait for the next event.
|
||||
cpuSetNextEventDelta(((psxRegs.iopNextEventCycle - psxRegs.cycle) * mutiplier) - EEsCycle);
|
||||
cpuSetNextEventDelta(nextIopEventDeta - EEsCycle);
|
||||
}
|
||||
|
||||
// Apply vsync and other counter nextCycles
|
||||
|
||||
@@ -59,6 +59,9 @@ const vixl::aarch64::VRegister& armQRegister(int n)
|
||||
}
|
||||
|
||||
|
||||
// Opt-in only (matches origin/master): uncomment to compile vixl's
|
||||
// PrintDisassembler/Decoder for armDisassembleAndDumpCode. Off by default so
|
||||
// the disassembler TUs and statics don't ship in normal builds.
|
||||
//#define INCLUDE_DISASSEMBLER
|
||||
|
||||
#ifdef INCLUDE_DISASSEMBLER
|
||||
@@ -71,6 +74,7 @@ thread_local a64::MacroAssembler* armAsm;
|
||||
thread_local u8* armAsmPtr;
|
||||
thread_local size_t armAsmCapacity;
|
||||
thread_local ArmConstantPool* armConstantPool;
|
||||
thread_local ArmAddressRecorder* armAddressRecorder;
|
||||
|
||||
#ifdef INCLUDE_DISASSEMBLER
|
||||
static std::mutex armDisasmMutex;
|
||||
@@ -136,12 +140,15 @@ void armDisassembleAndDumpCode(const void* ptr, size_t size)
|
||||
std::unique_lock lock(armDisasmMutex);
|
||||
if (!armDisasm)
|
||||
{
|
||||
armDisasm = std::make_unique<a64::PrintDisassembler>(stderr);
|
||||
std::FILE* logFile = Log::GetFileLogHandle();
|
||||
armDisasm = std::make_unique<a64::PrintDisassembler>(logFile ? logFile : stderr);
|
||||
armDisasmDecoder = std::make_unique<a64::Decoder>();
|
||||
armDisasmDecoder->AppendVisitor(armDisasm.get());
|
||||
}
|
||||
|
||||
armDisasmDecoder->Decode(static_cast<const vixl::aarch64::Instruction*>(ptr), static_cast<const vixl::aarch64::Instruction*>(ptr) + size);
|
||||
const auto* start = reinterpret_cast<const vixl::aarch64::Instruction*>(ptr);
|
||||
const auto* end = reinterpret_cast<const vixl::aarch64::Instruction*>(static_cast<const u8*>(ptr) + size);
|
||||
armDisasmDecoder->Decode(start, end);
|
||||
#else
|
||||
Console.Error("Not compiled with INCLUDE_DISASSEMBLER");
|
||||
#endif
|
||||
@@ -162,13 +169,21 @@ void armEmitJmp(const void* ptr, bool force_inline)
|
||||
|
||||
if (use_blr)
|
||||
{
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnAbsoluteTarget(ptr);
|
||||
armAsm->Mov(RXVIXLSCRATCH, reinterpret_cast<uintptr_t>(ptr));
|
||||
armAsm->Br(RXVIXLSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->b(displacement);
|
||||
{
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->b(displacement);
|
||||
}
|
||||
// Record after emission: the scope entry may flush a pending vixl
|
||||
// literal pool, so the insn address is only known once it's out.
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnDirectBranch(armGetCurrentCodePointer() - 4, ptr, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,13 +202,19 @@ void armEmitCall(const void* ptr, bool force_inline)
|
||||
|
||||
if (use_blr)
|
||||
{
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnAbsoluteTarget(ptr);
|
||||
armAsm->Mov(RXVIXLSCRATCH, reinterpret_cast<uintptr_t>(ptr));
|
||||
armAsm->Blr(RXVIXLSCRATCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->bl(displacement);
|
||||
{
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->bl(displacement);
|
||||
}
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnDirectBranch(armGetCurrentCodePointer() - 4, ptr, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +247,23 @@ void armEmitCondBranch(a64::Condition cond, const void* ptr)
|
||||
static_cast<s64>(reinterpret_cast<intptr_t>(ptr) - reinterpret_cast<intptr_t>(armGetCurrentCodePointer()));
|
||||
//pxAssert(Common::IsAligned(jump_distance, 4));
|
||||
|
||||
// A recorder patching this branch on relocation needs the imm26 reach of a
|
||||
// plain B — B.cond's ±1MB imm19 may not survive the move. Force the long
|
||||
// form for targets the recorder marks relocatable and record the B.
|
||||
if (armAddressRecorder && armAddressRecorder->WantsLongCondBranch(ptr))
|
||||
{
|
||||
a64::MacroEmissionCheckScope guard(armAsm);
|
||||
a64::Label branch_not_taken;
|
||||
armAsm->b(&branch_not_taken, a64::InvertCondition(cond));
|
||||
|
||||
const s64 new_jump_distance =
|
||||
static_cast<s64>(reinterpret_cast<intptr_t>(ptr) - reinterpret_cast<intptr_t>(armGetCurrentCodePointer()));
|
||||
armAsm->b(new_jump_distance >> 2);
|
||||
armAddressRecorder->OnDirectBranch(armGetCurrentCodePointer() - 4, ptr, false);
|
||||
armAsm->bind(&branch_not_taken);
|
||||
return;
|
||||
}
|
||||
|
||||
if (a64::Instruction::IsValidImmPCOffset(a64::CondBranchType, jump_distance >> 2))
|
||||
{
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
@@ -249,6 +287,23 @@ void armMoveAddressToReg(const vixl::aarch64::Register& reg, const void* addr)
|
||||
// psxAsm->Mov(reg, static_cast<u64>(reinterpret_cast<uintptr_t>(addr)));
|
||||
pxAssert(reg.IsX());
|
||||
|
||||
if (armAddressRecorder &&
|
||||
armAddressRecorder->ClassifyMove(addr) == ArmAddressRecorder::MoveForm::CanonicalAbs)
|
||||
{
|
||||
// Fixed-width 16-byte form: every operand bit lives in a movz/movk
|
||||
// imm16 field a relocation patcher can rewrite in place.
|
||||
const u64 v = reinterpret_cast<uintptr_t>(addr);
|
||||
{
|
||||
vixl::ExactAssemblyScope guard(armAsm, 16);
|
||||
armAsm->movz(reg, v & 0xFFFF, 0);
|
||||
armAsm->movk(reg, (v >> 16) & 0xFFFF, 16);
|
||||
armAsm->movk(reg, (v >> 32) & 0xFFFF, 32);
|
||||
armAsm->movk(reg, (v >> 48) & 0xFFFF, 48);
|
||||
}
|
||||
armAddressRecorder->OnCanonicalAbsMove(armGetCurrentCodePointer() - 16, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
const void* current_code_ptr_page = reinterpret_cast<const void*>(
|
||||
reinterpret_cast<uintptr_t>(armGetCurrentCodePointer()) & ~static_cast<uintptr_t>(0xFFF));
|
||||
const void* ptr_page =
|
||||
@@ -261,6 +316,8 @@ void armMoveAddressToReg(const vixl::aarch64::Register& reg, const void* addr)
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->adrp(reg, page_displacement);
|
||||
}
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnAdrp(armGetCurrentCodePointer() - 4, addr);
|
||||
armAsm->Add(reg, reg, page_offset);
|
||||
}
|
||||
else if (vixl::IsInt21(page_displacement) && a64::Assembler::IsImmLogical(page_offset, 64))
|
||||
@@ -269,10 +326,14 @@ void armMoveAddressToReg(const vixl::aarch64::Register& reg, const void* addr)
|
||||
a64::SingleEmissionCheckScope guard(armAsm);
|
||||
armAsm->adrp(reg, page_displacement);
|
||||
}
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnAdrp(armGetCurrentCodePointer() - 4, addr);
|
||||
armAsm->Orr(reg, reg, page_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (armAddressRecorder)
|
||||
armAddressRecorder->OnAbsoluteTarget(addr);
|
||||
armAsm->Mov(reg, reinterpret_cast<uintptr_t>(addr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,14 @@
|
||||
#define RXARG3 vixl::aarch64::x2
|
||||
#define RXARG4 vixl::aarch64::x3
|
||||
|
||||
#define RXVIXLSCRATCH vixl::aarch64::x16
|
||||
#define RWVIXLSCRATCH vixl::aarch64::w16
|
||||
#define RSCRATCHADDR vixl::aarch64::x17
|
||||
#define RXVIXLSCRATCH vixl::aarch64::x16 // Reserved for VIXL internal use — do NOT use in rec code
|
||||
#define RWVIXLSCRATCH vixl::aarch64::w16 // Reserved for VIXL internal use — do NOT use in rec code
|
||||
#define RSCRATCHADDR vixl::aarch64::x17 // Address scratch — removed from VIXL pool in armStartBlock
|
||||
|
||||
// General-purpose value scratch registers for recompiler use.
|
||||
// These are caller-saved and NOT in VIXL's scratch pool.
|
||||
#define RXSCRATCH vixl::aarch64::x8
|
||||
#define RWSCRATCH vixl::aarch64::w8
|
||||
|
||||
#define RQSCRATCH vixl::aarch64::q30
|
||||
#define RDSCRATCH vixl::aarch64::d30
|
||||
@@ -59,12 +64,54 @@ const vixl::aarch64::VRegister& armQRegister(int n);
|
||||
|
||||
class ArmConstantPool;
|
||||
|
||||
// Address-emission observer for the on-disk VU program cache. While a
|
||||
// recorder is attached (mVU code-cache episodes only — see mVUopenCodeCache),
|
||||
// the emit helpers below report every host-address-bearing emission so the
|
||||
// recorder can build a relocation fixup table, and let it force canonical
|
||||
// fixed-width forms where the default encoding couldn't be patched after the
|
||||
// code block moves:
|
||||
// - armMoveAddressToReg of a volatile (heap) target → movz+movk×3 (16 bytes,
|
||||
// patchable) instead of the shortest mov/adrp form.
|
||||
// - armEmitCondBranch to a relocatable target → inverted-cond skip + B imm26
|
||||
// (B.cond's ±1MB imm19 can't survive arbitrary replacement).
|
||||
// `at` arguments are the address of the first emitted instruction of the
|
||||
// reported shape. All hooks are no-ops when no recorder is attached.
|
||||
class ArmAddressRecorder
|
||||
{
|
||||
public:
|
||||
enum class MoveForm
|
||||
{
|
||||
Default, // emit in shortest form; recorder may still log it
|
||||
CanonicalAbs, // emit fixed-width movz+movk×3 so the operand is patchable
|
||||
};
|
||||
|
||||
virtual ~ArmAddressRecorder() = default;
|
||||
|
||||
// armMoveAddressToReg: pick the emission form for `addr`.
|
||||
virtual MoveForm ClassifyMove(const void* addr) = 0;
|
||||
// armMoveAddressToReg emitted the canonical 16-byte movz+movk×3 at `at`.
|
||||
virtual void OnCanonicalAbsMove(u8* at, const void* addr) = 0;
|
||||
// armMoveAddressToReg emitted ADRP (+Add/Orr) at `at`; the page offset is
|
||||
// PC-relative and must be re-paged if this code moves.
|
||||
virtual void OnAdrp(u8* at, const void* addr) = 0;
|
||||
// armEmitJmp/armEmitCall/armEmitCondBranch emitted a direct B/BL imm26 at
|
||||
// `at` targeting `target`.
|
||||
virtual void OnDirectBranch(u8* at, const void* target, bool is_call) = 0;
|
||||
// armEmitCondBranch: return true to force the long (cond-skip + B) form.
|
||||
virtual bool WantsLongCondBranch(const void* target) = 0;
|
||||
// An absolute (movz/movk-materialized) target with no patch site — emitted
|
||||
// by the out-of-range paths of armEmitJmp/armEmitCall/armMoveAddressToReg.
|
||||
// Recorder uses this to verify the target is run-invariant.
|
||||
virtual void OnAbsoluteTarget(const void* target) = 0;
|
||||
};
|
||||
|
||||
static const u32 SP_SCRATCH_OFFSET = 0;
|
||||
|
||||
extern thread_local vixl::aarch64::MacroAssembler* armAsm;
|
||||
extern thread_local u8* armAsmPtr;
|
||||
extern thread_local size_t armAsmCapacity;
|
||||
extern thread_local ArmConstantPool* armConstantPool;
|
||||
extern thread_local ArmAddressRecorder* armAddressRecorder;
|
||||
|
||||
static __fi bool armHasBlock()
|
||||
{
|
||||
@@ -104,6 +151,34 @@ void armGetMemOperandInRegister(const vixl::aarch64::Register& addr_reg,
|
||||
|
||||
void armLoadConstant128(const vixl::aarch64::VRegister& reg, const void* ptr);
|
||||
|
||||
// Pack 4 per-lane bool lanes (each lane is all-1s or 0 — the natural output of
|
||||
// a NEON CMxx / FCMxx against zero) into a 4-bit GPR using the canonical
|
||||
// AArch64 movemask idiom: AND with a per-lane weight vector, ADDV-sum across
|
||||
// lanes, then UMOV to GPR.
|
||||
//
|
||||
// `data` is clobbered (AND in-place, ADDV writes the low S lane in-place).
|
||||
// `tmp` is loaded with the weight vector via the vixl literal pool; must
|
||||
// differ from `data`. Both must be Q-form (128-bit).
|
||||
//
|
||||
// PS2 MAC flag bit order is bit0=W, bit3=X (reverse of NEON lane order). Pass
|
||||
// reverse=true to get that mapping; reverse=false yields lane[i]→bit[i].
|
||||
//
|
||||
// Emits 4 insns: ldr q (literal pool) + and.16b + addv s + umov w.
|
||||
__fi static void armEmitPackLaneBits(const vixl::aarch64::Register& dst,
|
||||
const vixl::aarch64::VRegister& data, const vixl::aarch64::VRegister& tmp,
|
||||
bool reverse)
|
||||
{
|
||||
// Weight vector as u32 lanes [0..3]. low64 packs lanes 0+1, high64 packs 2+3.
|
||||
// forward {1,2,4,8}: low = (2<<32)|1, high = (8<<32)|4
|
||||
// reverse {8,4,2,1}: low = (4<<32)|8, high = (1<<32)|2
|
||||
const u64 low64 = reverse ? 0x0000000400000008ULL : 0x0000000200000001ULL;
|
||||
const u64 high64 = reverse ? 0x0000000100000002ULL : 0x0000000800000004ULL;
|
||||
armAsm->Ldr(tmp, high64, low64);
|
||||
armAsm->And(data.V16B(), data.V16B(), tmp.V16B());
|
||||
armAsm->Addv(vixl::aarch64::VRegister(data.GetCode(), 32), data.V4S());
|
||||
armAsm->Umov(dst, data.V4S(), 0);
|
||||
}
|
||||
|
||||
// may clobber RSCRATCH/RSCRATCH2. they shouldn't be inputs.
|
||||
void armEmitVTBL(const vixl::aarch64::VRegister& dst, const vixl::aarch64::VRegister& src1,
|
||||
const vixl::aarch64::VRegister& src2, const vixl::aarch64::VRegister& tbl);
|
||||
|
||||
+334
-17
@@ -1,30 +1,347 @@
|
||||
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include "arm64/AsmHelpers.h"
|
||||
#include "arm64/iR5900-arm64.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/HostSys.h"
|
||||
#include "Memory.h"
|
||||
#include "MTVU.h"
|
||||
#include "SaveState.h"
|
||||
#include "vtlb.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
|
||||
void vtlb_DynBackpatchLoadStore(uptr code_address, u32 code_size, u32 guest_pc, u32 guest_addr, u32 gpr_bitmask, u32 fpr_bitmask, u8 address_register, u8 data_register, u8 size_in_bits, bool is_signed, bool is_load, bool is_fpr)
|
||||
namespace a64 = vixl::aarch64;
|
||||
|
||||
using namespace vtlb_private;
|
||||
|
||||
void vtlb_DynBackpatchLoadStore(uptr code_address, u32 code_size, u32 guest_pc, u32 guest_addr,
|
||||
u32 gpr_bitmask, u32 fpr_bitmask, u8 address_register, u8 data_register,
|
||||
u8 size_in_bits, bool is_signed, bool is_load, bool is_fpr)
|
||||
{
|
||||
pxFailRel("Not implemented.");
|
||||
DevCon.WriteLn("Backpatching %s at %p[%u] (pc %08X vaddr %08X): GPR %08X FPR %08X Addr %u Data %u Size %u Flags %02X %02X",
|
||||
is_load ? "load" : "store", (void*)code_address, code_size, guest_pc, guest_addr,
|
||||
gpr_bitmask, fpr_bitmask, address_register, data_register, size_in_bits, is_signed, is_load);
|
||||
|
||||
u8* thunk = recBeginThunk();
|
||||
|
||||
// Collect caller-saved GPRs that need saving.
|
||||
// Callee-saved (x19+) are preserved by the C call and don't need saving.
|
||||
// For loads into a GPR, skip the data register (result goes there).
|
||||
static constexpr u32 MAX_SAVE_GPRS = 16;
|
||||
u8 gprs_to_save[MAX_SAVE_GPRS];
|
||||
u32 num_gprs = 0;
|
||||
|
||||
for (u32 i = 0; i < 19; i++)
|
||||
{
|
||||
if (!(gpr_bitmask & (1u << i)))
|
||||
continue;
|
||||
// Skip scratch/reserved: x8 (RWSCRATCH), x16 (VIXL), x17 (RSCRATCHADDR), x18 (platform)
|
||||
if (i == 8 || i >= 16)
|
||||
continue;
|
||||
// For loads into GPR, skip the data register
|
||||
if (is_load && !is_fpr && i == data_register)
|
||||
continue;
|
||||
pxAssert(num_gprs < MAX_SAVE_GPRS);
|
||||
gprs_to_save[num_gprs++] = static_cast<u8>(i);
|
||||
}
|
||||
|
||||
// Collect NEON regs that need saving.
|
||||
// q8-q15 lower 64 bits are callee-saved, but the JIT uses full 128-bit, so save all live ones.
|
||||
static constexpr u32 MAX_SAVE_FPRS = 32;
|
||||
u8 fprs_to_save[MAX_SAVE_FPRS];
|
||||
u32 num_fprs = 0;
|
||||
|
||||
for (u32 i = 0; i < 32; i++)
|
||||
{
|
||||
if (!(fpr_bitmask & (1u << i)))
|
||||
continue;
|
||||
// For loads into FPR, skip the data register
|
||||
if (is_load && is_fpr && i == data_register)
|
||||
continue;
|
||||
pxAssert(num_fprs < MAX_SAVE_FPRS);
|
||||
fprs_to_save[num_fprs++] = static_cast<u8>(i);
|
||||
}
|
||||
|
||||
// Calculate stack size (must be 16-byte aligned)
|
||||
const u32 gpr_save_bytes = num_gprs * 8;
|
||||
const u32 fpr_save_bytes = num_fprs * 16;
|
||||
const u32 stack_size = (gpr_save_bytes + fpr_save_bytes + 15u) & ~15u;
|
||||
|
||||
if (stack_size > 0)
|
||||
armAsm->Sub(a64::sp, a64::sp, stack_size);
|
||||
|
||||
// Save GPRs to stack
|
||||
u32 offset = 0;
|
||||
for (u32 i = 0; i < num_gprs; i++)
|
||||
{
|
||||
armAsm->Str(a64::XRegister(gprs_to_save[i]), a64::MemOperand(a64::sp, offset));
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Save NEON regs to stack
|
||||
for (u32 i = 0; i < num_fprs; i++)
|
||||
{
|
||||
armAsm->Str(a64::QRegister(fprs_to_save[i]), a64::MemOperand(a64::sp, offset));
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
// At this point, all host registers still have their original JIT values
|
||||
// (STR only reads, doesn't modify the source register).
|
||||
|
||||
// Flush cpuRegs.pc and cpuRegs.code for exception handling.
|
||||
// The fastmem path skips iFlushCall, so these may be stale.
|
||||
// If the vtlb handler triggers a TLB miss or other exception,
|
||||
// cpuTlbMiss reads cpuRegs.pc to set EPC.
|
||||
armAsm->Mov(RWSCRATCH, guest_pc);
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.pc));
|
||||
|
||||
armAsm->Mov(RWSCRATCH, *(u32*)PSM(guest_pc));
|
||||
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.code));
|
||||
|
||||
// Set up arguments for the vtlb handler call.
|
||||
|
||||
// 128-bit fastmem path. Always uses q0 (data_register == 0); the JIT
|
||||
// callers in recVTLB-arm64.cpp materialize the load result / store
|
||||
// value into q0 and never allocate q0 to a guest register at the
|
||||
// fastmem emit point. vtlb_memRead128 returns r128 in q0 per AAPCS64;
|
||||
// vtlb_memWrite128 takes value in q0.
|
||||
if (size_in_bits == 128)
|
||||
{
|
||||
pxAssertRel(is_fpr && data_register == 0,
|
||||
"128-bit fastmem backpatch must target q0");
|
||||
|
||||
if (address_register != 9)
|
||||
armAsm->Mov(a64::w9, armWRegister(address_register));
|
||||
|
||||
armAsm->Lsr(a64::w8, a64::w9, VTLB_PAGE_BITS);
|
||||
armMoveAddressToReg(RSCRATCHADDR, vtlb_private::vtlbdata.vmap);
|
||||
armAsm->Ldr(a64::x8, a64::MemOperand(RSCRATCHADDR, a64::x8, a64::LSL, 3));
|
||||
armAsm->Add(a64::x0, a64::x8, a64::Operand(a64::w9, a64::UXTW));
|
||||
|
||||
a64::Label slow_path, done;
|
||||
armAsm->Tbnz(a64::x0, 63, &slow_path);
|
||||
|
||||
if (is_load)
|
||||
armAsm->Ldr(a64::q0, a64::MemOperand(a64::x0));
|
||||
else
|
||||
armAsm->Str(a64::q0, a64::MemOperand(a64::x0));
|
||||
armAsm->B(&done);
|
||||
|
||||
armAsm->Bind(&slow_path);
|
||||
armAsm->Mov(a64::w0, a64::w9);
|
||||
// Spill/reload RECCYCLE around the vtlb handler call — the slow path
|
||||
// dispatches to MMIO handlers (hwRead*/hwWrite*) which read/write
|
||||
// cpuRegs.cycle (timer regs, IntCHackCheck, etc.). Without this,
|
||||
// the handler sees a stale cycle value, which can mis-schedule
|
||||
// events and cause cascading mid-block timing bugs. Matches the
|
||||
// pattern at recVTLB-arm64.cpp:112+120.
|
||||
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
if (is_load)
|
||||
armEmitCall((void*)vtlb_memRead128);
|
||||
else
|
||||
armEmitCall((void*)vtlb_memWrite128);
|
||||
armAsm->Ldr(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
|
||||
armAsm->Bind(&done);
|
||||
}
|
||||
else if (is_load)
|
||||
{
|
||||
// Load backpatch: emit inline VTLB read code (same as vtlbSoftmemRead).
|
||||
if (address_register != 9)
|
||||
armAsm->Mov(a64::w9, armWRegister(address_register));
|
||||
|
||||
// Inline VTLB lookup
|
||||
armAsm->Lsr(a64::w8, a64::w9, VTLB_PAGE_BITS);
|
||||
armMoveAddressToReg(RSCRATCHADDR, vtlb_private::vtlbdata.vmap);
|
||||
armAsm->Ldr(a64::x8, a64::MemOperand(RSCRATCHADDR, a64::x8, a64::LSL, 3));
|
||||
armAsm->Add(a64::x0, a64::x8, a64::Operand(a64::w9, a64::UXTW));
|
||||
|
||||
a64::Label slow_path, done;
|
||||
armAsm->Tbnz(a64::x0, 63, &slow_path);
|
||||
|
||||
// Fast path: direct memory read via resolved host pointer
|
||||
switch (size_in_bits)
|
||||
{
|
||||
case 8:
|
||||
if (is_signed)
|
||||
armAsm->Ldrsb(a64::x0, a64::MemOperand(a64::x0));
|
||||
else
|
||||
armAsm->Ldrb(a64::w0, a64::MemOperand(a64::x0));
|
||||
break;
|
||||
case 16:
|
||||
if (is_signed)
|
||||
armAsm->Ldrsh(a64::x0, a64::MemOperand(a64::x0));
|
||||
else
|
||||
armAsm->Ldrh(a64::w0, a64::MemOperand(a64::x0));
|
||||
break;
|
||||
case 32:
|
||||
if (is_signed)
|
||||
armAsm->Ldrsw(a64::x0, a64::MemOperand(a64::x0));
|
||||
else
|
||||
armAsm->Ldr(a64::w0, a64::MemOperand(a64::x0));
|
||||
break;
|
||||
case 64:
|
||||
armAsm->Ldr(a64::x0, a64::MemOperand(a64::x0));
|
||||
break;
|
||||
default: pxFailRel("Unsupported load size in backpatch"); break;
|
||||
}
|
||||
armAsm->B(&done);
|
||||
|
||||
// Slow path: call vtlb_memRead handler
|
||||
armAsm->Bind(&slow_path);
|
||||
armAsm->Mov(a64::w0, a64::w9);
|
||||
// Spill/reload RECCYCLE — see 128-bit slow_path above for rationale.
|
||||
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
switch (size_in_bits)
|
||||
{
|
||||
case 8: armEmitCall((void*)vtlb_memRead<mem8_t>); break;
|
||||
case 16: armEmitCall((void*)vtlb_memRead<mem16_t>); break;
|
||||
case 32: armEmitCall((void*)vtlb_memRead<mem32_t>); break;
|
||||
case 64: armEmitCall((void*)vtlb_memRead<mem64_t>); break;
|
||||
default: break;
|
||||
}
|
||||
armAsm->Ldr(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
// Extend the handler return into x0 for the 64-bit cpuRegs.GPR store.
|
||||
// AAPCS64 leaves the upper bits of x0 unspecified for sub-word returns,
|
||||
// so UNSIGNED sub-64-bit loads must Uxtw too — otherwise the garbage
|
||||
// upper 32 bits leak into the 64-bit EE GPR (LWU/LBU/LHU faulting to an
|
||||
// MMIO/handler page). The fast inline path zero-extends via Ldrb/Ldrh/
|
||||
// Ldr w0; this mirrors that, and the const-paddr shortcut in
|
||||
// recVTLB-arm64.cpp which handles the identical hazard.
|
||||
if (size_in_bits < 64)
|
||||
{
|
||||
if (is_signed)
|
||||
{
|
||||
if (size_in_bits == 8)
|
||||
armAsm->Sxtb(a64::x0, a64::w0);
|
||||
else if (size_in_bits == 16)
|
||||
armAsm->Sxth(a64::x0, a64::w0);
|
||||
else if (size_in_bits == 32)
|
||||
armAsm->Sxtw(a64::x0, a64::w0);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Uxtw(a64::x0, a64::w0);
|
||||
}
|
||||
}
|
||||
|
||||
armAsm->Bind(&done);
|
||||
|
||||
// Move result to data register
|
||||
if (!is_fpr)
|
||||
{
|
||||
if (data_register != 0)
|
||||
armAsm->Mov(armXRegister(data_register), a64::x0);
|
||||
}
|
||||
else
|
||||
{
|
||||
armAsm->Fmov(a64::SRegister(data_register), a64::w0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store backpatch: emit inline VTLB write code (same as vtlbSoftmemWrite),
|
||||
// emitting the inline VTLB lookup + store rather than calling vtlb_memWrite.
|
||||
// Move address to w9, value to w10 (standard scratch for inline VTLB).
|
||||
if (address_register != 9)
|
||||
armAsm->Mov(a64::w9, armWRegister(address_register));
|
||||
if (data_register != 10)
|
||||
{
|
||||
if (size_in_bits <= 32)
|
||||
armAsm->Mov(a64::w10, armWRegister(data_register));
|
||||
else
|
||||
armAsm->Mov(a64::x10, armXRegister(data_register));
|
||||
}
|
||||
|
||||
// Inline VTLB lookup: vmap[addr >> PAGE_BITS] → ppf
|
||||
armAsm->Lsr(a64::w8, a64::w9, VTLB_PAGE_BITS);
|
||||
armMoveAddressToReg(RSCRATCHADDR, vtlb_private::vtlbdata.vmap);
|
||||
armAsm->Ldr(a64::x8, a64::MemOperand(RSCRATCHADDR, a64::x8, a64::LSL, 3));
|
||||
armAsm->Add(a64::x0, a64::x8, a64::Operand(a64::w9, a64::UXTW));
|
||||
|
||||
a64::Label slow_path, done;
|
||||
armAsm->Tbnz(a64::x0, 63, &slow_path);
|
||||
|
||||
// Fast path: direct memory write via resolved host pointer
|
||||
switch (size_in_bits)
|
||||
{
|
||||
case 8: armAsm->Strb(a64::w10, a64::MemOperand(a64::x0)); break;
|
||||
case 16: armAsm->Strh(a64::w10, a64::MemOperand(a64::x0)); break;
|
||||
case 32: armAsm->Str(a64::w10, a64::MemOperand(a64::x0)); break;
|
||||
case 64: armAsm->Str(a64::x10, a64::MemOperand(a64::x0)); break;
|
||||
default: pxFailRel("Unsupported store size in backpatch"); break;
|
||||
}
|
||||
armAsm->B(&done);
|
||||
|
||||
// Slow path: call vtlb_memWrite handler
|
||||
armAsm->Bind(&slow_path);
|
||||
armAsm->Mov(a64::w0, a64::w9);
|
||||
if (size_in_bits <= 32)
|
||||
armAsm->Mov(a64::w1, a64::w10);
|
||||
else
|
||||
armAsm->Mov(a64::x1, a64::x10);
|
||||
|
||||
// Spill/reload RECCYCLE — see 128-bit slow_path above for rationale.
|
||||
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
switch (size_in_bits)
|
||||
{
|
||||
case 8: armEmitCall((void*)vtlb_memWrite<mem8_t>); break;
|
||||
case 16: armEmitCall((void*)vtlb_memWrite<mem16_t>); break;
|
||||
case 32: armEmitCall((void*)vtlb_memWrite<mem32_t>); break;
|
||||
case 64: armEmitCall((void*)vtlb_memWrite<mem64_t>); break;
|
||||
default: pxFailRel("Unsupported store size in backpatch"); break;
|
||||
}
|
||||
armAsm->Ldr(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
|
||||
|
||||
armAsm->Bind(&done);
|
||||
}
|
||||
|
||||
// Restore GPRs from stack
|
||||
offset = 0;
|
||||
for (u32 i = 0; i < num_gprs; i++)
|
||||
{
|
||||
armAsm->Ldr(a64::XRegister(gprs_to_save[i]), a64::MemOperand(a64::sp, offset));
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Restore NEON regs from stack
|
||||
for (u32 i = 0; i < num_fprs; i++)
|
||||
{
|
||||
armAsm->Ldr(a64::QRegister(fprs_to_save[i]), a64::MemOperand(a64::sp, offset));
|
||||
offset += 16;
|
||||
}
|
||||
|
||||
if (stack_size > 0)
|
||||
armAsm->Add(a64::sp, a64::sp, stack_size);
|
||||
|
||||
// Branch back to the instruction after the faulting load/store
|
||||
armEmitJmp((void*)(code_address + code_size));
|
||||
|
||||
u8* thunk_end = recEndThunk();
|
||||
|
||||
// Flush instruction cache for the ENTIRE thunk.
|
||||
// ARM64 icache is not coherent with dcache — without this, the CPU may
|
||||
// execute stale instructions from previously compiled code at the thunk's
|
||||
// address, causing SIGILL or corruption.
|
||||
HostSys::FlushInstructionCache(thunk, static_cast<u32>(thunk_end - thunk));
|
||||
|
||||
// Patch the faulting instruction with a B (branch) to the thunk.
|
||||
// ARM64 B instruction: 0x14000000 | imm26, where imm26 = byte_offset / 4
|
||||
const s64 branch_offset = static_cast<s64>(thunk - reinterpret_cast<u8*>(code_address));
|
||||
pxAssert((branch_offset & 3) == 0);
|
||||
const s64 branch_imm26 = branch_offset >> 2;
|
||||
pxAssertRel(branch_imm26 >= -0x2000000 && branch_imm26 <= 0x1FFFFFF,
|
||||
"Backpatch thunk too far from faulting instruction for B instruction");
|
||||
|
||||
HostSys::BeginCodeWrite();
|
||||
u32* patch_ptr = reinterpret_cast<u32*>(code_address);
|
||||
*patch_ptr = 0x14000000u | (static_cast<u32>(branch_imm26) & 0x03FFFFFFu);
|
||||
HostSys::EndCodeWrite();
|
||||
|
||||
// Flush icache at the patch point too.
|
||||
HostSys::FlushInstructionCache(reinterpret_cast<void*>(code_address), 4);
|
||||
}
|
||||
|
||||
bool SaveStateBase::vuJITFreeze()
|
||||
{
|
||||
if(IsSaving())
|
||||
vu1Thread.WaitVU();
|
||||
|
||||
Console.Warning("recompiler state is stubbed in arm64!");
|
||||
|
||||
// HACK!!
|
||||
|
||||
// size of microRegInfo structure
|
||||
std::array<u8,96> empty_data{};
|
||||
Freeze(empty_data);
|
||||
Freeze(empty_data);
|
||||
return true;
|
||||
}
|
||||
// vuJITFreeze() is defined in microVU-arm64.cpp
|
||||
|
||||
+16
-2
@@ -516,7 +516,8 @@ static __ri void vtlb_Miss(u32 addr, u32 mode)
|
||||
if (EmuConfig.Gamefixes.GoemonTlbHack)
|
||||
GoemonTlbMissDebug();
|
||||
|
||||
// Hack to handle expected tlb miss by some games.
|
||||
// Interpreter: raise the exception, then CancelInstruction stops the current
|
||||
// instruction so the exception vector is dispatched immediately.
|
||||
if (Cpu == &intCpu)
|
||||
{
|
||||
if (mode)
|
||||
@@ -524,7 +525,6 @@ static __ri void vtlb_Miss(u32 addr, u32 mode)
|
||||
else
|
||||
cpuTlbMissR(addr, cpuRegs.branch);
|
||||
|
||||
// Exception handled. Current instruction need to be stopped
|
||||
Cpu->CancelInstruction();
|
||||
return;
|
||||
}
|
||||
@@ -539,9 +539,23 @@ static __ri void vtlb_Miss(u32 addr, u32 mode)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __aarch64__
|
||||
// arm64 recompiler: raise the TLB-miss exception here. cpuTlbMissR/W sets
|
||||
// cpuRegs.pc to the exception vector, which the rec picks up at the next
|
||||
// dispatch — no CancelInstruction longjmp (which is interpreter-only; the
|
||||
// arm64 rec returns and lets the exception state take effect at block end).
|
||||
if (mode)
|
||||
cpuTlbMissW(addr, cpuRegs.branch);
|
||||
else
|
||||
cpuTlbMissR(addr, cpuRegs.branch);
|
||||
#else
|
||||
// x86 recompiler: upstream behavior — log and continue without raising
|
||||
// (x86 recCancelInstruction is a stub, so the arm64 exception path above
|
||||
// must not run here).
|
||||
static int spamStop = 0;
|
||||
if (spamStop++ < 50 || IsDevBuild)
|
||||
Console.Error(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
// BusError exception: more serious than a TLB miss. If properly emulated the PS2 kernel
|
||||
|
||||
+5
-2
@@ -121,7 +121,10 @@ namespace vtlb_private
|
||||
static const uint VTLB_PAGE_MASK = 4095;
|
||||
static const uint VTLB_PAGE_SIZE = 4096;
|
||||
|
||||
static const uint VTLB_PMAP_SZ = _1mb * 512;
|
||||
// Physical map covers 1GB to include RAM mirrors at 0x20000000 (uncached)
|
||||
// and 0x30000000 (uncached & accelerated) used by BIOS InitRDRAM.
|
||||
// 1GB is sufficient for all known PS2 mappings.
|
||||
static const uint VTLB_PMAP_SZ = _1mb * 1024;
|
||||
static const uint VTLB_PMAP_ITEMS = VTLB_PMAP_SZ / VTLB_PAGE_SIZE;
|
||||
static const uint VTLB_VMAP_ITEMS = _4gb / VTLB_PAGE_SIZE;
|
||||
|
||||
@@ -189,7 +192,7 @@ namespace vtlb_private
|
||||
// third indexer -- 128 possible handlers!
|
||||
void* RWFT[5][2][VTLB_HANDLER_ITEMS];
|
||||
|
||||
VTLBPhysical pmap[VTLB_PMAP_ITEMS]; //512KB // PS2 physical to x86 physical
|
||||
VTLBPhysical pmap[VTLB_PMAP_ITEMS]; //2MB (VTLB_PMAP_ITEMS * sizeof(VTLBPhysical)) // PS2 physical to host physical
|
||||
|
||||
VTLBVirtual* vmap; //4MB (allocated by vtlb_init) // PS2 virtual to x86 physical
|
||||
|
||||
|
||||
Reference in New Issue
Block a user