mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
1073 lines
27 KiB
C++
1073 lines
27 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "Common.h"
|
|
#include "vtlb.h"
|
|
#include "x86/iCore.h"
|
|
#include "x86/iR5900.h"
|
|
|
|
#include "common/Perf.h"
|
|
|
|
using namespace vtlb_private;
|
|
using namespace x86Emitter;
|
|
|
|
// we need enough for a 32-bit jump forwards (5 bytes)
|
|
static constexpr u32 LOADSTORE_PADDING = 5;
|
|
|
|
//#define LOG_STORES
|
|
|
|
static u32 GetAllocatedGPRBitmask()
|
|
{
|
|
u32 mask = 0;
|
|
for (u32 i = 0; i < iREGCNT_GPR; i++)
|
|
{
|
|
if (x86regs[i].inuse)
|
|
mask |= (1u << i);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
static u32 GetAllocatedXMMBitmask()
|
|
{
|
|
u32 mask = 0;
|
|
for (u32 i = 0; i < iREGCNT_XMM; i++)
|
|
{
|
|
if (xmmregs[i].inuse)
|
|
mask |= (1u << i);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
/*
|
|
// Pseudo-Code For the following Dynarec Implementations -->
|
|
|
|
u32 vmv = vmap[addr>>VTLB_PAGE_BITS].raw();
|
|
sptr ppf=addr+vmv;
|
|
if (!(ppf<0))
|
|
{
|
|
data[0]=*reinterpret_cast<DataType*>(ppf);
|
|
if (DataSize==128)
|
|
data[1]=*reinterpret_cast<DataType*>(ppf+8);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
//has to: translate, find function, call function
|
|
u32 hand=(u8)vmv;
|
|
u32 paddr=(ppf-hand) << 1;
|
|
//Console.WriteLn("Translated 0x%08X to 0x%08X",params addr,paddr);
|
|
return reinterpret_cast<TemplateHelper<DataSize,false>::HandlerType*>(RWFT[TemplateHelper<DataSize,false>::sidx][0][hand])(paddr,data);
|
|
}
|
|
|
|
// And in ASM it looks something like this -->
|
|
|
|
mov eax,ecx;
|
|
shr eax,VTLB_PAGE_BITS;
|
|
mov rax,[rax*wordsize+vmap];
|
|
add rcx,rax;
|
|
js _fullread;
|
|
|
|
//these are wrong order, just an example ...
|
|
mov [rax],ecx;
|
|
mov ecx,[rdx];
|
|
mov [rax+4],ecx;
|
|
mov ecx,[rdx+4];
|
|
mov [rax+4+4],ecx;
|
|
mov ecx,[rdx+4+4];
|
|
mov [rax+4+4+4+4],ecx;
|
|
mov ecx,[rdx+4+4+4+4];
|
|
///....
|
|
|
|
jmp cont;
|
|
_fullread:
|
|
movzx eax,al;
|
|
sub ecx,eax;
|
|
call [eax+stuff];
|
|
cont:
|
|
........
|
|
|
|
*/
|
|
|
|
#ifdef LOG_STORES
|
|
static std::FILE* logfile;
|
|
static bool CheckLogFile()
|
|
{
|
|
if (!logfile)
|
|
logfile = std::fopen("C:\\Dumps\\comp\\memlog.bad.txt", "wb");
|
|
return (logfile != nullptr);
|
|
}
|
|
|
|
static void LogWrite(u32 addr, u64 val)
|
|
{
|
|
if (!CheckLogFile())
|
|
return;
|
|
|
|
std::fprintf(logfile, "%08X @ %u: %llx\n", addr, cpuRegs.cycle, val);
|
|
std::fflush(logfile);
|
|
}
|
|
|
|
static void __vectorcall LogWriteQuad(u32 addr, __m128i val)
|
|
{
|
|
if (!CheckLogFile())
|
|
return;
|
|
|
|
std::fprintf(logfile, "%08X @ %u: %llx %llx\n", addr, cpuRegs.cycle, val.m128i_u64[0], val.m128i_u64[1]);
|
|
std::fflush(logfile);
|
|
}
|
|
#endif
|
|
|
|
namespace vtlb_private
|
|
{
|
|
// ------------------------------------------------------------------------
|
|
// Prepares eax and ecx for Direct or Indirect operations.
|
|
//
|
|
static void DynGen_PrepRegs(int addr_reg, int value_reg, u32 sz, bool xmm)
|
|
{
|
|
_freeX86reg(arg1regd);
|
|
EE::Profiler.EmitMem(addr_reg);
|
|
xMOV(arg1regd, xRegister32(addr_reg));
|
|
|
|
if (value_reg >= 0)
|
|
{
|
|
if (sz == 128)
|
|
{
|
|
pxAssert(xmm);
|
|
_freeXMMreg(xRegisterSSE::GetArgRegister(1, 0).GetId());
|
|
xMOVAPS(xRegisterSSE::GetArgRegister(1, 0), xRegisterSSE::GetInstance(value_reg));
|
|
}
|
|
else if (xmm)
|
|
{
|
|
// 32bit xmms are passed in GPRs
|
|
pxAssert(sz == 32);
|
|
_freeX86reg(arg2regd);
|
|
xMOVD(arg2regd, xRegisterSSE(value_reg));
|
|
}
|
|
else
|
|
{
|
|
_freeX86reg(arg2regd);
|
|
xMOV(arg2reg, xRegister64(value_reg));
|
|
}
|
|
}
|
|
|
|
xMOV(eax, arg1regd);
|
|
xSHR(eax, VTLB_PAGE_BITS);
|
|
xMOV(rax, ptrNative[xComplexAddress(arg3reg, vtlbdata.vmap, rax * wordsize)]);
|
|
xADD(arg1reg, rax);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
static void DynGen_DirectRead(u32 bits, bool sign)
|
|
{
|
|
pxAssert(bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 128);
|
|
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
if (sign)
|
|
xMOVSX(rax, ptr8[arg1reg]);
|
|
else
|
|
xMOVZX(rax, ptr8[arg1reg]);
|
|
break;
|
|
|
|
case 16:
|
|
if (sign)
|
|
xMOVSX(rax, ptr16[arg1reg]);
|
|
else
|
|
xMOVZX(rax, ptr16[arg1reg]);
|
|
break;
|
|
|
|
case 32:
|
|
if (sign)
|
|
xMOVSX(rax, ptr32[arg1reg]);
|
|
else
|
|
xMOV(eax, ptr32[arg1reg]);
|
|
break;
|
|
|
|
case 64:
|
|
xMOV(rax, ptr64[arg1reg]);
|
|
break;
|
|
|
|
case 128:
|
|
xMOVAPS(xmm0, ptr128[arg1reg]);
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
static void DynGen_DirectWrite(u32 bits)
|
|
{
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
xMOV(ptr[arg1reg], xRegister8(arg2regd));
|
|
break;
|
|
|
|
case 16:
|
|
xMOV(ptr[arg1reg], xRegister16(arg2regd));
|
|
break;
|
|
|
|
case 32:
|
|
xMOV(ptr[arg1reg], arg2regd);
|
|
break;
|
|
|
|
case 64:
|
|
xMOV(ptr[arg1reg], arg2reg);
|
|
break;
|
|
|
|
case 128:
|
|
xMOVAPS(ptr[arg1reg], xRegisterSSE::GetArgRegister(1, 0));
|
|
break;
|
|
}
|
|
}
|
|
} // namespace vtlb_private
|
|
|
|
static constexpr u32 INDIRECT_DISPATCHER_SIZE = 32;
|
|
static constexpr u32 INDIRECT_DISPATCHERS_SIZE = 2 * 5 * 2 * INDIRECT_DISPATCHER_SIZE;
|
|
static u8* m_IndirectDispatchers = nullptr;
|
|
|
|
// ------------------------------------------------------------------------
|
|
// mode - 0 for read, 1 for write!
|
|
// operandsize - 0 thru 4 represents 8, 16, 32, 64, and 128 bits.
|
|
//
|
|
static u8* GetIndirectDispatcherPtr(int mode, int operandsize, int sign = 0)
|
|
{
|
|
pxAssert(mode || operandsize >= 3 ? !sign : true);
|
|
|
|
return &m_IndirectDispatchers[(mode * (8 * INDIRECT_DISPATCHER_SIZE)) + (sign * 5 * INDIRECT_DISPATCHER_SIZE) +
|
|
(operandsize * INDIRECT_DISPATCHER_SIZE)];
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Generates a JS instruction that targets the appropriate templated instance of
|
|
// the vtlb Indirect Dispatcher.
|
|
//
|
|
|
|
template <typename GenDirectFn>
|
|
static void DynGen_HandlerTest(const GenDirectFn& gen_direct, int mode, int bits, bool sign = false)
|
|
{
|
|
int szidx = 0;
|
|
switch (bits)
|
|
{
|
|
case 8: szidx = 0; break;
|
|
case 16: szidx = 1; break;
|
|
case 32: szidx = 2; break;
|
|
case 64: szidx = 3; break;
|
|
case 128: szidx = 4; break;
|
|
jNO_DEFAULT;
|
|
}
|
|
xForwardJS8 to_handler;
|
|
gen_direct();
|
|
xForwardJump8 done;
|
|
to_handler.SetTarget();
|
|
xFastCall(GetIndirectDispatcherPtr(mode, szidx, sign));
|
|
done.SetTarget();
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Generates the various instances of the indirect dispatchers
|
|
// In: arg1reg: vtlb entry, arg2reg: data ptr (if mode >= 64)
|
|
// Out: eax: result (if mode < 64)
|
|
static void DynGen_IndirectTlbDispatcher(int mode, int bits, bool sign)
|
|
{
|
|
// fixup stack
|
|
#ifdef _WIN32
|
|
xSUB(rsp, 32 + 8);
|
|
#else
|
|
xSUB(rsp, 8);
|
|
#endif
|
|
|
|
xMOVZX(eax, al);
|
|
if (wordsize != 8)
|
|
xSUB(arg1regd, 0x80000000);
|
|
xSUB(arg1regd, eax);
|
|
|
|
// jump to the indirect handler, which is a C++ function.
|
|
// [ecx is address, edx is data]
|
|
sptr table = (sptr)vtlbdata.RWFT[bits][mode];
|
|
if (table == (s32)table)
|
|
{
|
|
xFastCall(ptrNative[(rax * wordsize) + table], arg1reg, arg2reg);
|
|
}
|
|
else
|
|
{
|
|
xLEA(arg3reg, ptr[(void*)table]);
|
|
xFastCall(ptrNative[(rax * wordsize) + arg3reg], arg1reg, arg2reg);
|
|
}
|
|
|
|
if (!mode)
|
|
{
|
|
if (bits == 0)
|
|
{
|
|
if (sign)
|
|
xMOVSX(rax, al);
|
|
else
|
|
xMOVZX(rax, al);
|
|
}
|
|
else if (bits == 1)
|
|
{
|
|
if (sign)
|
|
xMOVSX(rax, ax);
|
|
else
|
|
xMOVZX(rax, ax);
|
|
}
|
|
else if (bits == 2)
|
|
{
|
|
if (sign)
|
|
xCDQE();
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
xADD(rsp, 32 + 8);
|
|
#else
|
|
xADD(rsp, 8);
|
|
#endif
|
|
|
|
xRET();
|
|
}
|
|
|
|
// One-time initialization procedure. Multiple subsequent calls during the lifespan of the
|
|
// process will be ignored.
|
|
//
|
|
void vtlb_DynGenDispatchers()
|
|
{
|
|
m_IndirectDispatchers = xGetAlignedCallTarget();
|
|
|
|
// clear the buffer to 0xcc (easier debugging).
|
|
std::memset(m_IndirectDispatchers, 0xcc, INDIRECT_DISPATCHERS_SIZE);
|
|
|
|
for (int mode = 0; mode < 2; ++mode)
|
|
{
|
|
for (int bits = 0; bits < 5; ++bits)
|
|
{
|
|
for (int sign = 0; sign < (!mode && bits < 3 ? 2 : 1); sign++)
|
|
{
|
|
xSetPtr(GetIndirectDispatcherPtr(mode, bits, !!sign));
|
|
xSetTextPtr(R5900_TEXTPTR);
|
|
|
|
DynGen_IndirectTlbDispatcher(mode, bits, !!sign);
|
|
}
|
|
}
|
|
}
|
|
|
|
Perf::any.Register(m_IndirectDispatchers, INDIRECT_DISPATCHERS_SIZE, "TLB Dispatcher");
|
|
|
|
xSetPtr(m_IndirectDispatchers + INDIRECT_DISPATCHERS_SIZE);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Dynarec Load Implementations
|
|
// ------------------------------------------------------------------------
|
|
// Recompiled input registers:
|
|
// ecx - source address to read from
|
|
// Returns read value in eax.
|
|
int vtlb_DynGenReadNonQuad(u32 bits, bool sign, bool xmm, int addr_reg, vtlb_ReadRegAllocCallback dest_reg_alloc)
|
|
{
|
|
pxAssume(bits <= 64);
|
|
|
|
int x86_dest_reg;
|
|
if (!CHECK_FASTMEM || vtlb_IsFaultingPC(pc))
|
|
{
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
|
|
DynGen_PrepRegs(addr_reg, -1, bits, xmm);
|
|
DynGen_HandlerTest([bits, sign]() { DynGen_DirectRead(bits, sign); }, 0, bits, sign && bits < 64);
|
|
|
|
if (!xmm)
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeX86reg(eax), eax.GetId());
|
|
xMOV(xRegister64(x86_dest_reg), rax);
|
|
}
|
|
else
|
|
{
|
|
// we shouldn't be loading any FPRs which aren't 32bit..
|
|
// we use MOVD here despite it being floating-point data, because we're going int->float reinterpret.
|
|
pxAssert(bits == 32);
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
xMOVDZX(xRegisterSSE(x86_dest_reg), eax);
|
|
}
|
|
|
|
return x86_dest_reg;
|
|
}
|
|
|
|
const u8* codeStart;
|
|
const xAddressReg x86addr(addr_reg);
|
|
if (!xmm)
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeX86reg(eax), eax.GetId());
|
|
codeStart = x86Ptr;
|
|
const xRegister64 x86reg(x86_dest_reg);
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
sign ? xMOVSX(x86reg, ptr8[RFASTMEMBASE + x86addr]) : xMOVZX(xRegister32(x86reg), ptr8[RFASTMEMBASE + x86addr]);
|
|
break;
|
|
case 16:
|
|
sign ? xMOVSX(x86reg, ptr16[RFASTMEMBASE + x86addr]) : xMOVZX(xRegister32(x86reg), ptr16[RFASTMEMBASE + x86addr]);
|
|
break;
|
|
case 32:
|
|
sign ? xMOVSX(x86reg, ptr32[RFASTMEMBASE + x86addr]) : xMOV(xRegister32(x86reg), ptr32[RFASTMEMBASE + x86addr]);
|
|
break;
|
|
case 64:
|
|
xMOV(x86reg, ptr64[RFASTMEMBASE + x86addr]);
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pxAssert(bits == 32);
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
codeStart = x86Ptr;
|
|
const xRegisterSSE xmmreg(x86_dest_reg);
|
|
xMOVSSZX(xmmreg, ptr32[RFASTMEMBASE + x86addr]);
|
|
}
|
|
|
|
const u32 padding = LOADSTORE_PADDING - std::min<u32>(static_cast<u32>(x86Ptr - codeStart), 5);
|
|
for (u32 i = 0; i < padding; i++)
|
|
xNOP();
|
|
|
|
vtlb_AddLoadStoreInfo((uptr)codeStart, static_cast<u32>(x86Ptr - codeStart),
|
|
pc, GetAllocatedGPRBitmask(), GetAllocatedXMMBitmask(),
|
|
static_cast<u8>(addr_reg), static_cast<u8>(x86_dest_reg),
|
|
static_cast<u8>(bits), sign, true, xmm);
|
|
|
|
return x86_dest_reg;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Recompiled input registers:
|
|
// ecx - source address to read from
|
|
// Returns read value in eax.
|
|
//
|
|
// TLB lookup is performed in const, with the assumption that the COP0/TLB will clear the
|
|
// recompiler if the TLB is changed.
|
|
//
|
|
int vtlb_DynGenReadNonQuad_Const(u32 bits, bool sign, bool xmm, u32 addr_const, vtlb_ReadRegAllocCallback dest_reg_alloc)
|
|
{
|
|
EE::Profiler.EmitConstMem(addr_const);
|
|
|
|
int x86_dest_reg;
|
|
auto vmv = vtlbdata.vmap[addr_const >> VTLB_PAGE_BITS];
|
|
if (!vmv.isHandler(addr_const))
|
|
{
|
|
auto ppf = vmv.assumePtr(addr_const);
|
|
if (!xmm)
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeX86reg(eax), eax.GetId());
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), ptr8[(u8*)ppf]) : xMOVZX(xRegister32(x86_dest_reg), ptr8[(u8*)ppf]);
|
|
break;
|
|
|
|
case 16:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), ptr16[(u16*)ppf]) : xMOVZX(xRegister32(x86_dest_reg), ptr16[(u16*)ppf]);
|
|
break;
|
|
|
|
case 32:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), ptr32[(u32*)ppf]) : xMOV(xRegister32(x86_dest_reg), ptr32[(u32*)ppf]);
|
|
break;
|
|
|
|
case 64:
|
|
xMOV(xRegister64(x86_dest_reg), ptr64[(u64*)ppf]);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
xMOVSSZX(xRegisterSSE(x86_dest_reg), ptr32[(float*)ppf]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// has to: translate, find function, call function
|
|
u32 paddr = vmv.assumeHandlerGetPAddr(addr_const);
|
|
|
|
int szidx = 0;
|
|
switch (bits)
|
|
{
|
|
case 8: szidx = 0; break;
|
|
case 16: szidx = 1; break;
|
|
case 32: szidx = 2; break;
|
|
case 64: szidx = 3; break;
|
|
}
|
|
|
|
// Shortcut for the INTC_STAT register, which many games like to spin on heavily.
|
|
if ((bits == 32) && !EmuConfig.Speedhacks.IntcStat && (paddr == INTC_STAT))
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeX86reg(eax), eax.GetId());
|
|
if (!xmm)
|
|
{
|
|
if (sign)
|
|
xMOVSX(xRegister64(x86_dest_reg), ptr32[&psHu32(INTC_STAT)]);
|
|
else
|
|
xMOV(xRegister32(x86_dest_reg), ptr32[&psHu32(INTC_STAT)]);
|
|
}
|
|
else
|
|
{
|
|
xMOVDZX(xRegisterSSE(x86_dest_reg), ptr32[&psHu32(INTC_STAT)]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
xFastCall(vmv.assumeHandlerGetRaw(szidx, false), paddr);
|
|
|
|
if (!xmm)
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeX86reg(eax), eax.GetId());
|
|
switch (bits)
|
|
{
|
|
// save REX prefix by using 32bit dest for zext
|
|
case 8:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), al) : xMOVZX(xRegister32(x86_dest_reg), al);
|
|
break;
|
|
|
|
case 16:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), ax) : xMOVZX(xRegister32(x86_dest_reg), ax);
|
|
break;
|
|
|
|
case 32:
|
|
sign ? xMOVSX(xRegister64(x86_dest_reg), eax) : xMOV(xRegister32(x86_dest_reg), eax);
|
|
break;
|
|
|
|
case 64:
|
|
xMOV(xRegister64(x86_dest_reg), rax);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x86_dest_reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
xMOVDZX(xRegisterSSE(x86_dest_reg), eax);
|
|
}
|
|
}
|
|
}
|
|
|
|
return x86_dest_reg;
|
|
}
|
|
|
|
int vtlb_DynGenReadQuad(u32 bits, int addr_reg, vtlb_ReadRegAllocCallback dest_reg_alloc)
|
|
{
|
|
pxAssume(bits == 128);
|
|
|
|
if (!CHECK_FASTMEM || vtlb_IsFaultingPC(pc))
|
|
{
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
|
|
DynGen_PrepRegs(arg1regd.GetId(), -1, bits, true);
|
|
DynGen_HandlerTest([bits]() {DynGen_DirectRead(bits, false); }, 0, bits);
|
|
|
|
const int reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0); // Handler returns in xmm0
|
|
if (reg >= 0)
|
|
xMOVAPS(xRegisterSSE(reg), xmm0);
|
|
|
|
return reg;
|
|
}
|
|
|
|
const int reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0); // Handler returns in xmm0
|
|
const u8* codeStart = x86Ptr;
|
|
|
|
xMOVAPS(xRegisterSSE(reg), ptr128[RFASTMEMBASE + arg1reg]);
|
|
|
|
const u32 padding = LOADSTORE_PADDING - std::min<u32>(static_cast<u32>(x86Ptr - codeStart), 5);
|
|
for (u32 i = 0; i < padding; i++)
|
|
xNOP();
|
|
|
|
vtlb_AddLoadStoreInfo((uptr)codeStart, static_cast<u32>(x86Ptr - codeStart),
|
|
pc, GetAllocatedGPRBitmask(), GetAllocatedXMMBitmask(),
|
|
static_cast<u8>(arg1reg.GetId()), static_cast<u8>(reg),
|
|
static_cast<u8>(bits), false, true, true);
|
|
|
|
return reg;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// TLB lookup is performed in const, with the assumption that the COP0/TLB will clear the
|
|
// recompiler if the TLB is changed.
|
|
int vtlb_DynGenReadQuad_Const(u32 bits, u32 addr_const, vtlb_ReadRegAllocCallback dest_reg_alloc)
|
|
{
|
|
pxAssert(bits == 128);
|
|
|
|
EE::Profiler.EmitConstMem(addr_const);
|
|
|
|
int reg;
|
|
auto vmv = vtlbdata.vmap[addr_const >> VTLB_PAGE_BITS];
|
|
if (!vmv.isHandler(addr_const))
|
|
{
|
|
void* ppf = reinterpret_cast<void*>(vmv.assumePtr(addr_const));
|
|
reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
if (reg >= 0)
|
|
xMOVAPS(xRegisterSSE(reg), ptr128[ppf]);
|
|
}
|
|
else
|
|
{
|
|
// has to: translate, find function, call function
|
|
u32 paddr = vmv.assumeHandlerGetPAddr(addr_const);
|
|
|
|
const int szidx = 4;
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
xFastCall(vmv.assumeHandlerGetRaw(szidx, 0), paddr);
|
|
|
|
reg = dest_reg_alloc ? dest_reg_alloc() : (_freeXMMreg(0), 0);
|
|
xMOVAPS(xRegisterSSE(reg), xmm0);
|
|
}
|
|
|
|
return reg;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Dynarec Store Implementations
|
|
|
|
void vtlb_DynGenWrite(u32 sz, bool xmm, int addr_reg, int value_reg)
|
|
{
|
|
#ifdef LOG_STORES
|
|
{
|
|
xSUB(rsp, 16 * 16);
|
|
for (u32 i = 0; i < 16; i++)
|
|
xMOVAPS(ptr[rsp + i * 16], xRegisterSSE::GetInstance(i));
|
|
for (const auto& reg : {rbx, rcx, rdx, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, rbp})
|
|
xPUSH(reg);
|
|
|
|
xPUSH(xRegister64(addr_reg));
|
|
xPUSH(xRegister64(value_reg));
|
|
xPUSH(arg1reg);
|
|
xPUSH(arg2reg);
|
|
xMOV(arg1regd, xRegister32(addr_reg));
|
|
if (xmm)
|
|
{
|
|
xSUB(rsp, 32 + 32);
|
|
xMOVAPS(ptr[rsp + 32], xRegisterSSE::GetInstance(value_reg));
|
|
xMOVAPS(ptr[rsp + 48], xRegisterSSE::GetArgRegister(1, 0));
|
|
if (sz < 128)
|
|
xPSHUF.D(xRegisterSSE::GetArgRegister(1, 0), xRegisterSSE::GetInstance(value_reg), 0);
|
|
else
|
|
xMOVAPS(xRegisterSSE::GetArgRegister(1, 0), xRegisterSSE::GetInstance(value_reg));
|
|
xFastCall((void*)LogWriteQuad);
|
|
xMOVAPS(xRegisterSSE::GetArgRegister(1, 0), ptr[rsp + 48]);
|
|
xMOVAPS(xRegisterSSE::GetInstance(value_reg), ptr[rsp + 32]);
|
|
xADD(rsp, 32 + 32);
|
|
}
|
|
else
|
|
{
|
|
xMOV(arg2reg, xRegister64(value_reg));
|
|
if (sz == 8)
|
|
xAND(arg2regd, 0xFF);
|
|
else if (sz == 16)
|
|
xAND(arg2regd, 0xFFFF);
|
|
else if (sz == 32)
|
|
xAND(arg2regd, -1);
|
|
xSUB(rsp, 32);
|
|
xFastCall((void*)LogWrite);
|
|
xADD(rsp, 32);
|
|
}
|
|
xPOP(arg2reg);
|
|
xPOP(arg1reg);
|
|
xPOP(xRegister64(value_reg));
|
|
xPOP(xRegister64(addr_reg));
|
|
|
|
for (const auto& reg : {rbp, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rdx, rcx, rbx})
|
|
xPOP(reg);
|
|
|
|
for (u32 i = 0; i < 16; i++)
|
|
xMOVAPS(xRegisterSSE::GetInstance(i), ptr[rsp + i * 16]);
|
|
xADD(rsp, 16 * 16);
|
|
}
|
|
#endif
|
|
|
|
if (!CHECK_FASTMEM || vtlb_IsFaultingPC(pc))
|
|
{
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
|
|
DynGen_PrepRegs(addr_reg, value_reg, sz, xmm);
|
|
DynGen_HandlerTest([sz]() { DynGen_DirectWrite(sz); }, 1, sz);
|
|
return;
|
|
}
|
|
|
|
const u8* codeStart = x86Ptr;
|
|
|
|
const xAddressReg vaddr_reg(addr_reg);
|
|
if (!xmm)
|
|
{
|
|
switch (sz)
|
|
{
|
|
case 8:
|
|
xMOV(ptr8[RFASTMEMBASE + vaddr_reg], xRegister8(xRegister32(value_reg)));
|
|
break;
|
|
case 16:
|
|
xMOV(ptr16[RFASTMEMBASE + vaddr_reg], xRegister16(value_reg));
|
|
break;
|
|
case 32:
|
|
xMOV(ptr32[RFASTMEMBASE + vaddr_reg], xRegister32(value_reg));
|
|
break;
|
|
case 64:
|
|
xMOV(ptr64[RFASTMEMBASE + vaddr_reg], xRegister64(value_reg));
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pxAssert(sz == 32 || sz == 128);
|
|
switch (sz)
|
|
{
|
|
case 32:
|
|
xMOVSS(ptr32[RFASTMEMBASE + vaddr_reg], xRegisterSSE(value_reg));
|
|
break;
|
|
case 128:
|
|
xMOVAPS(ptr128[RFASTMEMBASE + vaddr_reg], xRegisterSSE(value_reg));
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
|
|
const u32 padding = LOADSTORE_PADDING - std::min<u32>(static_cast<u32>(x86Ptr - codeStart), 5);
|
|
for (u32 i = 0; i < padding; i++)
|
|
xNOP();
|
|
|
|
vtlb_AddLoadStoreInfo((uptr)codeStart, static_cast<u32>(x86Ptr - codeStart),
|
|
pc, GetAllocatedGPRBitmask(), GetAllocatedXMMBitmask(),
|
|
static_cast<u8>(addr_reg), static_cast<u8>(value_reg),
|
|
static_cast<u8>(sz), false, false, xmm);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Generates code for a store instruction, where the address is a known constant.
|
|
// TLB lookup is performed in const, with the assumption that the COP0/TLB will clear the
|
|
// recompiler if the TLB is changed.
|
|
void vtlb_DynGenWrite_Const(u32 bits, bool xmm, u32 addr_const, int value_reg)
|
|
{
|
|
EE::Profiler.EmitConstMem(addr_const);
|
|
|
|
#ifdef LOG_STORES
|
|
{
|
|
xSUB(rsp, 16 * 16);
|
|
for (u32 i = 0; i < 16; i++)
|
|
xMOVAPS(ptr[rsp + i * 16], xRegisterSSE::GetInstance(i));
|
|
for (const auto& reg : { rbx, rcx, rdx, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15, rbp })
|
|
xPUSH(reg);
|
|
|
|
xPUSH(xRegister64(value_reg));
|
|
xPUSH(xRegister64(value_reg));
|
|
xPUSH(arg1reg);
|
|
xPUSH(arg2reg);
|
|
xMOV(arg1reg, addr_const);
|
|
if (xmm)
|
|
{
|
|
xSUB(rsp, 32 + 32);
|
|
xMOVAPS(ptr[rsp + 32], xRegisterSSE::GetInstance(value_reg));
|
|
xMOVAPS(ptr[rsp + 48], xRegisterSSE::GetArgRegister(1, 0));
|
|
if (bits < 128)
|
|
xPSHUF.D(xRegisterSSE::GetArgRegister(1, 0), xRegisterSSE::GetInstance(value_reg), 0);
|
|
else
|
|
xMOVAPS(xRegisterSSE::GetArgRegister(1, 0), xRegisterSSE::GetInstance(value_reg));
|
|
xFastCall((void*)LogWriteQuad);
|
|
xMOVAPS(xRegisterSSE::GetArgRegister(1, 0), ptr[rsp + 48]);
|
|
xMOVAPS(xRegisterSSE::GetInstance(value_reg), ptr[rsp + 32]);
|
|
xADD(rsp, 32 + 32);
|
|
}
|
|
else
|
|
{
|
|
xMOV(arg2reg, xRegister64(value_reg));
|
|
if (bits == 8)
|
|
xAND(arg2regd, 0xFF);
|
|
else if (bits == 16)
|
|
xAND(arg2regd, 0xFFFF);
|
|
else if (bits == 32)
|
|
xAND(arg2regd, -1);
|
|
xSUB(rsp, 32);
|
|
xFastCall((void*)LogWrite);
|
|
xADD(rsp, 32);
|
|
}
|
|
xPOP(arg2reg);
|
|
xPOP(arg1reg);
|
|
xPOP(xRegister64(value_reg));
|
|
xPOP(xRegister64(value_reg));
|
|
|
|
for (const auto& reg : {rbp, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rdx, rcx, rbx})
|
|
xPOP(reg);
|
|
|
|
for (u32 i = 0; i < 16; i++)
|
|
xMOVAPS(xRegisterSSE::GetInstance(i), ptr[rsp + i * 16]);
|
|
xADD(rsp, 16 * 16);
|
|
}
|
|
#endif
|
|
|
|
auto vmv = vtlbdata.vmap[addr_const >> VTLB_PAGE_BITS];
|
|
if (!vmv.isHandler(addr_const))
|
|
{
|
|
auto ppf = vmv.assumePtr(addr_const);
|
|
if (!xmm)
|
|
{
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
xMOV(ptr[(void*)ppf], xRegister8(xRegister32(value_reg)));
|
|
break;
|
|
|
|
case 16:
|
|
xMOV(ptr[(void*)ppf], xRegister16(value_reg));
|
|
break;
|
|
|
|
case 32:
|
|
xMOV(ptr[(void*)ppf], xRegister32(value_reg));
|
|
break;
|
|
|
|
case 64:
|
|
xMOV(ptr64[(void*)ppf], xRegister64(value_reg));
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (bits)
|
|
{
|
|
case 32:
|
|
xMOVSS(ptr[(void*)ppf], xRegisterSSE(value_reg));
|
|
break;
|
|
|
|
case 128:
|
|
xMOVAPS(ptr128[(void*)ppf], xRegisterSSE(value_reg));
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// has to: translate, find function, call function
|
|
u32 paddr = vmv.assumeHandlerGetPAddr(addr_const);
|
|
|
|
int szidx = 0;
|
|
switch (bits)
|
|
{
|
|
case 8:
|
|
szidx = 0;
|
|
break;
|
|
case 16:
|
|
szidx = 1;
|
|
break;
|
|
case 32:
|
|
szidx = 2;
|
|
break;
|
|
case 64:
|
|
szidx = 3;
|
|
break;
|
|
case 128:
|
|
szidx = 4;
|
|
break;
|
|
}
|
|
|
|
iFlushCall(FLUSH_FULLVTLB);
|
|
|
|
_freeX86reg(arg1regd);
|
|
xMOV(arg1regd, paddr);
|
|
if (bits == 128)
|
|
{
|
|
pxAssert(xmm);
|
|
const xRegisterSSE argreg(xRegisterSSE::GetArgRegister(1, 0));
|
|
_freeXMMreg(argreg.GetId());
|
|
xMOVAPS(argreg, xRegisterSSE(value_reg));
|
|
}
|
|
else if (xmm)
|
|
{
|
|
pxAssert(bits == 32);
|
|
_freeX86reg(arg2regd);
|
|
xMOVD(arg2regd, xRegisterSSE(value_reg));
|
|
}
|
|
else
|
|
{
|
|
_freeX86reg(arg2regd);
|
|
xMOV(arg2reg, xRegister64(value_reg));
|
|
}
|
|
|
|
xFastCall(vmv.assumeHandlerGetRaw(szidx, true));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Extra Implementations
|
|
|
|
// ecx - virtual address
|
|
// Returns physical address in eax.
|
|
// Clobbers edx
|
|
void vtlb_DynV2P()
|
|
{
|
|
xMOV(eax, ecx);
|
|
xAND(ecx, VTLB_PAGE_MASK); // vaddr & VTLB_PAGE_MASK
|
|
|
|
xSHR(eax, VTLB_PAGE_BITS);
|
|
xMOV(eax, ptr[xComplexAddress(rdx, vtlbdata.ppmap, rax * 4)]); // vtlbdata.ppmap[vaddr >> VTLB_PAGE_BITS];
|
|
|
|
xOR(eax, ecx);
|
|
}
|
|
|
|
void vtlb_DynBackpatchLoadStore(uptr code_address, u32 code_size, u32 guest_pc, u32 guest_addr,
|
|
u32 gpr_bitmask, u32 fpr_bitmask, u8 address_register, u8 data_register,
|
|
u8 size_in_bits, bool is_signed, bool is_load, bool is_xmm)
|
|
{
|
|
static constexpr u32 GPR_SIZE = 8;
|
|
static constexpr u32 XMM_SIZE = 16;
|
|
|
|
// on win32, we need to reserve an additional 32 bytes shadow space when calling out to C
|
|
#ifdef _WIN32
|
|
static constexpr u32 SHADOW_SIZE = 32;
|
|
#else
|
|
static constexpr u32 SHADOW_SIZE = 0;
|
|
#endif
|
|
|
|
#if 0
|
|
DevCon.WriteLn("Backpatching %s at %p[%u] (pc %08X vaddr %08X): Bitmask %08X %08X Addr %u Data %u Size %u Flags %02X %02X",
|
|
is_load ? "load" : "store", (void*)code_address, code_size, guest_pc, guest_addr, gpr_bitmask, fpr_bitmask,
|
|
address_register, data_register, size_in_bits, is_signed, is_load);
|
|
#endif
|
|
|
|
u8* thunk = recBeginThunk();
|
|
|
|
// save regs
|
|
u32 num_gprs = 0;
|
|
u32 num_fprs = 0;
|
|
|
|
const u32 arg1id = static_cast<u32>(arg1reg.GetId());
|
|
const u32 arg2id = static_cast<u32>(arg2reg.GetId());
|
|
const u32 arg3id = static_cast<u32>(arg3reg.GetId());
|
|
|
|
for (u32 i = 0; i < iREGCNT_GPR; i++)
|
|
{
|
|
if ((gpr_bitmask & (1u << i)) && (i == arg1id || i == arg2id || xRegisterBase::IsCallerSaved(i)) && (!is_load || is_xmm || data_register != i))
|
|
num_gprs++;
|
|
}
|
|
for (u32 i = 0; i < iREGCNT_XMM; i++)
|
|
{
|
|
if (fpr_bitmask & (1u << i) && xRegisterSSE::IsCallerSaved(i) && (!is_load || !is_xmm || data_register != i))
|
|
num_fprs++;
|
|
}
|
|
|
|
const u32 stack_size = (((num_gprs + 1) & ~1u) * GPR_SIZE) + (num_fprs * XMM_SIZE) + SHADOW_SIZE;
|
|
|
|
if (stack_size > 0)
|
|
{
|
|
xSUB(rsp, stack_size);
|
|
|
|
u32 stack_offset = SHADOW_SIZE;
|
|
for (u32 i = 0; i < iREGCNT_XMM; i++)
|
|
{
|
|
if (fpr_bitmask & (1u << i) && xRegisterSSE::IsCallerSaved(i) && (!is_load || !is_xmm || data_register != i))
|
|
{
|
|
xMOVAPS(ptr128[rsp + stack_offset], xRegisterSSE(i));
|
|
stack_offset += XMM_SIZE;
|
|
}
|
|
}
|
|
|
|
for (u32 i = 0; i < iREGCNT_GPR; i++)
|
|
{
|
|
if ((gpr_bitmask & (1u << i)) && (i == arg1id || i == arg2id || i == arg3id || xRegisterBase::IsCallerSaved(i)) && (!is_load || is_xmm || data_register != i))
|
|
{
|
|
xMOV(ptr64[rsp + stack_offset], xRegister64(i));
|
|
stack_offset += GPR_SIZE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_load)
|
|
{
|
|
DynGen_PrepRegs(address_register, -1, size_in_bits, is_xmm);
|
|
DynGen_HandlerTest([size_in_bits, is_signed]() {DynGen_DirectRead(size_in_bits, is_signed); }, 0, size_in_bits, is_signed && size_in_bits <= 32);
|
|
|
|
if (size_in_bits == 128)
|
|
{
|
|
if (data_register != xmm0.GetId())
|
|
xMOVAPS(xRegisterSSE(data_register), xmm0);
|
|
}
|
|
else
|
|
{
|
|
if (is_xmm)
|
|
{
|
|
xMOVDZX(xRegisterSSE(data_register), rax);
|
|
}
|
|
else
|
|
{
|
|
if (data_register != eax.GetId())
|
|
xMOV(xRegister64(data_register), rax);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (address_register != arg1reg.GetId())
|
|
xMOV(arg1regd, xRegister32(address_register));
|
|
|
|
if (size_in_bits == 128)
|
|
{
|
|
const xRegisterSSE argreg(xRegisterSSE::GetArgRegister(1, 0));
|
|
if (data_register != argreg.GetId())
|
|
xMOVAPS(argreg, xRegisterSSE(data_register));
|
|
}
|
|
else
|
|
{
|
|
if (is_xmm)
|
|
{
|
|
xMOVD(arg2reg, xRegisterSSE(data_register));
|
|
}
|
|
else
|
|
{
|
|
if (data_register != arg2reg.GetId())
|
|
xMOV(arg2reg, xRegister64(data_register));
|
|
}
|
|
}
|
|
|
|
DynGen_PrepRegs(address_register, data_register, size_in_bits, is_xmm);
|
|
DynGen_HandlerTest([size_in_bits]() { DynGen_DirectWrite(size_in_bits); }, 1, size_in_bits);
|
|
}
|
|
|
|
// restore regs
|
|
if (stack_size > 0)
|
|
{
|
|
u32 stack_offset = SHADOW_SIZE;
|
|
for (u32 i = 0; i < iREGCNT_XMM; i++)
|
|
{
|
|
if (fpr_bitmask & (1u << i) && xRegisterSSE::IsCallerSaved(i) && (!is_load || !is_xmm || data_register != i))
|
|
{
|
|
xMOVAPS(xRegisterSSE(i), ptr128[rsp + stack_offset]);
|
|
stack_offset += XMM_SIZE;
|
|
}
|
|
}
|
|
|
|
for (u32 i = 0; i < iREGCNT_GPR; i++)
|
|
{
|
|
if ((gpr_bitmask & (1u << i)) && (i == arg1id || i == arg2id || i == arg3id || xRegisterBase::IsCallerSaved(i)) && (!is_load || is_xmm || data_register != i))
|
|
{
|
|
xMOV(xRegister64(i), ptr64[rsp + stack_offset]);
|
|
stack_offset += GPR_SIZE;
|
|
}
|
|
}
|
|
|
|
xADD(rsp, stack_size);
|
|
}
|
|
|
|
xJMP((void*)(code_address + code_size));
|
|
|
|
recEndThunk();
|
|
|
|
// backpatch to a jump to the slowmem handler
|
|
x86Ptr = (u8*)code_address;
|
|
xJMP(thunk);
|
|
|
|
// fill the rest of it with nops, if any
|
|
pxAssertRel(static_cast<u32>((uptr)x86Ptr - code_address) <= code_size, "Overflowed when backpatching");
|
|
for (u32 i = static_cast<u32>((uptr)x86Ptr - code_address); i < code_size; i++)
|
|
xNOP();
|
|
}
|