arm64: EE (R5900) recompiler

Full ARM64 EE dynarec — dispatcher, iFlushCall, block emitter and opcode subgroups
(Arit/Branch/Jump/LoadStore/Misc/Move/MultDiv/Shift/Templates), the COP0/COP2/FPU/MMI
coprocessor codegen, the register allocator core (iCore), VTLB codegen, and the EE
block-analysis pass. EE GPRs are allocated in NEON registers, per the approach used
in a reference ARM64 PS2 implementation.

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:
Brian Degenhardt
2026-06-20 20:27:55 -07:00
co-authored by Ryan Walklin Claude Opus 4.8
parent 6799ab0d2d
commit ac8258a950
24 changed files with 13794 additions and 1 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ namespace R5900
{
// Generates an entry for the given opcode name.
// Assumes the default function naming schemes for interpreter and recompiler functions.
#ifdef _M_X86 // TODO(Stenzek): Remove me once EE/VU/IOP recs are added.
#if defined(_M_X86) || defined(ARCH_ARM64) // ARM64 EE recompiler is supported
# define MakeOpcode( name, cycles, flags ) \
static const OPCODE name = { \
#name, \
+212
View File
@@ -0,0 +1,212 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64-specific BaseBlocks with block linking that stays signal-safe.
//
// x86 BaseBlocks (pcsx2/x86/BaseblockEx.h) uses a std::multimap to track
// pending link sites and rewrites them whenever a block is added or
// removed. The multimap is not used here because Remove() runs from the
// SIGSEGV fastmem handler, where mutating an STL container is unsafe.
//
// This implementation supports block linking while keeping Remove() signal-safe:
// - Link()/New() touch the multimap, but they only run from the
// compile path (single-threaded, never from a signal).
// - Remove() does NOT walk the link map. Instead it overwrites the
// first 4 bytes of each removed block with `B JITCompile`, so any
// stale link still resolves correctly via the dispatcher (which
// can re-patch the link to the freshly compiled target on its next
// dispatch). Block memory isn't reclaimed until a full reset, so
// this 4-byte rewrite always lands on memory the recompiler still
// owns.
//
// The patch site for each link is the address of a single B instruction
// emitted by SetBranchImm (see iR5900-arm64.cpp). Aligned 32-bit stores
// are atomic on AArch64, and `cacheflush(2)` on the patch site is
// async-signal-safe, so the redirect-stub Remove() write is signal-safe.
//
// Range: B imm26 covers ±128 MB. The EE recompiler region is 64 MB
// (HostMemoryMap::EErecSize), and JITCompile lives in the same region,
// so all link sites are reachable with a single B.
#pragma once
#include <map>
#include "x86/BaseblockEx.h" // BASEBLOCK, BASEBLOCKEX, BaseBlockArray, recLUT_SetPage
class Arm64BaseBlocks
{
protected:
using linkmap_t = std::multimap<u32, uptr>;
BaseBlockArray blocks;
linkmap_t links;
uptr jitcompile = 0;
// Encode a B imm26 from `site` to `target`. ARM64 B encoding:
// bits[31:26] = 000101
// bits[25:0] = sign-extended ((target - site) >> 2)
static u32 EncodeB(uptr site, uptr target)
{
const intptr_t off = static_cast<intptr_t>(target) - static_cast<intptr_t>(site);
pxAssertRel((off & 3) == 0, "Branch offset not 4-byte aligned");
const intptr_t imm26 = off >> 2;
pxAssertRel(imm26 >= -(1 << 25) && imm26 < (1 << 25), "Branch offset out of B imm26 range");
return 0x14000000u | (static_cast<u32>(imm26) & 0x03FFFFFFu);
}
static void PatchAtomic(uptr site, u32 instr)
{
// 4-byte aligned word stores are atomic on AArch64.
*reinterpret_cast<volatile u32*>(site) = instr;
// Then make sure cores fetching instructions see the new word.
__builtin___clear_cache(reinterpret_cast<char*>(site),
reinterpret_cast<char*>(site) + 4);
}
public:
Arm64BaseBlocks()
: blocks(0x4000)
{
}
void SetJITCompile(const void* recompiler_)
{
jitcompile = reinterpret_cast<uptr>(recompiler_);
}
// Register a link site that wants to branch directly to the block at
// `pc`. Patches immediately if a block already exists; otherwise
// records the site so New(pc, ...) can patch it later.
void Link(u32 pc, void* patch_site)
{
pxAssertRel(jitcompile, "SetJITCompile() must be called before Link()");
BASEBLOCKEX* target = Get(pc);
const uptr target_addr = (target && target->startpc == pc)
? target->fnptr : jitcompile;
PatchAtomic(reinterpret_cast<uptr>(patch_site),
EncodeB(reinterpret_cast<uptr>(patch_site), target_addr));
links.insert({pc, reinterpret_cast<uptr>(patch_site)});
}
BASEBLOCKEX* New(u32 startpc, uptr fnptr)
{
// Patch any pending links waiting for a block at this PC. After
// patching they go directly to fnptr instead of routing through
// JITCompile.
const auto range = links.equal_range(startpc);
for (auto it = range.first; it != range.second; ++it)
PatchAtomic(it->second, EncodeB(it->second, fnptr));
return blocks.insert(startpc, fnptr);
}
int LastIndex(u32 startpc) const
{
if (blocks.size() == 0)
return -1;
int imin = 0, imax = (int)blocks.size() - 1, imid;
while (imin != imax)
{
imid = (imin + imax + 1) >> 1;
if (blocks[imid].startpc > startpc)
imax = imid - 1;
else
imin = imid;
}
if (IsDevBuild)
{
if (imin != 0)
pxAssert(blocks[imin].startpc <= startpc);
if (imin < (int)blocks.size() - 1)
pxAssert(blocks[imin + 1].startpc > startpc);
}
return imin;
}
__fi int Index(u32 startpc) const
{
int idx = LastIndex(startpc);
if ((idx == -1) || (startpc < blocks[idx].startpc) ||
((blocks[idx].size) && (startpc >= blocks[idx].startpc + blocks[idx].size * 4)))
return -1;
else
return idx;
}
__fi BASEBLOCKEX* operator[](int idx)
{
if (idx < 0 || idx >= (int)blocks.size())
return 0;
return &blocks[idx];
}
__fi BASEBLOCKEX* Get(u32 startpc)
{
return (*this)[Index(startpc)];
}
// Signal-safe: writes a redirect stub at each removed block's entry
// point so any stale link still resolves through JITCompile, then
// erases from the flat sorted array. Does NOT touch the link map —
// stale entries there are harmless (they just trigger a re-patch on
// the next compile cycle for the same PC).
__fi void Remove(int first, int last)
{
pxAssert(first <= last);
if (jitcompile)
{
for (int i = first; i <= last; ++i)
{
const uptr site = blocks[i].fnptr;
PatchAtomic(site, EncodeB(site, jitcompile));
}
}
blocks.erase(first, last + 1);
}
__fi void Reset()
{
blocks.clear();
links.clear();
}
#ifdef PCSX2_RECOMPILER_TESTS
// Test-only introspection. Returns true iff a link patch site within the
// block containing src_pc targets a block at dst_pc. The link multimap
// is keyed by destination PC, so the entries for dst_pc are walked to check
// whether the patch site lies inside [block.fnptr, block.fnptr + x86size),
// where x86size is BASEBLOCKEX's (legacy-named) host machine-code byte size.
// O(L_d + log B) where L_d is the number of links to dst_pc and B is the
// block count.
bool IsLinked(u32 src_pc, u32 dst_pc) const
{
const int idx = LastIndex(src_pc);
if (idx < 0)
return false;
const BASEBLOCKEX& b = blocks[idx];
if (src_pc < b.startpc || src_pc >= b.startpc + b.size * 4)
return false;
const uptr lo = b.fnptr;
const uptr hi = b.fnptr + b.x86size;
const auto range = links.equal_range(dst_pc);
for (auto it = range.first; it != range.second; ++it)
{
if (it->second >= lo && it->second < hi)
return true;
}
return false;
}
#endif
};
+325
View File
@@ -0,0 +1,325 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE COP0 Instruction Codegen — NEON-based
#include "arm64/iR5900-arm64.h"
#include "arm64/AsmHelpers.h"
#include "Hw.h"
#include "Memory.h"
namespace a64 = vixl::aarch64;
namespace Interp = R5900::Interpreter::OpcodeImpl::COP0;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace COP0 {
// COP0 branch (BC0F/T/FL/TL) — native DMAC-condition test. The branch
// condition is (((DMAC_STAT | ~DMAC_PCR) & 0x3ff) == 0x3ff): BC0T branches
// when true, BC0F when false. Emitting the test inline avoids a costly
// iFlushCall + interpreter dispatch per iteration in DMAC-poll spin loops.
// BC0 uses the same SaveBranchState/recompileNextInstruction/SetBranchImm
// branch-emission pattern as recBC1F/recBC1T on this target.
// Emit the DMAC condition test, leaving the result in the flags (CMP vs 0x3ff).
// 32-bit loads are fine: the result is masked to the low 10 bits, matching x86.
static void _setupBranchTestBC0()
{
_eeFlushAllDirty();
armLoadPtr(RWARG1, &psHu32(DMAC_PCR));
armAsm->Mvn(RWARG1, RWARG1); // ~PCR
armLoadPtr(RWSCRATCH, &psHu32(DMAC_STAT));
armAsm->Orr(RWARG1, RWARG1, RWSCRATCH); // STAT | ~PCR
armAsm->And(RWARG1, RWARG1, 0x3ff);
armAsm->Cmp(RWARG1, 0x3ff); // EQ ⇔ condition true
}
static a64::Label* s_pBC0Label = nullptr;
static void recSetBranchBC0(bool branchOnTrue)
{
_setupBranchTestBC0();
s_pBC0Label = new a64::Label();
// Emit the "skip the taken-branch" jump: the negation of the branch cond.
if (branchOnTrue)
armAsm->B(s_pBC0Label, a64::ne); // BC0T: skip (fall through) when condition false
else
armAsm->B(s_pBC0Label, a64::eq); // BC0F: skip when condition true
}
static void recBindBC0Label()
{
armAsm->Bind(s_pBC0Label);
delete s_pBC0Label;
s_pBC0Label = nullptr;
}
void recBC0F()
{
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
const bool swap = TrySwapDelaySlot(0, 0, 0, false);
recSetBranchBC0(false);
if (!swap)
{
SaveBranchState();
recompileNextInstruction(true, false);
}
SetBranchImm(branchTo);
recBindBC0Label();
if (!swap)
{
pc -= 4;
LoadBranchState();
recompileNextInstruction(true, false);
}
SetBranchImm(pc);
}
void recBC0T()
{
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
const bool swap = TrySwapDelaySlot(0, 0, 0, false);
recSetBranchBC0(true);
if (!swap)
{
SaveBranchState();
recompileNextInstruction(true, false);
}
SetBranchImm(branchTo);
recBindBC0Label();
if (!swap)
{
pc -= 4;
LoadBranchState();
recompileNextInstruction(true, false);
}
SetBranchImm(pc);
}
void recBC0FL()
{
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
recSetBranchBC0(false);
SaveBranchState();
recompileNextInstruction(true, false);
SetBranchImm(branchTo);
recBindBC0Label();
LoadBranchState();
SetBranchImm(pc);
}
void recBC0TL()
{
const u32 branchTo = ((s32)_Imm_ * 4) + pc;
recSetBranchBC0(true);
SaveBranchState();
recompileNextInstruction(true, false);
SetBranchImm(branchTo);
recBindBC0Label();
LoadBranchState();
SetBranchImm(pc);
}
REC_FUNC(TLBR);
REC_FUNC(TLBP);
REC_FUNC(TLBWI);
REC_FUNC(TLBWR);
REC_SYS(ERET);
REC_SYS(EI);
// DI — inline, non-branching. Unlike EI (which must branch so newly-enabled
// interrupts fire), disabling interrupts needs no block exit, so DI is emitted
// inline and the block stays open. Mirrors x86 iCOP0.cpp. The next instruction
// is recompiled BEFORE applying DI so the interrupt-mask change takes effect one
// instruction late (fixes booting in Jak X, Namco 50th, Spongebob, The
// Incredibles, etc.).
void recDI()
{
if (!g_recompilingDelaySlot)
recompileNextInstruction(false, false); // DI delayed by one instruction
// Clear Status.EIE (bit 16) unless in user mode with no exception level:
// clear iff (EXL|ERL|EDI) set OR KSU == 0 (kernel). Matches Interp::DI
// (COP0.cpp:708-717) and the x86 TEST 0x20006 / TEST 0x18 guard.
a64::Label doClear;
a64::Label done;
armLoadEERegPtr(RWSCRATCH, &cpuRegs.CP0.r[12]); // Status
armAsm->Tst(RWSCRATCH, 0x20006); // EXL | ERL | EDI
armAsm->B(&doClear, a64::ne);
armAsm->Tst(RWSCRATCH, 0x18); // KSU (non-zero => user mode)
armAsm->B(&done, a64::ne);
armAsm->Bind(&doClear);
armAsm->And(RWSCRATCH, RWSCRATCH, ~static_cast<u32>(0x10000)); // clear EIE
armStoreEERegPtr(RWSCRATCH, &cpuRegs.CP0.r[12]);
armAsm->Bind(&done);
}
#ifdef FORCE_INTERP_COP0
REC_FUNC(MFC0);
REC_FUNC(MTC0);
#else
// Apply pending block cycles to RECCYCLE and flush to cpuRegs.cycle so the
// interpreter helper (which reads cpuRegs.cycle directly) sees the right
// value. The helper is called inline mid-block (not via DispatcherEvent),
// so the caller must follow up with emitReloadCycle() once it returns to
// keep RECCYCLE in sync with anything the helper wrote.
static void emitFlushBlockCycles()
{
u32 cycles = scaleblockcycles_clear();
if (cycles != 0)
armAsm->Add(RECCYCLE, RECCYCLE, cycles);
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
}
static void emitReloadCycle()
{
armAsm->Ldr(RECCYCLE, armCpuRegMem(&cpuRegs.cycle));
}
// MFC0: rt = sign_extend(COP0[rd])
void recMFC0()
{
// Count (rd=9) must still tick even when writing to $zero — interpreter
// MFC0 increments CP0.n.Count and updates lastCOP0Cycle before checking
// _Rt_. Match that gate exactly.
if (!_Rt_ && _Rd_ != 9)
return;
switch (_Rd_)
{
case 9: // Count — inline cycle update; no iFlushCall/interp call
{ // (matches interp COP0.cpp:564-575).
// Bring cpuRegs.cycle / RECCYCLE current first (Count reads it).
emitFlushBlockCycles(); // RECCYCLE (x25) == updated cycle, also in memory
// incr = cycle - lastCOP0Cycle; if (incr == 0) incr++; (interp :566-568)
armAsm->Ldr(RXSCRATCH, armCpuRegMem(&cpuRegs.lastCOP0Cycle));
armAsm->Sub(RXSCRATCH, RECCYCLE, RXSCRATCH);
armAsm->Cmp(RXSCRATCH, 0);
armAsm->Csinc(RXSCRATCH, RXSCRATCH, a64::xzr, a64::ne); // 0 -> 1
// CP0.n.Count += incr (32-bit register; low 32 of incr)
armAsm->Ldr(RWARG1, armCpuRegMem(&cpuRegs.CP0.r[9]));
armAsm->Add(RWARG1, RWARG1, RWSCRATCH);
armAsm->Str(RWARG1, armCpuRegMem(&cpuRegs.CP0.r[9]));
// lastCOP0Cycle = cycle (interp :569)
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.lastCOP0Cycle));
if (!_Rt_)
return;
// rt = sign_extend(CP0.r[9]) (interp :571-577)
_deleteEEreg(_Rt_, 0);
GPR_DEL_CONST(_Rt_);
armLoadEERegPtr(RWSCRATCH, &cpuRegs.CP0.r[9]);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
return;
}
case 25: // Performance counters — cycle-dependent
iFlushCall(FLUSH_INTERPRETER);
emitFlushBlockCycles();
armEmitCall((void*)Interp::MFC0);
emitReloadCycle();
return;
case 24: // Debug breakpoint register — ignore
return;
case 12: // Status — mask reserved bits, matching interp MFC0 case 12 (COP0.cpp)
_deleteEEreg(_Rt_, 0);
GPR_DEL_CONST(_Rt_);
armLoadEERegPtr(RWSCRATCH, &cpuRegs.CP0.r[_Rd_]);
// 0xf0c79c1f is not a valid AArch64 logical immediate, so materialize it
// in a scratch register. Use RWARG1 (caller-saved, dead here), not the
// reserved address scratch x17 (RSCRATCHADDR), which armLoad*Ptr clobbers.
armAsm->Mov(RWARG1, 0xf0c79c1fu);
armAsm->And(RWSCRATCH, RWSCRATCH, RWARG1);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
return;
default:
// Simple case: rt = sign_extend(cpuRegs.CP0.r[rd])
_deleteEEreg(_Rt_, 0);
GPR_DEL_CONST(_Rt_);
armLoadEERegPtr(RWSCRATCH, &cpuRegs.CP0.r[_Rd_]);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
return;
}
}
// MTC0: COP0[rd] = rt
void recMTC0()
{
switch (_Rd_)
{
case 9: // Count — inline; no iFlushCall/interp call
{ // (matches interp COP0.cpp:583-585): lastCOP0Cycle = cycle;
// CP0.r[9] = rt[31:0]. Bring cycle current first.
emitFlushBlockCycles(); // RECCYCLE (x25) == updated cycle, also in memory
armAsm->Str(RECCYCLE, armCpuRegMem(&cpuRegs.lastCOP0Cycle));
if (GPR_IS_CONST1(_Rt_))
{
armAsm->Mov(RWSCRATCH, g_cpuConstRegs[_Rt_].UL[0]);
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.CP0.r[9]));
}
else
{
_deleteEEreg(_Rt_, 1);
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.CP0.r[9]));
}
return;
}
case 12: // Status — has side effects (interrupt check)
case 16: // Config — has side effects
case 25: // Performance counters
iFlushCall(FLUSH_INTERPRETER);
emitFlushBlockCycles();
armEmitCall((void*)Interp::MTC0);
emitReloadCycle();
return;
case 24: // Debug breakpoint register — log-only in interp (COP0.cpp:599-601)
return;
default:
// Simple case: cpuRegs.CP0.r[rd] = rt[31:0]
if (GPR_IS_CONST1(_Rt_))
{
armAsm->Mov(RWSCRATCH, g_cpuConstRegs[_Rt_].UL[0]);
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.CP0.r[_Rd_]));
}
else
{
_deleteEEreg(_Rt_, 1);
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]);
armAsm->Str(RWSCRATCH, armCpuRegMem(&cpuRegs.CP0.r[_Rd_]));
}
return;
}
}
#endif // !FORCE_INTERP_COP0
} // namespace COP0
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+327
View File
@@ -0,0 +1,327 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "arm64/AsmHelpers.h"
#include "VUmicro.h"
// ARM64 Register Allocator
// Mirrors the x86 allocator in x86/iCore.h but adapted for ARM64 register conventions.
//#define RALOG(...) fprintf(stderr, __VA_ARGS__)
#define RALOG(...)
////////////////////////////////////////////////////////////////////////////////
// Shared Register Allocation Flags (same as x86 — shared with instruction codegen)
#define MODE_READ 1
#define MODE_WRITE 2
#define MODE_CALLEESAVED 0x20
#define MODE_COP2 0x40
#define PROCESS_EE_XMM 0x02
#define PROCESS_EE_S 0x04
#define PROCESS_EE_T 0x08
#define PROCESS_EE_D 0x10
#define PROCESS_EE_LO 0x40
#define PROCESS_EE_HI 0x80
#define PROCESS_EE_ACC 0x40
// Extract host register index from info bitmask.
// ARM64 needs 5 bits per field (registers 0-28), unlike x86 which uses 4 bits (0-15).
// Fields are packed into a 32-bit info word with 5-bit register indices.
//
// NOTE: EEREC_LO, EEREC_HI and EEREC_ACC intentionally decode the SAME field
// (bits 23..27). Five distinct 5-bit fields plus the presence flags overflow the
// 32-bit word, so LO/HI/ACC share one slot. This is only sound because no op needs
// two of them live simultaneously through the allocator: integer MULT/DIV/MADD and
// PMFHL load LO/HI directly from memory (bypassing the allocator), and ACC is used
// exclusively by FPU ops (never alongside LO/HI). eeRecompileCodeXMM asserts the one
// dangerous combination (LO+HI); an op needing both must bypass the allocator too.
#define EEREC_S (((info) >> 8) & 0x1f)
#define EEREC_T (((info) >> 13) & 0x1f)
#define EEREC_D (((info) >> 18) & 0x1f)
#define EEREC_LO (((info) >> 23) & 0x1f)
#define EEREC_HI (((info) >> 23) & 0x1f)
#define EEREC_ACC (((info) >> 23) & 0x1f)
#define PROCESS_EE_SET_S(reg) (((reg) << 8) | PROCESS_EE_S)
#define PROCESS_EE_SET_T(reg) (((reg) << 13) | PROCESS_EE_T)
#define PROCESS_EE_SET_D(reg) (((reg) << 18) | PROCESS_EE_D)
#define PROCESS_EE_SET_LO(reg) (((reg) << 23) | PROCESS_EE_LO)
#define PROCESS_EE_SET_HI(reg) (((reg) << 23) | PROCESS_EE_HI)
#define PROCESS_EE_SET_ACC(reg) (((reg) << 23) | PROCESS_EE_ACC)
#define PROCESS_CONSTS 1
#define PROCESS_CONSTT 2
////////////////////////////////////////////////////////////////////////////////
// NEON (128-bit) Register Allocation — equivalent to XMM on x86
enum xmminfo : u16
{
XMMINFO_READLO = 0x001,
XMMINFO_READHI = 0x002,
XMMINFO_WRITELO = 0x004,
XMMINFO_WRITEHI = 0x008,
XMMINFO_WRITED = 0x010,
XMMINFO_READD = 0x020,
XMMINFO_READS = 0x040,
XMMINFO_READT = 0x080,
XMMINFO_READACC = 0x200,
XMMINFO_WRITEACC = 0x400,
XMMINFO_WRITET = 0x800,
XMMINFO_64BITOP = 0x1000,
XMMINFO_FORCEREGS = 0x2000,
XMMINFO_FORCEREGT = 0x4000,
XMMINFO_NORENAME = 0x8000
};
////////////////////////////////////////////////////////////////////////////////
// ARM64 GPR Register Allocation
// Total number of ARM64 GPR registers we track (x0-x30 = 31)
static constexpr int NUM_ARM_GPR_REGS = 31;
// Total number of ARM64 NEON registers we track (q0-q28, excluding q29-q31 scratch)
static constexpr int NUM_ARM_NEON_REGS = 29;
enum arm64gprtype : u8
{
ARM64TYPE_TEMP = 0,
ARM64TYPE_GPR = 1, // EE GPR (lower 64 bits)
ARM64TYPE_FPRC = 2, // FPU control register
ARM64TYPE_VIREG = 3, // VU integer register
ARM64TYPE_PCWRITEBACK = 4,
ARM64TYPE_PSX = 5, // IOP GPR
ARM64TYPE_PSX_PCWRITEBACK = 6
};
struct _arm64gprregs
{
u8 inuse;
s8 reg; // guest register index
u8 mode; // MODE_READ / MODE_WRITE
u8 needed; // pinned for current instruction
u8 type; // ARM64TYPE_*
u16 counter; // LRU counter
u32 extra; // extra info (e.g., IOP constant regs)
};
// NEON register types — same values as XMM types for compatibility
#define NEONTYPE_TEMP 0
#define NEONTYPE_GPRREG 1 // EE GPR (full 128 bits)
#define NEONTYPE_FPREG 6 // FPU register
#define NEONTYPE_FPACC 7 // FPU accumulator
#define NEONTYPE_VFREG 8 // VU VF register
// x86 type aliases — used by shared analysis code (iR5900Analysis.cpp)
#define XMMTYPE_TEMP NEONTYPE_TEMP
#define XMMTYPE_GPRREG NEONTYPE_GPRREG
#define XMMTYPE_FPREG NEONTYPE_FPREG
#define XMMTYPE_VFREG NEONTYPE_VFREG
#define X86TYPE_VIREG ARM64TYPE_VIREG
#define X86TYPE_GPR ARM64TYPE_GPR
// Register index aliases for analysis (same values as x86)
#define XMMGPR_LO NEONGPR_LO // 33
#define XMMGPR_HI NEONGPR_HI // 32
#define XMMFPU_ACC NEONFPU_ACC // 32
#define NEONGPR_LO 33
#define NEONGPR_HI 32
#define NEONFPU_ACC 32
enum : int
{
DELETE_REG_FREE = 0,
DELETE_REG_FLUSH = 1,
DELETE_REG_FLUSH_AND_FREE = 2,
DELETE_REG_FREE_NO_WRITEBACK = 3
};
struct _arm64neonregs
{
u8 inuse;
s8 reg; // guest register index
u8 type; // NEONTYPE_*
u8 mode; // MODE_READ / MODE_WRITE
u8 needed; // pinned for current instruction
u16 counter; // LRU counter
};
////////////////////////////////////////////////////////////////////////////////
// ARM64 GPR allocator functions
extern _arm64gprregs arm64gprs[NUM_ARM_GPR_REGS], s_saveArm64GPRregs[NUM_ARM_GPR_REGS];
bool _isAllocatableArm64GPR(int armreg);
void _initArm64GPRregs();
int _getFreeArm64GPR(int mode);
int _allocArm64GPR(int type, int reg, int mode);
int _checkArm64GPR(int type, int reg, int mode);
bool _hasArm64GPR(int type, int reg, int required_mode = 0);
void _addNeededArm64GPR(int type, int reg);
void _clearNeededArm64GPRregs();
void _freeArm64GPR(int armreg);
void _freeArm64GPRWithoutWriteback(int armreg);
void _freeArm64GPRregs();
void _flushArm64GPRregs();
void _flushConstRegs(bool delete_const);
void _flushConstReg(int reg);
void _validateRegs();
void _writebackArm64GPR(int armreg);
void mVUFreeCOP2GPR(int hostreg);
bool mVUIsReservedCOP2(int hostreg);
////////////////////////////////////////////////////////////////////////////////
// ARM64 NEON allocator functions
extern _arm64neonregs arm64neon[NUM_ARM_NEON_REGS], s_saveArm64NEONregs[NUM_ARM_NEON_REGS];
void _initArm64NEONregs();
int _getFreeArm64NEON(u32 maxreg = NUM_ARM_NEON_REGS);
int _allocTempNEONreg();
int _allocFPtoNEONreg(int fpreg, int mode);
int _allocGPRtoNEONreg(int gprreg, int mode);
int _allocFPACCtoNEONreg(int mode);
void _reallocateNEONreg(int neonreg, int newtype, int newreg, int newmode, bool writeback = true);
int _checkNEONreg(int type, int reg, int mode);
bool _hasNEONreg(int type, int reg, int required_mode = 0);
void _addNeededFPtoNEONreg(int fpreg);
void _addNeededFPACCtoNEONreg();
void _addNeededGPRtoArm64GPR(int gprreg);
void _addNeededPSXtoArm64GPR(int gprreg);
void _addNeededGPRtoNEONreg(int gprreg);
void _clearNeededNEONregs();
void _deleteGPRtoArm64GPR(int reg, int flush);
void _deletePSXtoArm64GPR(int reg, int flush);
void _deleteGPRtoNEONreg(int reg, int flush);
void _deleteFPtoNEONreg(int reg, int flush);
void _freeNEONreg(int neonreg);
void _freeNEONregWithoutWriteback(int neonreg);
void _freeNEONregs();
void _writebackNEONreg(int neonreg);
int _allocVFtoNEONreg(int vfreg, int mode);
void mVUFreeCOP2NEONreg(int hostreg);
void _flushCOP2regs();
void _flushNEONreg(int neonreg);
void _flushNEONregs();
////////////////////////////////////////////////////////////////////////////////
// Instruction Info — architecture-independent, shared with x86
// (EEINST, liveness analysis, etc.)
#define EEINST_LIVE 1
#define EEINST_LASTUSE 8
#define EEINST_XMM 0x20 // keep name for compat — means "will use NEON/128-bit"
#define EEINST_USED 0x40
#define EEINST_COP2_DENORMALIZE_STATUS_FLAG 0x100
#define EEINST_COP2_NORMALIZE_STATUS_FLAG 0x200
#define EEINST_COP2_STATUS_FLAG 0x400
#define EEINST_COP2_MAC_FLAG 0x800
#define EEINST_COP2_CLIP_FLAG 0x1000
#define EEINST_COP2_SYNC_VU0 0x2000
#define EEINST_COP2_FINISH_VU0 0x4000
#define EEINST_COP2_FLUSH_VU0_REGISTERS 0x8000
struct EEINST
{
u16 info;
u8 regs[34]; // HI=32, LO=33
u8 fpuregs[33]; // ACC=32
u8 vfregs[34]; // ACC=32, I=33
u8 viregs[16];
u8 writeType[3], writeReg[3];
u8 readType[4], readReg[4];
};
extern EEINST* g_pCurInstInfo;
extern void _recClearInst(EEINST* pinst);
extern u32 _recIsRegReadOrWritten(EEINST* pinst, int size, u8 xmmtype, u8 reg);
extern void _recFillRegister(EEINST& pinst, int type, int reg, int write);
#define EE_WRITE_DEAD_VALUES 1
static __fi bool EEINST_USEDTEST(u32 reg)
{
return (g_pCurInstInfo->regs[reg] & (EEINST_USED | EEINST_LASTUSE)) == EEINST_USED;
}
static __fi bool EEINST_XMMUSEDTEST(u32 reg)
{
return (g_pCurInstInfo->regs[reg] & (EEINST_USED | EEINST_XMM | EEINST_LASTUSE)) == (EEINST_USED | EEINST_XMM);
}
static __fi bool EEINST_VFUSEDTEST(u32 reg)
{
return (g_pCurInstInfo->vfregs[reg] & (EEINST_USED | EEINST_LASTUSE)) == EEINST_USED;
}
static __fi bool EEINST_VIUSEDTEST(u32 reg)
{
return (g_pCurInstInfo->viregs[reg] & (EEINST_USED | EEINST_LASTUSE)) == EEINST_USED;
}
static __fi bool EEINST_LIVETEST(u32 reg)
{
return EE_WRITE_DEAD_VALUES || ((g_pCurInstInfo->regs[reg] & EEINST_LIVE) != 0);
}
static __fi bool EEINST_RENAMETEST(u32 reg)
{
return (reg == 0 || !EEINST_USEDTEST(reg) || !EEINST_LIVETEST(reg));
}
static __fi bool FPUINST_ISLIVE(u32 reg) { return !!(g_pCurInstInfo->fpuregs[reg] & EEINST_LIVE); }
static __fi bool FPUINST_LASTUSE(u32 reg) { return !!(g_pCurInstInfo->fpuregs[reg] & EEINST_LASTUSE); }
static __fi bool FPUINST_USEDTEST(u32 reg)
{
return (g_pCurInstInfo->fpuregs[reg] & (EEINST_USED | EEINST_LASTUSE)) == EEINST_USED;
}
static __fi bool FPUINST_LIVETEST(u32 reg)
{
return EE_WRITE_DEAD_VALUES || FPUINST_ISLIVE(reg);
}
static __fi bool FPUINST_RENAMETEST(u32 reg)
{
return (!EEINST_USEDTEST(reg) || !EEINST_LIVETEST(reg));
}
extern u16 g_arm64AllocCounter;
extern u16 g_neonAllocCounter;
// Allocates only if later instructions use this register
int _allocIfUsedGPRtoArm64(int gprreg, int mode);
int _allocIfUsedVItoArm64(int vireg, int mode);
int _allocIfUsedGPRtoNEON(int gprreg, int mode);
int _allocIfUsedFPUtoNEON(int fpureg, int mode);
////////////////////////////////////////////////////////////////////////////////
// Flush call parameters — same values as x86 for compatibility
#define FLUSH_NONE 0x000
#define FLUSH_CONSTANT_REGS 0x001
#define FLUSH_FLUSH_XMM 0x002 // flush NEON regs (keep name for compat)
#define FLUSH_FREE_XMM 0x004 // flush + free NEON regs
#define FLUSH_ALL_X86 0x020 // flush ARM64 GPRs (keep name for compat)
#define FLUSH_FREE_TEMP_X86 0x040 // flush + free temp ARM64 GPRs
#define FLUSH_FREE_NONTEMP_X86 0x080 // free non-temp ARM64 GPRs
#define FLUSH_FREE_VU0 0x100
#define FLUSH_PC 0x200
#define FLUSH_CODE 0x800
#define FLUSH_EVERYTHING 0x1ff
#define FLUSH_INTERPRETER 0xfff
#define FLUSH_FULLVTLB 0x000
#define FLUSH_NODESTROY (FLUSH_CONSTANT_REGS | FLUSH_FLUSH_XMM | FLUSH_ALL_X86)
File diff suppressed because it is too large Load Diff
+412
View File
@@ -0,0 +1,412 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE FPU (COP1) — "Full" / DOUBLE-precision codegen.
//
// This is the arm64 port of pcsx2/x86/iFPUd.cpp: the PS2-accurate FPU that
// widens each single to IEEE double, performs the op in double, then narrows
// back to a PS2 single with the hardware's overflow/underflow/clamp semantics.
// It is selected only when CHECK_FPU_FULL (EmuConfig.Cpu.Recompiler.fpuFullMode,
// the GameDB `eeClampMode:3` path — FFX, Max Payne, Dark Cloud 2, Klonoa 2 …).
// Default config runs the single-precision fast path in iFPU-arm64.cpp.
//
// The algorithm is translated from the x86 semantics; the codegen follows the
// iFPU-arm64.cpp idioms (scalar Fcvt, GPR bit-twiddle via Fmov, the
// armLoadEERegPtr fprc[31]/ACCflag accessors). The shared interpreter
// (FPU.cpp fpuDouble) has no double path, so this codegen has no interpreter
// counterpart.
#include "arm64/iR5900-arm64.h"
#include <cfloat>
namespace a64 = vixl::aarch64;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace COP1 {
namespace DOUBLE {
#define _Ft_ _Rt_
#define _Fs_ _Rd_
#define _Fd_ _Sa_
#define FPUflagO 0x00008000
#define FPUflagU 0x00004000
#define FPUflagSO 0x00000010
#define FPUflagSU 0x00000008
// ---- PS2 single -> IEEE double --------------------------------------------
//
// A PS2 single with exponent field 0xff is a *normal* large number (1.m * 2^128),
// but IEEE reads exp 0xff as Inf/NaN — so a plain cvtss2sd would corrupt it.
// For those (and only those) lower the exponent by one in the single domain,
// widen exactly, then raise the exponent by one in the double domain. Mirrors
// x86 ToDouble (xPSUB.D one_exp / xCVTSS2SD / xPADD.Q dbl_one_exp).
//
// Operates in place on temp NEON reg `idx`: reads the S lane, writes the D lane.
static void ToDouble(int idx)
{
const a64::VRegister s = armSRegister(idx);
const a64::VRegister d = armDRegister(idx);
a64::Label simple, done;
armAsm->Fmov(RWSCRATCH, s);
armAsm->And(RWARG1, RWSCRATCH, 0x7f800000);
armAsm->Cmp(RWARG1, 0x7f800000);
armAsm->B(&simple, a64::ne);
// Complex: exp field == 0xff (Inf/NaN to IEEE, finite to PS2).
armAsm->Sub(RWSCRATCH, RWSCRATCH, 0x00800000); // lower exponent by one (single)
armAsm->Fmov(s, RWSCRATCH);
armAsm->Fcvt(d, s); // cvtss2sd (now finite)
armAsm->Fmov(RXSCRATCH, d);
armAsm->Mov(RXARG1, static_cast<u64>(1) << 52); // dbl_one_exp
armAsm->Add(RXSCRATCH, RXSCRATCH, RXARG1); // raise exponent by one (double)
armAsm->Fmov(d, RXSCRATCH);
armAsm->B(&done);
armAsm->Bind(&simple);
armAsm->Fcvt(d, s);
armAsm->Bind(&done);
}
// ---- IEEE double -> PS2 single (full overflow/underflow/flag handling) -----
//
// Port of x86 ToPS2FPU_Full. `idx` holds the double result (D lane); `absidx`
// is a scratch NEON reg. On return the PS2 single is in `idx`'s S lane.
// Comparisons are done on the integer bit pattern of |x| — valid because every
// operand here is a finite double, so unsigned-integer order == magnitude order
// (sidesteps NaN/unordered, which never reach this point for ADD/SUB/MUL).
static void ToPS2FPU_Full(int idx, bool flags, int /*absidx*/, bool acc, bool addsub)
{
const a64::VRegister s = armSRegister(idx);
const a64::VRegister d = armDRegister(idx);
if (flags)
{
armLoadEERegPtr(RWSCRATCH, &fpuRegs.fprc[31]);
armAsm->Bic(RWSCRATCH, RWSCRATCH, FPUflagO | FPUflagU);
armStoreEERegPtr(RWSCRATCH, &fpuRegs.fprc[31]);
if (acc)
{
armLoadEERegPtr(RWSCRATCH, &fpuRegs.ACCflag);
armAsm->Bic(RWSCRATCH, RWSCRATCH, 1);
armStoreEERegPtr(RWSCRATCH, &fpuRegs.ACCflag);
}
}
// abs = |reg| (integer, low 63 bits)
armAsm->Fmov(RXSCRATCH, d);
armAsm->And(RXARG1, RXSCRATCH, 0x7fffffffffffffffULL);
a64::Label toComplex, toUnderflow, toOverflow, end;
armAsm->Mov(RXARG2, static_cast<u64>(1151) << 52); // dbl_cvt_overflow (2^128)
armAsm->Cmp(RXARG1, RXARG2);
armAsm->B(&toComplex, a64::hs);
armAsm->Mov(RXARG2, static_cast<u64>(897) << 52); // dbl_underflow (2^-126)
armAsm->Cmp(RXARG1, RXARG2);
armAsm->B(&toUnderflow, a64::lo);
// In-range: plain narrow.
armAsm->Fcvt(s, d);
armAsm->B(&end);
armAsm->Bind(&toComplex);
armAsm->Mov(RXARG2, static_cast<u64>(1152) << 52); // dbl_ps2_overflow (2^129)
armAsm->Cmp(RXARG1, RXARG2);
armAsm->B(&toOverflow, a64::hs);
// Large but PS2-representable (exp-0xff range): lower double exp, narrow,
// raise single exp — the inverse of ToDouble's complex path.
armAsm->Mov(RXARG2, static_cast<u64>(1) << 52);
armAsm->Sub(RXSCRATCH, RXSCRATCH, RXARG2);
armAsm->Fmov(d, RXSCRATCH);
armAsm->Fcvt(s, d);
armAsm->Fmov(RWSCRATCH, s);
armAsm->Add(RWSCRATCH, RWSCRATCH, 0x00800000);
armAsm->Fmov(s, RWSCRATCH);
armAsm->B(&end);
armAsm->Bind(&toOverflow);
// Beyond PS2 range: narrow then clamp to +/-max (keep sign, set all other bits).
armAsm->Fcvt(s, d);
armAsm->Fmov(RWSCRATCH, s);
armAsm->Orr(RWSCRATCH, RWSCRATCH, 0x7fffffff);
armAsm->Fmov(s, RWSCRATCH);
if (flags)
{
armLoadEERegPtr(RWARG1, &fpuRegs.fprc[31]);
armAsm->Orr(RWARG1, RWARG1, FPUflagO | FPUflagSO);
armStoreEERegPtr(RWARG1, &fpuRegs.fprc[31]);
if (acc)
{
armLoadEERegPtr(RWARG1, &fpuRegs.ACCflag);
armAsm->Orr(RWARG1, RWARG1, 1);
armStoreEERegPtr(RWARG1, &fpuRegs.ACCflag);
}
}
armAsm->B(&end);
armAsm->Bind(&toUnderflow);
a64::Label uDone;
if (flags)
{
// Set U|SU unless the result is exactly +/-0.
armAsm->Fmov(RXSCRATCH, d);
armAsm->And(RXARG1, RXSCRATCH, 0x7fffffffffffffffULL);
a64::Label isZero;
armAsm->Cbz(RXARG1, &isZero);
armLoadEERegPtr(RWARG2, &fpuRegs.fprc[31]);
armAsm->Orr(RWARG2, RWARG2, FPUflagU | FPUflagSU);
armStoreEERegPtr(RWARG2, &fpuRegs.fprc[31]);
if (addsub)
{
// ADD/SUB leave the (post-normalization) mantissa bits in place;
// reconstruct a PS2 denormal single: bits[22:0] = dbl_mant[51:29],
// bit31 = sign, exp = 0. (x86 PSLL.Q 12 / PSRL.Q 41 / sign<<31 / POR.)
armAsm->Fmov(RXSCRATCH, d);
armAsm->Lsl(RXARG1, RXSCRATCH, 12);
armAsm->Lsr(RXARG1, RXARG1, 41);
armAsm->Lsr(RXARG2, RXSCRATCH, 63);
armAsm->Lsl(RXARG2, RXARG2, 31);
armAsm->Orr(RWSCRATCH, RWARG1, RWARG2);
armAsm->Fmov(s, RWSCRATCH);
armAsm->B(&uDone);
}
armAsm->Bind(&isZero);
}
// Flush to +/-0 (keep sign).
armAsm->Fcvt(s, d);
armAsm->Fmov(RWSCRATCH, s);
armAsm->And(RWSCRATCH, RWSCRATCH, 0x80000000);
armAsm->Fmov(s, RWSCRATCH);
armAsm->Bind(&uDone);
armAsm->Bind(&end);
}
// ---- PS2 add/sub guard-bit emulation --------------------------------------
//
// The EE FPU has no guard bits to the right of the mantissa; subtraction (and
// add of mixed signs) can shift the mantissa left and expose what would have
// been guard bits. This masks the low mantissa bits of the smaller operand by
// the exponent difference so they read as zero. Port of x86 FPU_ADD_SUB; both
// operands (single, in temp NEON regs `idxd`/`idxt`) are mutated in place.
static void FPU_ADD_SUB(int idxd, int idxt)
{
const a64::VRegister sd = armSRegister(idxd);
const a64::VRegister st = armSRegister(idxt);
armAsm->Fmov(RWARG1, sd); // d bits
armAsm->Fmov(RWARG2, st); // t bits
armAsm->Ubfx(RWARG3, RWARG1, 23, 8); // expd
armAsm->Ubfx(RWSCRATCH, RWARG2, 23, 8); // expt
armAsm->Sub(RWARG3, RWARG3, RWSCRATCH); // diff = expd - expt (signed)
a64::Label caseD25, casePos, caseEq, caseDn25, done;
armAsm->Cmp(RWARG3, 25);
armAsm->B(&caseD25, a64::ge);
armAsm->Cmp(RWARG3, 0);
armAsm->B(&casePos, a64::gt);
armAsm->B(&caseEq, a64::eq);
armAsm->Cmn(RWARG3, 25); // cmp diff, -25
armAsm->B(&caseDn25, a64::le);
// diff in -24..-1 (expd < expt): mask tempd's low (-diff-1) bits.
armAsm->Neg(RWSCRATCH, RWARG3);
armAsm->Sub(RWSCRATCH, RWSCRATCH, 1);
armAsm->Mov(RWARG4, 0xffffffff);
armAsm->Lsl(RWARG4, RWARG4, RWSCRATCH);
armAsm->And(RWARG1, RWARG1, RWARG4);
armAsm->Fmov(sd, RWARG1);
armAsm->B(&done);
armAsm->Bind(&caseD25);
// diff >= 25 (expt much smaller): tempt keeps only its sign.
armAsm->And(RWARG2, RWARG2, 0x80000000);
armAsm->Fmov(st, RWARG2);
armAsm->B(&done);
armAsm->Bind(&casePos);
// diff in 1..24 (expt smaller): mask tempt's low (diff-1) bits.
armAsm->Sub(RWSCRATCH, RWARG3, 1);
armAsm->Mov(RWARG4, 0xffffffff);
armAsm->Lsl(RWARG4, RWARG4, RWSCRATCH);
armAsm->And(RWARG2, RWARG2, RWARG4);
armAsm->Fmov(st, RWARG2);
armAsm->B(&done);
armAsm->Bind(&caseDn25);
// diff <= -25 (expd much smaller): tempd keeps only its sign.
armAsm->And(RWARG1, RWARG1, 0x80000000);
armAsm->Fmov(sd, RWARG1);
armAsm->Bind(&caseEq); // diff == 0: nothing
armAsm->Bind(&done);
}
// ---- Op cores --------------------------------------------------------------
// Copy an allocator-resident FP source (EEREC_S/EEREC_T) into a fresh temp so
// ToDouble can mutate it without corrupting the guest fpr slot.
static int copySrc(int eerec)
{
const int idx = _allocTempNEONreg();
armAsm->Fmov(armSRegister(idx), armSRegister(eerec));
return idx;
}
// ADD/SUB/ADDA/SUBA: FPU_ADD_SUB guard mask -> widen -> op in double -> narrow.
static void recFPUOp(int info, int eeRecDst, int op /*0=add,1=sub*/, bool acc)
{
const int sreg = copySrc(EEREC_S);
const int treg = copySrc(EEREC_T);
FPU_ADD_SUB(sreg, treg);
ToDouble(sreg);
ToDouble(treg);
if (op == 0)
armAsm->Fadd(armDRegister(sreg), armDRegister(sreg), armDRegister(treg));
else
armAsm->Fsub(armDRegister(sreg), armDRegister(sreg), armDRegister(treg));
ToPS2FPU_Full(sreg, true, treg, acc, true);
armAsm->Fmov(armSRegister(eeRecDst), armSRegister(sreg));
_freeNEONreg(sreg);
_freeNEONreg(treg);
}
// MUL/MULA: widen -> multiply in double -> narrow. (FPUMULHACK — the Tales of
// Destiny gamefix — is intentionally not folded in here; default off.)
static void recMULop(int info, int eeRecDst, bool acc)
{
const int sreg = copySrc(EEREC_S);
const int treg = copySrc(EEREC_T);
ToDouble(sreg);
ToDouble(treg);
armAsm->Fmul(armDRegister(sreg), armDRegister(sreg), armDRegister(treg));
ToPS2FPU_Full(sreg, true, treg, acc, false);
armAsm->Fmov(armSRegister(eeRecDst), armSRegister(sreg));
_freeNEONreg(sreg);
_freeNEONreg(treg);
}
// MADD/MSUB/MADDA/MSUBA: (Fd or ACC) = ACC +/- Fs*Ft, with two PS2-accurate
// roundings (the multiply, then the accumulate) and overflow propagation from
// BOTH the product and the prior ACC. Port of x86 recMaddsub.
//
// The control flow mirrors x86: do the full-mode multiply (which may raise O),
// guard-mask ACC against the product, then branch on whether the product
// overflowed (FPUflagO) or the incoming ACC was already saturated (ACCflag&1).
// If either did, the accumulate is dominated by a 2^128-class term and the
// result is just +/-max with the dominant sign — skip the double add entirely.
// Only when both are finite is the accumulation performed in double.
static void recMaddsub(int info, int eeRecDst, int op /*0=add,1=sub*/, bool acc)
{
const int sreg = copySrc(EEREC_S);
const int treg = copySrc(EEREC_T);
// --- multiply stage: sreg = ToPS2FPU(ToDouble(s) * ToDouble(t)). Sets O on
// product overflow; acc=false so it never touches ACCflag here. ---
ToDouble(sreg);
ToDouble(treg);
armAsm->Fmul(armDRegister(sreg), armDRegister(sreg), armDRegister(treg));
ToPS2FPU_Full(sreg, true, treg, false, false);
// --- reload ACC (allocator-resident) into treg, then guard-mask it against
// the single-precision product. ---
armAsm->Fmov(armSRegister(treg), armSRegister(EEREC_ACC));
FPU_ADD_SUB(treg, sreg);
a64::Label mulovf, accovf, operation, skipall;
// product overflowed? -> mulovf
armLoadEERegPtr(RWSCRATCH, &fpuRegs.fprc[31]);
armAsm->Tst(RWSCRATCH, FPUflagO);
armAsm->B(&mulovf, a64::ne);
ToDouble(sreg);
// prior ACC saturated? -> accovf
armLoadEERegPtr(RWSCRATCH, &fpuRegs.ACCflag);
armAsm->Tst(RWSCRATCH, 1);
armAsm->B(&accovf, a64::ne);
ToDouble(treg);
armAsm->B(&operation);
armAsm->Bind(&mulovf);
// Product is a saturated single; for SUB negate its sign, then it becomes
// the (single) accumulate result. Falls through into accovf.
if (op == 1)
{
armAsm->Fmov(RWSCRATCH, armSRegister(sreg));
armAsm->Eor(RWSCRATCH, RWSCRATCH, 0x80000000);
armAsm->Fmov(armSRegister(sreg), RWSCRATCH);
}
armAsm->Fmov(armSRegister(treg), armSRegister(sreg));
armAsm->Bind(&accovf);
// SetMaxValue(treg): keep sign, set all lower bits -> +/-PS2 max.
armAsm->Fmov(RWSCRATCH, armSRegister(treg));
armAsm->Orr(RWSCRATCH, RWSCRATCH, 0x7fffffff);
armAsm->Fmov(armSRegister(treg), RWSCRATCH);
// Clear O|U then raise O|SO (and ACCflag for the *A variants).
armLoadEERegPtr(RWSCRATCH, &fpuRegs.fprc[31]);
armAsm->Bic(RWSCRATCH, RWSCRATCH, FPUflagO | FPUflagU);
armAsm->Orr(RWSCRATCH, RWSCRATCH, FPUflagO | FPUflagSO);
armStoreEERegPtr(RWSCRATCH, &fpuRegs.fprc[31]);
if (acc)
{
armLoadEERegPtr(RWSCRATCH, &fpuRegs.ACCflag);
armAsm->Orr(RWSCRATCH, RWSCRATCH, 1);
armStoreEERegPtr(RWSCRATCH, &fpuRegs.ACCflag);
}
armAsm->B(&skipall);
armAsm->Bind(&operation);
// Both finite: accumulate in double, narrow with flags.
if (op == 1)
armAsm->Fsub(armDRegister(treg), armDRegister(treg), armDRegister(sreg));
else
armAsm->Fadd(armDRegister(treg), armDRegister(treg), armDRegister(sreg));
ToPS2FPU_Full(treg, true, sreg, acc, true);
armAsm->Bind(&skipall);
armAsm->Fmov(armSRegister(eeRecDst), armSRegister(treg));
_freeNEONreg(sreg);
_freeNEONreg(treg);
}
// ---- Per-opcode DOUBLE emitters (called by the CHECK_FPU_FULL branch in
// iFPU-arm64.cpp via eeFPURecompileCode) -------------------------------
void recADD_S_xmm(int info) { recFPUOp(info, EEREC_D, 0, false); }
void recSUB_S_xmm(int info) { recFPUOp(info, EEREC_D, 1, false); }
void recADDA_S_xmm(int info) { recFPUOp(info, EEREC_ACC, 0, true); }
void recSUBA_S_xmm(int info) { recFPUOp(info, EEREC_ACC, 1, true); }
void recMUL_S_xmm(int info) { recMULop(info, EEREC_D, false); }
void recMULA_S_xmm(int info) { recMULop(info, EEREC_ACC, true); }
void recMADD_S_xmm(int info) { recMaddsub(info, EEREC_D, 0, false); }
void recMSUB_S_xmm(int info) { recMaddsub(info, EEREC_D, 1, false); }
void recMADDA_S_xmm(int info) { recMaddsub(info, EEREC_ACC, 0, true); }
void recMSUBA_S_xmm(int info) { recMaddsub(info, EEREC_ACC, 1, true); }
#undef _Ft_
#undef _Fs_
#undef _Fd_
} // namespace DOUBLE
} // namespace COP1
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+297
View File
@@ -0,0 +1,297 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "Config.h"
#include "R5900.h"
#include "R5900OpcodeTables.h"
#include "VU.h"
#include "arm64/iCore-arm64.h"
// Per-category interpreter fallback toggles.
// Comment out a define to enable native ARM64 codegen for that category.
// #define FORCE_INTERP_BRANCH 1
// #define FORCE_INTERP_JUMP 1
// #define FORCE_INTERP_MOVE 1
// #define FORCE_INTERP_SHIFT 1
// #define FORCE_INTERP_ALU 1
// #define FORCE_INTERP_ARITIMM 1
// #define FORCE_INTERP_MULTDIV 1
// #define FORCE_INTERP_MEMORY 1
// #define FORCE_INTERP_COP0 1
// #define FORCE_INTERP_FPU 1
// #define FORCE_INTERP_COP2 1
// Reserved ARM64 registers for the recompiler
// x19: Fastmem base pointer (callee-saved)
#define RFASTMEMBASE vixl::aarch64::x19
// x20: Pointer to cpuRegs struct (callee-saved). Loaded once at JIT entry by
// EnterRecompiledCode and never modified for the duration of JIT execution.
// Use armCpuRegMem() to construct cpuRegs-relative MemOperands cheaply.
#define RSTATE vixl::aarch64::x20
// x24: Pinned &VU0 (callee-saved). Loaded once at JIT entry by
// EnterRecompiledCode; used to address VU0.{VF,VI,ACC,q,statusflag,...}
// fields via single [RVU0, #imm12] load/store in iCOP2-arm64.cpp,
// instead of the 3-mov+ldr abs-addr materialization sequence.
// Survives armEmitCall (callee-saved per AAPCS) and the mVU dispatcher's
// outer Stp/Ldp pair, so cross-EE/mVU dispatches preserve it.
#define RVU0 vixl::aarch64::x24
// x25: Pinned cpuRegs.cycle (callee-saved). Always valid while in JIT;
// flushed to memory only around C calls (DispatcherEvent, JITCompile,
// recBranchCall). Block-to-block control flow keeps it live, so linked
// branches don't reload the cycle counter from memory each iteration.
#define RECCYCLE vixl::aarch64::x25
// Build a MemOperand addressing a cpuRegs field via RSTATE.
// Replaces the 3-instruction `armMoveAddressToReg(RSCRATCHADDR, &cpuRegs.X);
// Ldr/Str ..., [RSCRATCHADDR]` pattern with a single Ldr/Str using a
// signed/unsigned-immediate offset on RSTATE. ARM64 LDR with imm12 covers
// offsets up to 32760 bytes (64-bit) / 16380 bytes (32-bit) — easily larger
// than cpuRegs / fpuRegs combined, so a single instruction suffices for
// every reachable field.
static __fi vixl::aarch64::MemOperand armCpuRegMem(const void* field)
{
const ptrdiff_t off = reinterpret_cast<const u8*>(field) - reinterpret_cast<const u8*>(&cpuRegs);
return vixl::aarch64::MemOperand(RSTATE, static_cast<int64_t>(off));
}
// Pinned-base load/store helpers: when the target is anywhere inside
// _cpuRegistersPack (cpuRegs + fpuRegs), reach it via [RSTATE, #off] in one
// instruction; otherwise fall back to the generic 4-inst armLoadPtr/StorePtr.
static __fi bool armIsCpuRegPtr(const void* field)
{
const u8* base = reinterpret_cast<const u8*>(&_cpuRegistersPack);
const u8* p = reinterpret_cast<const u8*>(field);
return p >= base && p < base + sizeof(cpuRegistersPack);
}
static __fi void armLoadEERegPtr(const vixl::aarch64::CPURegister& reg, const void* field)
{
if (armIsCpuRegPtr(field))
armAsm->Ldr(reg, armCpuRegMem(field));
else
armLoadPtr(reg, field);
}
static __fi void armStoreEERegPtr(const vixl::aarch64::CPURegister& reg, const void* field)
{
if (armIsCpuRegPtr(field))
armAsm->Str(reg, armCpuRegMem(field));
else
armStorePtr(reg, field);
}
// Build a MemOperand addressing a VU0 field via RVU0. VURegs is < 2 KB, so
// every reachable field fits in imm12 for byte/halfword/word/doubleword/quad
// ldr/str. Mirrors armCpuRegMem for VU0 — used by iCOP2-arm64.cpp.
static __fi vixl::aarch64::MemOperand armVU0Mem(const void* field)
{
const ptrdiff_t off = reinterpret_cast<const u8*>(field) - reinterpret_cast<const u8*>(&VU0);
return vixl::aarch64::MemOperand(RVU0, static_cast<int64_t>(off));
}
// Emit LD1R from a VU0 field, broadcasting to all lanes. ARM64 LD1R does
// NOT support [base, #imm] addressing — only [base] or post-index. vixl's
// LoadStoreStructAddrModeField silently drops the offset (and the assert
// is gated on VIXL_DEBUG, so Devel builds ship the wrong encoding instead
// of trapping). Materialize the address with a single ADD imm12 instead of
// 3-mov: VURegs fields fit within 4 KB of &VU0, so one ADD suffices.
// Total cost: ADD + LD1R = 2 insns, vs the original 4-insn 3-mov + LD1R.
static __fi void armLd1rVU0(const vixl::aarch64::VRegister& vt, const void* field)
{
const ptrdiff_t off = reinterpret_cast<const u8*>(field) - reinterpret_cast<const u8*>(&VU0);
armAsm->Add(RSCRATCHADDR, RVU0, static_cast<int64_t>(off));
armAsm->Ld1r(vt, vixl::aarch64::MemOperand(RSCRATCHADDR));
}
extern u32 maxrecmem;
extern u32 pc; // recompiler pc
extern int g_branch; // set for branch
extern u32 target; // branch target
extern u32 s_nBlockCycles; // cycles of current block recompiling
extern bool s_nBlockInterlocked; // Current block has VU0 interlocking
//////////////////////////////////////////////////////////////////////////////////////////
// Interpreter fallback macros
#define REC_FUNC(f) \
void rec##f() \
{ \
/* Delete destination register's const/alloc state before interpreter call. \
* The interpreter writes directly to cpuRegs, making any cached const or \
* allocated register stale. SPECIAL ops (Rd), loads/COP (Rt). */ \
const u32 _op = cpuRegs.code >> 26; \
const int _dest = (_op == 0 || _op == 0x1C) ? _Rd_ : _Rt_; \
if (_dest > 0) \
_deleteEEreg(_dest, 1); \
recCall(Interp::f); \
}
#define REC_FUNC_DEL(f, delreg) \
void rec##f() \
{ \
if ((delreg) > 0) \
_deleteEEreg(delreg, 1); \
recCall(Interp::f); \
}
#define REC_SYS(f) \
void rec##f() \
{ \
recBranchCall(Interp::f); \
}
#define REC_SYS_DEL(f, delreg) \
void rec##f() \
{ \
if ((delreg) > 0) \
_deleteEEreg(delreg, 1); \
recBranchCall(Interp::f); \
}
extern bool g_recompilingDelaySlot;
// Used for generating backpatch thunks for fastmem
u8* recBeginThunk();
u8* recEndThunk();
// Branch processing
bool TrySwapDelaySlot(u32 rs, u32 rt, u32 rd, bool allow_loadstore);
void SaveBranchState();
void LoadBranchState();
void recompileNextInstruction(bool delayslot, bool swapped_delay_slot);
void SetBranchReg();
void SetBranchImm(u32 imm);
void iFlushCall(int flushtype);
void recBranchCall(void (*func)());
void recCall(void (*func)());
// Emit the post-interpreter-call TLB-miss exception dispatch (defined in
// iR5900-arm64.cpp). DispatcherReg/s_recTlbMissOccurred are file-local there,
// so cross-TU interpreter-call sites (recVTLB-arm64.cpp) route through this.
void recEmitInterpTlbMissCheck();
u32 scaleblockcycles_clear();
// COP2 / VU0 sync emit helper (defined in iCOP2-arm64.cpp).
// interlock=true mirrors x86 COP2_Interlock (CFC2/CTC2/QMFC2/QMTC2 path);
// interlock=false mirrors mVUSyncVU0 / mVUFinishVU0 gating used by LQC2/SQC2
// and the COP2 macro-arithmetic setup. finishFunc is the secondary helper
// to invoke after vu0Sync (typically _vu0FinishMicro or _vu0WaitMicro);
// pass nullptr for "sync only". Emits zero instructions when EEINST analysis
// flags say no sync is needed.
void cop2EmitConditionalSync(bool interlock, void (*finishFunc)());
// COP2 macro-mode microVU0 state setup/teardown (defined in microVU-arm64.cpp).
// Mirrors x86 setupMacroOp/endMacroOp's regAlloc reset, microVU0.cop2 = 1,
// prog.IRinfo.curPC/info[0] init, code = cpuRegs.code, and flag scaffolding.
// Required before invoking any mVU emitter (mVU_LQI/SQI/MFIR/...) from a
// COP2 macro-mode dispatch wrapper. eeinstInfo is g_pCurInstInfo->info (or 0
// when EEINST analysis isn't live for this site).
void mVUmacroSetupCOP2State(int mode, u32 eeinstInfo);
void mVUmacroEndCOP2State();
// COP2 macro-mode setup/teardown wrapper (defined in iCOP2-arm64.cpp).
// Calls cop2EmitConditionalSync, emits status-flag denormalize/normalize when
// mode & 0x10, then runs mVUmacroSetup/EndCOP2State to ready microVU0 for the
// mVU emitter pass. REC_COP2_mVU0_ARM64-style wrappers in iR5900Misc-arm64.cpp
// bracket calls to mVUmacroEmit_<op> with these.
void setupMacroOp_arm64(int mode);
void endMacroOp_arm64(int mode);
// COP2 macro-mode emit adapters (defined in microVU-arm64.cpp). Each runs the
// pass1+pass2 dispatch x86 uses in REC_COP2_mVU0 (microVU_Macro.inl:127-133).
// mode is the same mode bits passed to setupMacroOp_arm64; only bit 0x04
// (requires analysis pass) is observed by the adapter.
void mVUmacroEmit_LQI(int mode);
void mVUmacroEmit_SQI(int mode);
void mVUmacroEmit_LQD(int mode);
void mVUmacroEmit_SQD(int mode);
void mVUmacroEmit_MTIR(int mode);
void mVUmacroEmit_MFIR(int mode);
void mVUmacroEmit_ILWR(int mode);
void mVUmacroEmit_ISWR(int mode);
void mVUmacroEmit_RNEXT(int mode);
void mVUmacroEmit_RGET(int mode);
void mVUmacroEmit_RINIT(int mode);
void mVUmacroEmit_RXOR(int mode);
namespace R5900
{
namespace Dynarec
{
extern void recDoBranchImm(u32 branchTo, u32* jmpSkip, bool isLikely = false, bool swappedDelaySlot = false);
}
}
////////////////////////////////////////////////////////////////////
// Constant Propagation
#define GPR_IS_CONST1(reg) (EE_CONST_PROP && (reg) < 32 && (g_cpuHasConstReg & (1 << (reg))))
#define GPR_IS_CONST2(reg1, reg2) (EE_CONST_PROP && (g_cpuHasConstReg & (1 << (reg1))) && (g_cpuHasConstReg & (1 << (reg2))))
#define GPR_IS_DIRTY_CONST(reg) (EE_CONST_PROP && (reg) < 32 && (g_cpuHasConstReg & (1 << (reg))) && (!(g_cpuFlushedConstReg & (1 << (reg)))))
#define GPR_SET_CONST(reg) \
{ \
if ((reg) < 32) \
{ \
g_cpuHasConstReg |= (1 << (reg)); \
g_cpuFlushedConstReg &= ~(1 << (reg)); \
} \
}
#define GPR_DEL_CONST(reg) \
{ \
if ((reg) < 32) \
g_cpuHasConstReg &= ~(1 << (reg)); \
}
alignas(16) extern GPR_reg64 g_cpuConstRegs[32];
extern u32 g_cpuHasConstReg, g_cpuFlushedConstReg;
// Move guest GPR value to an ARM64 register
void _eeMoveGPRtoR(const vixl::aarch64::Register& to, int fromgpr, bool allow_preload = true);
void _eeFlushAllDirty();
void _eeOnWriteReg(int reg, int signext);
// Totally deletes from const, NEON, and GPR entries
// if flush is 1, also flushes to memory
void _deleteEEreg(int reg, int flush);
void _deleteEEreg128(int reg);
void _flushEEreg(int reg, bool clear = false);
//////////////////////////////////////
// Templates for code recompilation //
//////////////////////////////////////
typedef void (*R5900FNPTR)();
typedef void (*R5900FNPTR_INFO)(int info);
// Memory-based templates — no register allocation, all operands via cpuRegs memory.
void eeRecompileCodeRC0_MEM(R5900FNPTR constcode, R5900FNPTR_INFO constscode, R5900FNPTR_INFO consttcode, R5900FNPTR_INFO noconstcode, int xmminfo);
void eeRecompileCodeRC1_MEM(R5900FNPTR constcode, R5900FNPTR_INFO noconstcode, int xmminfo);
void eeRecompileCodeRC2_MEM(R5900FNPTR constcode, R5900FNPTR_INFO noconstcode, int xmminfo);
#define EERECOMPILE_CODERC0_MEM(fn, xmminfo) \
void rec##fn(void) \
{ \
eeRecompileCodeRC0_MEM(rec##fn##_const, rec##fn##_consts, rec##fn##_constt, rec##fn##_, (xmminfo)); \
}
#define EERECOMPILE_CODEX_MEM(codename, fn, xmminfo) \
void rec##fn(void) \
{ \
codename(rec##fn##_const, rec##fn##_, (xmminfo)); \
}
#define FPURECOMPILE_CONSTCODE(fn, xmminfo) \
void rec##fn(void) \
{ \
if (CHECK_FPU_FULL) \
eeFPURecompileCode(DOUBLE::rec##fn##_xmm, R5900::Interpreter::OpcodeImpl::COP1::fn, xmminfo); \
else \
eeFPURecompileCode(rec##fn##_xmm, R5900::Interpreter::OpcodeImpl::COP1::fn, xmminfo); \
}
int eeRecompileCodeXMM(int xmminfo);
void eeFPURecompileCode(R5900FNPTR_INFO xmmcode, R5900FNPTR fpucode, int xmminfo);
+65
View File
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 wrapper for the shared instruction analysis pass.
// Routes to ARM64-specific headers instead of x86 iR5900.h/iCore.h.
#pragma once
#include "arm64/iR5900-arm64.h"
#include "arm64/iCore-arm64.h"
// Re-export the shared analysis classes and functions from x86/iR5900Analysis.h
namespace R5900
{
class AnalysisPass
{
public:
AnalysisPass();
virtual ~AnalysisPass();
virtual void Run(u32 start, u32 end, EEINST* inst_cache);
protected:
template <class F>
void ForEachInstruction(u32 start, u32 end, EEINST* inst_cache, const F& func);
template <class F>
void DumpAnnotatedBlock(u32 start, u32 end, EEINST* inst_cache, const F& func);
};
class COP2FlagHackPass final : public AnalysisPass
{
public:
COP2FlagHackPass();
~COP2FlagHackPass();
void Run(u32 start, u32 end, EEINST* inst_cache) override;
private:
void DumpAnnotatedBlock(u32 start, u32 end, EEINST* inst_cache);
void CommitStatusFlag();
void CommitMACFlag();
void CommitClipFlag();
void CommitAllFlags();
bool m_status_denormalized = false;
EEINST* m_last_status_write = nullptr;
EEINST* m_last_mac_write = nullptr;
EEINST* m_last_clip_write = nullptr;
u32 m_cfc2_pc = 0;
};
class COP2MicroFinishPass final : public AnalysisPass
{
public:
COP2MicroFinishPass();
~COP2MicroFinishPass();
void Run(u32 start, u32 end, EEINST* inst_cache) override;
};
} // namespace R5900
void recBackpropBSC(u32 code, EEINST* prev, EEINST* pinst);
+456
View File
@@ -0,0 +1,456 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE ALU Instruction Codegen — memory-based
// All operands loaded from / stored to cpuRegs.GPR memory.
// No register allocation for scalar ops.
#include "arm64/iR5900-arm64.h"
namespace a64 = vixl::aarch64;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace Interp = R5900::Interpreter::OpcodeImpl;
#ifdef FORCE_INTERP_ALU
REC_FUNC(ADD);
void recADDU() { recADD(); }
REC_FUNC(DADD);
void recDADDU() { recDADD(); }
REC_FUNC(SUB);
void recSUBU() { recSUB(); }
REC_FUNC(DSUB);
void recDSUBU() { recDSUB(); }
REC_FUNC(AND);
REC_FUNC(OR);
REC_FUNC(XOR);
REC_FUNC(NOR);
REC_FUNC(SLT);
REC_FUNC(SLTU);
#else
// Memory load/store helpers — always use cpuRegs memory
static void memLoadS32() { armLoadEERegPtr(RWARG1, &cpuRegs.GPR.r[_Rs_].UL[0]); }
static void memLoadT32() { armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rt_].UL[0]); }
static void memLoadS64() { armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rs_].UD[0]); }
static void memLoadT64() { armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]); }
static void memStoreD() { armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]); }
/*********************************************************
* Register arithmetic rd = rs OP rt *
* 32-bit ops sign-extend result to 64 bits *
*********************************************************/
//// ADD / ADDU
static void recADD_const()
{
g_cpuConstRegs[_Rd_].SD[0] = s64(s32(g_cpuConstRegs[_Rs_].UL[0] + g_cpuConstRegs[_Rt_].UL[0]));
}
static void recADD_consts(int info)
{
const s32 cval = g_cpuConstRegs[_Rs_].SL[0];
memLoadT32();
if (cval != 0)
armAsm->Add(RWSCRATCH, RWSCRATCH, cval);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
memStoreD();
}
static void recADD_constt(int info)
{
const s32 cval = g_cpuConstRegs[_Rt_].SL[0];
memLoadS32();
if (cval != 0)
{
armAsm->Add(RWSCRATCH, RWARG1, cval);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
}
else
{
armAsm->Sxtw(RXSCRATCH, RWARG1);
}
memStoreD();
}
static void recADD_(int info)
{
memLoadS32();
memLoadT32();
armAsm->Add(RWSCRATCH, RWARG1, RWSCRATCH);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(ADD, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recADDU() { recADD(); }
//// DADD / DADDU
static void recDADD_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] + g_cpuConstRegs[_Rt_].UD[0];
}
static void recDADD_consts(int info)
{
const s64 cval = g_cpuConstRegs[_Rs_].SD[0];
memLoadT64();
if (cval != 0)
{
armAsm->Mov(RXARG1, cval);
armAsm->Add(RXSCRATCH, RXSCRATCH, RXARG1);
}
memStoreD();
}
static void recDADD_constt(int info)
{
const s64 cval = g_cpuConstRegs[_Rt_].SD[0];
memLoadS64();
if (cval != 0)
armAsm->Add(RXSCRATCH, RXARG1, cval);
else
armAsm->Mov(RXSCRATCH, RXARG1);
memStoreD();
}
static void recDADD_(int info)
{
memLoadS64();
memLoadT64();
armAsm->Add(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(DADD, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
void recDADDU() { recDADD(); }
//// SUB / SUBU
static void recSUB_const()
{
g_cpuConstRegs[_Rd_].SD[0] = s64(s32(g_cpuConstRegs[_Rs_].UL[0] - g_cpuConstRegs[_Rt_].UL[0]));
}
static void recSUB_consts(int info)
{
const s32 cval = g_cpuConstRegs[_Rs_].SL[0];
memLoadT32();
armAsm->Mov(RWARG1, cval);
armAsm->Sub(RWSCRATCH, RWARG1, RWSCRATCH);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
memStoreD();
}
static void recSUB_constt(int info)
{
const s32 cval = g_cpuConstRegs[_Rt_].SL[0];
memLoadS32();
if (cval != 0)
{
armAsm->Sub(RWSCRATCH, RWARG1, cval);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
}
else
{
armAsm->Sxtw(RXSCRATCH, RWARG1);
}
memStoreD();
}
static void recSUB_(int info)
{
// rs - rs == 0 always; emit a single store of zero.
if (_Rs_ == _Rt_)
{
armStoreEERegPtr(a64::xzr, &cpuRegs.GPR.r[_Rd_].UD[0]);
return;
}
memLoadS32();
memLoadT32();
armAsm->Sub(RWSCRATCH, RWARG1, RWSCRATCH);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(SUB, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT);
void recSUBU() { recSUB(); }
//// DSUB / DSUBU
static void recDSUB_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] - g_cpuConstRegs[_Rt_].UD[0];
}
static void recDSUB_consts(int info)
{
const s64 cval = g_cpuConstRegs[_Rs_].SD[0];
memLoadT64();
armAsm->Mov(RXARG1, cval);
armAsm->Sub(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
static void recDSUB_constt(int info)
{
const s64 cval = g_cpuConstRegs[_Rt_].SD[0];
memLoadS64();
if (cval != 0)
armAsm->Sub(RXSCRATCH, RXARG1, cval);
else
armAsm->Mov(RXSCRATCH, RXARG1);
memStoreD();
}
static void recDSUB_(int info)
{
// rs - rs == 0 always; emit a single store of zero.
if (_Rs_ == _Rt_)
{
armStoreEERegPtr(a64::xzr, &cpuRegs.GPR.r[_Rd_].UD[0]);
return;
}
memLoadS64();
memLoadT64();
armAsm->Sub(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(DSUB, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
void recDSUBU() { recDSUB(); }
//// AND — 64-bit
static void recAND_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] & g_cpuConstRegs[_Rt_].UD[0];
}
static void recAND_consts(int info)
{
memLoadT64();
armAsm->And(RXSCRATCH, RXSCRATCH, g_cpuConstRegs[_Rs_].UD[0]);
memStoreD();
}
static void recAND_constt(int info)
{
memLoadS64();
armAsm->And(RXSCRATCH, RXARG1, g_cpuConstRegs[_Rt_].UD[0]);
memStoreD();
}
static void recAND_(int info)
{
memLoadS64();
memLoadT64();
armAsm->And(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(AND, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
//// OR — 64-bit
static void recOR_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] | g_cpuConstRegs[_Rt_].UD[0];
}
static void recOR_consts(int info)
{
const u64 cval = g_cpuConstRegs[_Rs_].UD[0];
memLoadT64();
if (cval != 0)
armAsm->Orr(RXSCRATCH, RXSCRATCH, cval);
memStoreD();
}
static void recOR_constt(int info)
{
const u64 cval = g_cpuConstRegs[_Rt_].UD[0];
memLoadS64();
if (cval != 0)
armAsm->Orr(RXSCRATCH, RXARG1, cval);
else
armAsm->Mov(RXSCRATCH, RXARG1);
memStoreD();
}
static void recOR_(int info)
{
memLoadS64();
memLoadT64();
armAsm->Orr(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(OR, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
//// XOR — 64-bit
static void recXOR_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] ^ g_cpuConstRegs[_Rt_].UD[0];
}
static void recXOR_consts(int info)
{
memLoadT64();
armAsm->Mov(RXARG1, g_cpuConstRegs[_Rs_].UD[0]);
armAsm->Eor(RXSCRATCH, RXSCRATCH, RXARG1);
memStoreD();
}
static void recXOR_constt(int info)
{
memLoadS64();
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].UD[0]);
armAsm->Eor(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
static void recXOR_(int info)
{
// rs ^ rs == 0 always; skip the two operand loads (mirrors recSUB_).
if (_Rs_ == _Rt_)
{
armAsm->Mov(RXSCRATCH, 0);
memStoreD();
return;
}
memLoadS64();
memLoadT64();
armAsm->Eor(RXSCRATCH, RXARG1, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(XOR, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
//// NOR — rd = ~(rs | rt), 64-bit
static void recNOR_const()
{
g_cpuConstRegs[_Rd_].UD[0] = ~(g_cpuConstRegs[_Rs_].UD[0] | g_cpuConstRegs[_Rt_].UD[0]);
}
static void recNOR_consts(int info)
{
const u64 cval = g_cpuConstRegs[_Rs_].UD[0];
memLoadT64();
if (cval != 0)
armAsm->Orr(RXSCRATCH, RXSCRATCH, cval);
armAsm->Mvn(RXSCRATCH, RXSCRATCH);
memStoreD();
}
static void recNOR_constt(int info)
{
const u64 cval = g_cpuConstRegs[_Rt_].UD[0];
memLoadS64();
if (cval != 0)
armAsm->Orr(RXSCRATCH, RXARG1, cval);
else
armAsm->Mov(RXSCRATCH, RXARG1);
armAsm->Mvn(RXSCRATCH, RXSCRATCH);
memStoreD();
}
static void recNOR_(int info)
{
memLoadS64();
memLoadT64();
armAsm->Orr(RXSCRATCH, RXARG1, RXSCRATCH);
armAsm->Mvn(RXSCRATCH, RXSCRATCH);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(NOR, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
//// SLT — rd = (rs < rt) ? 1 : 0 (signed 64-bit compare)
static void recSLT_const()
{
g_cpuConstRegs[_Rd_].UD[0] = (g_cpuConstRegs[_Rs_].SD[0] < g_cpuConstRegs[_Rt_].SD[0]) ? 1 : 0;
}
static void recSLT_consts(int info)
{
memLoadT64();
armAsm->Mov(RXARG1, g_cpuConstRegs[_Rs_].SD[0]);
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lt);
memStoreD();
}
static void recSLT_constt(int info)
{
memLoadS64();
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].SD[0]);
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lt);
memStoreD();
}
static void recSLT_(int info)
{
memLoadS64();
memLoadT64();
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lt);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(SLT, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
//// SLTU — rd = (rs < rt) ? 1 : 0 (unsigned 64-bit compare)
static void recSLTU_const()
{
g_cpuConstRegs[_Rd_].UD[0] = (g_cpuConstRegs[_Rs_].UD[0] < g_cpuConstRegs[_Rt_].UD[0]) ? 1 : 0;
}
static void recSLTU_consts(int info)
{
memLoadT64();
armAsm->Mov(RXARG1, g_cpuConstRegs[_Rs_].UD[0]);
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lo);
memStoreD();
}
static void recSLTU_constt(int info)
{
memLoadS64();
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rt_].UD[0]);
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lo);
memStoreD();
}
static void recSLTU_(int info)
{
memLoadS64();
memLoadT64();
armAsm->Cmp(RXARG1, RXSCRATCH);
armAsm->Cset(RXSCRATCH, a64::lo);
memStoreD();
}
EERECOMPILE_CODERC0_MEM(SLTU, XMMINFO_WRITED | XMMINFO_READS | XMMINFO_READT | XMMINFO_64BITOP);
#endif // !FORCE_INTERP_ALU
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
+163
View File
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE ALU Immediate Instruction Codegen — memory-based
// rt = rs OP imm16. All operands via cpuRegs memory.
#include "arm64/iR5900-arm64.h"
namespace a64 = vixl::aarch64;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace Interp = R5900::Interpreter::OpcodeImpl;
#ifdef FORCE_INTERP_ARITIMM
REC_FUNC(ADDI);
void recADDIU() { recADDI(); }
REC_FUNC(DADDI);
void recDADDIU() { recDADDI(); }
REC_FUNC(ANDI);
REC_FUNC(ORI);
REC_FUNC(XORI);
REC_FUNC(SLTI);
REC_FUNC(SLTIU);
#else
// Memory load/store helpers
static void memLoadS32() { armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[_Rs_].UL[0]); }
static void memLoadS64() { armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]); }
static void memStoreT() { armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]); }
//// ADDI / ADDIU — rt = sign_extend(rs + imm)
static void recADDI_const()
{
g_cpuConstRegs[_Rt_].SD[0] = s64(s32(g_cpuConstRegs[_Rs_].UL[0] + u32(s32(_Imm_))));
}
static void recADDI_(int info)
{
memLoadS32();
if (_Imm_ != 0)
armAsm->Add(RWSCRATCH, RWSCRATCH, _Imm_);
armAsm->Sxtw(RXSCRATCH, RWSCRATCH);
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ADDI, XMMINFO_WRITET | XMMINFO_READS);
void recADDIU() { recADDI(); }
//// DADDI / DADDIU — rt = rs + sign_extend(imm)
static void recDADDI_const()
{
g_cpuConstRegs[_Rt_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] + u64(s64(_Imm_));
}
static void recDADDI_(int info)
{
memLoadS64();
if (_Imm_ != 0)
{
// vixl's Add(int64_t) picks the right ADD/SUB-imm encoding and
// materializes via x16 when the immediate is unencodable.
armAsm->Add(RXSCRATCH, RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
}
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, DADDI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
void recDADDIU() { recDADDI(); }
//// ANDI — rt = rs & zero_extend(imm16)
static void recANDI_const()
{
g_cpuConstRegs[_Rt_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] & (u64)(u16)_ImmU_;
}
static void recANDI_(int info)
{
memLoadS64();
if (_ImmU_ == 0)
armAsm->Mov(RXSCRATCH, 0);
else
armAsm->And(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ANDI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
//// ORI — rt = rs | zero_extend(imm16)
static void recORI_const()
{
g_cpuConstRegs[_Rt_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] | (u64)(u16)_ImmU_;
}
static void recORI_(int info)
{
memLoadS64();
if (_ImmU_ != 0)
armAsm->Orr(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, ORI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
//// XORI — rt = rs ^ zero_extend(imm16)
static void recXORI_const()
{
g_cpuConstRegs[_Rt_].UD[0] = g_cpuConstRegs[_Rs_].UD[0] ^ (u64)(u16)_ImmU_;
}
static void recXORI_(int info)
{
memLoadS64();
if (_ImmU_ != 0)
armAsm->Eor(RXSCRATCH, RXSCRATCH, static_cast<uint64_t>(static_cast<u16>(_ImmU_)));
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, XORI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
//// SLTI — rt = (rs < sign_extend(imm)) ? 1 : 0 (signed)
static void recSLTI_const()
{
g_cpuConstRegs[_Rt_].UD[0] = (g_cpuConstRegs[_Rs_].SD[0] < (s64)(s32)_Imm_) ? 1 : 0;
}
static void recSLTI_(int info)
{
memLoadS64();
armAsm->Cmp(RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
armAsm->Cset(RXSCRATCH, a64::lt);
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, SLTI, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
//// SLTIU — rt = (rs < sign_extend(imm)) ? 1 : 0 (unsigned)
static void recSLTIU_const()
{
g_cpuConstRegs[_Rt_].UD[0] = (g_cpuConstRegs[_Rs_].UD[0] < (u64)(s64)(s32)_Imm_) ? 1 : 0;
}
static void recSLTIU_(int info)
{
memLoadS64();
// Sign-extended imm — Cmp condition flags are signedness-agnostic; only
// the Cset (lo = unsigned-less-than) differs from SLTI.
armAsm->Cmp(RXSCRATCH, static_cast<int64_t>(static_cast<s32>(_Imm_)));
armAsm->Cset(RXSCRATCH, a64::lo);
memStoreT();
}
EERECOMPILE_CODEX_MEM(eeRecompileCodeRC1_MEM, SLTIU, XMMINFO_WRITET | XMMINFO_READS | XMMINFO_64BITOP);
#endif // !FORCE_INTERP_ARITIMM
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE Jump Instruction Codegen
#include "arm64/iR5900-arm64.h"
#include "Config.h"
#include "vtlb.h"
#include "common/Console.h"
namespace a64 = vixl::aarch64;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace Interp = R5900::Interpreter::OpcodeImpl;
#ifdef FORCE_INTERP_JUMP
REC_SYS(J);
REC_SYS(JAL);
REC_SYS(JR);
REC_SYS(JALR);
#else
/*********************************************************
* Jump to target *
* Format: OP target *
*********************************************************/
//// J
void recJ()
{
u32 newpc = (_InstrucTarget_ << 2) + (pc & 0xf0000000);
recompileNextInstruction(true, false);
if (EmuConfig.Gamefixes.GoemonTlbHack)
SetBranchImm(vtlb_V2P(newpc));
else
SetBranchImm(newpc);
}
//// JAL — jump and link (r31 = return address)
void recJAL()
{
u32 newpc = (_InstrucTarget_ << 2) + (pc & 0xf0000000);
_deleteEEreg(31, 0);
if (EE_CONST_PROP)
{
GPR_SET_CONST(31);
g_cpuConstRegs[31].UL[0] = pc + 4;
g_cpuConstRegs[31].UL[1] = 0;
}
else
{
armAsm->Mov(RXSCRATCH, (u64)(pc + 4));
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.GPR.r[31].UD[0]));
}
recompileNextInstruction(true, false);
if (EmuConfig.Gamefixes.GoemonTlbHack)
SetBranchImm(vtlb_V2P(newpc));
else
SetBranchImm(newpc);
}
/*********************************************************
* Register jump *
* Format: OP rs, rd *
*********************************************************/
//// JR — jump to address in rs
void recJR()
{
const u32 rs = _Rs_;
// Save jump target to memory BEFORE delay slot, so it can't be lost
// if the delay slot evicts registers.
_deleteEEreg(rs, 1); // flush rs to memory
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[rs].UL[0]);
armStoreEERegPtr(RWSCRATCH, &cpuRegs.pcWriteback);
recompileNextInstruction(true, false);
SetBranchReg();
}
//// JALR — jump to rs, link in rd
void recJALR()
{
const u32 rs = _Rs_;
const u32 rd = _Rd_;
const u32 newpc = pc + 4;
// Save jump target to memory BEFORE delay slot.
// Must read rs before writing rd in case rd == rs.
_deleteEEreg(rs, 1); // flush rs to memory
armLoadEERegPtr(RWSCRATCH, &cpuRegs.GPR.r[rs].UL[0]);
armStoreEERegPtr(RWSCRATCH, &cpuRegs.pcWriteback);
// Write link address to rd
if (rd)
{
_deleteEEreg(rd, 0);
if (EE_CONST_PROP)
{
GPR_SET_CONST(rd);
g_cpuConstRegs[rd].UL[0] = newpc;
g_cpuConstRegs[rd].UL[1] = 0;
}
else
{
armAsm->Mov(RXSCRATCH, (u64)newpc);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[rd].UD[0]);
}
}
recompileNextInstruction(true, false);
SetBranchReg();
}
#endif // !FORCE_INTERP_JUMP
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
+5
View File
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE Load/Store — all recXXX implementations are in recVTLB-arm64.cpp
// This file is intentionally empty.
File diff suppressed because it is too large Load Diff
+264
View File
@@ -0,0 +1,264 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
// ARM64 EE Move Instruction Codegen — memory-based
// All operands via cpuRegs memory.
#include "arm64/iR5900-arm64.h"
namespace a64 = vixl::aarch64;
namespace R5900 {
namespace Dynarec {
namespace OpcodeImpl {
namespace Interp = R5900::Interpreter::OpcodeImpl;
#ifdef FORCE_INTERP_MOVE
REC_FUNC(LUI);
REC_FUNC(MFLO);
REC_FUNC(MFHI);
REC_FUNC(MTLO);
REC_FUNC(MTHI);
REC_FUNC(MFLO1);
REC_FUNC(MFHI1);
REC_FUNC(MTLO1);
REC_FUNC(MTHI1);
REC_FUNC(MOVZ);
REC_FUNC(MOVN);
#else
//// LUI — rt = imm16 << 16 (sign-extended to 64 bits)
void recLUI()
{
if (!_Rt_) return;
_deleteEEreg(_Rt_, 0);
if (EE_CONST_PROP)
{
g_cpuConstRegs[_Rt_].SD[0] = s64(s32((u32)_ImmU_ << 16));
GPR_SET_CONST(_Rt_);
}
else
{
GPR_DEL_CONST(_Rt_);
armAsm->Mov(RXSCRATCH, s64(s32((u32)_ImmU_ << 16)));
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
}
}
//// MFLO / MFHI — rd = LO/HI (memory to memory)
void recMFLO()
{
if (!_Rd_) return;
_deleteEEreg(_Rd_, 0);
GPR_DEL_CONST(_Rd_);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
void recMFHI()
{
if (!_Rd_) return;
_deleteEEreg(_Rd_, 0);
GPR_DEL_CONST(_Rd_);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
//// MTLO / MTHI — LO/HI = rs (memory to memory)
void recMTLO()
{
if (GPR_IS_CONST1(_Rs_))
{
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rs_].SD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[0]);
}
else
{
_deleteEEreg(_Rs_, 1);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[0]);
}
}
void recMTHI()
{
if (GPR_IS_CONST1(_Rs_))
{
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rs_].SD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[0]);
}
else
{
_deleteEEreg(_Rs_, 1);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[0]);
}
}
//// MFLO1/MFHI1 — rd = LO1/HI1 (upper 64 bits of LO/HI)
void recMFLO1()
{
if (!_Rd_) return;
_deleteEEreg(_Rd_, 0);
GPR_DEL_CONST(_Rd_);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[1]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
void recMFHI1()
{
if (!_Rd_) return;
_deleteEEreg(_Rd_, 0);
GPR_DEL_CONST(_Rd_);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[1]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
//// MTLO1/MTHI1 — LO1/HI1 = rs
void recMTLO1()
{
if (GPR_IS_CONST1(_Rs_))
{
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rs_].SD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[1]);
}
else
{
_deleteEEreg(_Rs_, 1);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.LO.UD[1]);
}
}
void recMTHI1()
{
if (GPR_IS_CONST1(_Rs_))
{
armAsm->Mov(RXSCRATCH, g_cpuConstRegs[_Rs_].SD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[1]);
}
else
{
_deleteEEreg(_Rs_, 1);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.HI.UD[1]);
}
}
//// MOVZ — if (rt == 0) then rd = rs
// Memory-based: all loads/stores via cpuRegs
static void recMOVZtemp_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0];
}
static void recMOVZtemp_consts(int info)
{
// S is const — load T from memory, compare, conditionally store
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
armAsm->Cmp(RXSCRATCH, 0);
armAsm->Mov(RXARG1, g_cpuConstRegs[_Rs_].SD[0]);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
armAsm->Csel(RXSCRATCH, RXARG1, RXSCRATCH, a64::eq);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static void recMOVZtemp_constt(int info)
{
// T is constant and zero (checked in wrapper) — unconditional move
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static void recMOVZtemp_(int info)
{
// Load T for comparison
armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rt_].UD[0]);
armAsm->Cmp(RXARG1, 0);
// Load S
armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rs_].UD[0]);
// Load current D, conditional select, store back
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
armAsm->Csel(RXSCRATCH, RXARG1, RXSCRATCH, a64::eq);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static EERECOMPILE_CODERC0_MEM(MOVZtemp, XMMINFO_READS | XMMINFO_READT | XMMINFO_READD | XMMINFO_WRITED | XMMINFO_NORENAME);
void recMOVZ()
{
if (_Rs_ == _Rd_)
return;
if (GPR_IS_CONST1(_Rt_) && g_cpuConstRegs[_Rt_].UD[0] != 0)
return;
recMOVZtemp();
}
//// MOVN — if (rt != 0) then rd = rs
static void recMOVNtemp_const()
{
g_cpuConstRegs[_Rd_].UD[0] = g_cpuConstRegs[_Rs_].UD[0];
}
static void recMOVNtemp_consts(int info)
{
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rt_].UD[0]);
armAsm->Cmp(RXSCRATCH, 0);
armAsm->Mov(RXARG1, g_cpuConstRegs[_Rs_].SD[0]);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
armAsm->Csel(RXSCRATCH, RXARG1, RXSCRATCH, a64::ne);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static void recMOVNtemp_constt(int info)
{
// T is constant and non-zero (checked in wrapper) — unconditional move
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rs_].UD[0]);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static void recMOVNtemp_(int info)
{
armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rt_].UD[0]);
armAsm->Cmp(RXARG1, 0);
armLoadEERegPtr(RXARG1, &cpuRegs.GPR.r[_Rs_].UD[0]);
armLoadEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
armAsm->Csel(RXSCRATCH, RXARG1, RXSCRATCH, a64::ne);
armStoreEERegPtr(RXSCRATCH, &cpuRegs.GPR.r[_Rd_].UD[0]);
}
static EERECOMPILE_CODERC0_MEM(MOVNtemp, XMMINFO_READS | XMMINFO_READT | XMMINFO_READD | XMMINFO_WRITED | XMMINFO_NORENAME);
void recMOVN()
{
if (_Rs_ == _Rd_)
return;
if (GPR_IS_CONST1(_Rt_) && g_cpuConstRegs[_Rt_].UD[0] == 0)
return;
recMOVNtemp();
}
#endif // !FORCE_INTERP_MOVE
} // namespace OpcodeImpl
} // namespace Dynarec
} // namespace R5900
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More