mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
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:
co-authored by
Ryan Walklin
Claude Opus 4.8
parent
6799ab0d2d
commit
ac8258a950
@@ -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, \
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user