mirror of https://github.com/PCSX2/pcsx2.git
1078 lines
31 KiB
C++
1078 lines
31 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#pragma once
|
|
|
|
#include "common/Threading.h"
|
|
#include "common/Assertions.h"
|
|
#include "common/Pcsx2Defs.h"
|
|
|
|
static const uint iREGCNT_XMM = 16;
|
|
static const uint iREGCNT_GPR = 16;
|
|
|
|
enum XMMSSEType
|
|
{
|
|
XMMT_INT = 0, // integer (sse2 only)
|
|
XMMT_FPS = 1, // floating point
|
|
//XMMT_FPD = 3, // double
|
|
};
|
|
|
|
extern thread_local u8* x86Ptr;
|
|
extern thread_local XMMSSEType g_xmmtypes[iREGCNT_XMM];
|
|
|
|
namespace x86Emitter
|
|
{
|
|
// Win32 requires 32 bytes of shadow stack in the caller's frame.
|
|
#ifdef _WIN32
|
|
static constexpr int SHADOW_STACK_SIZE = 32;
|
|
#else
|
|
static constexpr int SHADOW_STACK_SIZE = 0;
|
|
#endif
|
|
|
|
extern void xWrite8(u8 val);
|
|
extern void xWrite16(u16 val);
|
|
extern void xWrite32(u32 val);
|
|
extern void xWrite64(u64 val);
|
|
|
|
extern const char* xGetRegName(int regid, int operandSize);
|
|
|
|
//------------------------------------------------------------------
|
|
// templated version of is_s8 is required, so that u16's get correct sign extension treatment.
|
|
template <typename T>
|
|
static __fi bool is_s8(T imm)
|
|
{
|
|
return (s8)imm == (typename std::make_signed<T>::type)imm;
|
|
}
|
|
|
|
template <typename T>
|
|
void xWrite(T val);
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// ALWAYS_USE_MOVAPS [define] / AlwaysUseMovaps [const]
|
|
// --------------------------------------------------------------------------------------
|
|
// This tells the recompiler's emitter to always use movaps instead of movdqa. Both instructions
|
|
// do the exact same thing, but movaps is 1 byte shorter, and thus results in a cleaner L1 cache
|
|
// and some marginal speed gains as a result. (it's possible someday in the future the per-
|
|
// formance of the two instructions could change, so this constant is provided to restore MOVDQA
|
|
// use easily at a later time, if needed).
|
|
//
|
|
#define ALWAYS_USE_MOVAPS
|
|
|
|
#ifdef ALWAYS_USE_MOVAPS
|
|
static const bool AlwaysUseMovaps = true;
|
|
#else
|
|
static const bool AlwaysUseMovaps = false;
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// __emitline - preprocessors definition
|
|
// --------------------------------------------------------------------------------------
|
|
// This is configured to inline emitter functions appropriately for release builds, and
|
|
// disables some of the more aggressive inlines for dev builds (which can be helpful when
|
|
// debugging). Additionally, I've set up the inlining to be as practical and intelligent
|
|
// as possible with regard to constant propagation. Namely this involves forcing inlining
|
|
// for (void*) forms of ModRM, which (thanks to constprop) reduce to virtually no code, and
|
|
// force-disabling inlining on complicated SibSB forms [since MSVC would sometimes inline
|
|
// despite being a generally bad idea].
|
|
//
|
|
// In the case of (Reg, Imm) forms, the inlining is up to the discreation of the compiler.
|
|
//
|
|
// Note: I *intentionally* use __fi directly for most single-line class members,
|
|
// when needed. There's no point in using __emitline in these cases since the debugger
|
|
// can't trace into single-line functions anyway.
|
|
//
|
|
#ifdef PCSX2_DEVBUILD
|
|
#define __emitinline
|
|
#else
|
|
#define __emitinline __fi
|
|
#endif
|
|
|
|
// ModRM 'mod' field enumeration. Provided mostly for reference:
|
|
enum ModRm_ModField
|
|
{
|
|
Mod_NoDisp = 0, // effective address operation with no displacement, in the form of [reg] (or uses special Disp32-only encoding in the case of [ebp] form)
|
|
Mod_Disp8, // effective address operation with 8 bit displacement, in the form of [reg+disp8]
|
|
Mod_Disp32, // effective address operation with 32 bit displacement, in the form of [reg+disp32],
|
|
Mod_Direct, // direct reg/reg operation
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// JccComparisonType - enumerated possibilities for inspired code branching!
|
|
//
|
|
enum JccComparisonType
|
|
{
|
|
Jcc_Unknown = -2,
|
|
Jcc_Unconditional = -1,
|
|
Jcc_Overflow = 0x0,
|
|
Jcc_NotOverflow = 0x1,
|
|
Jcc_Below = 0x2,
|
|
Jcc_Carry = 0x2,
|
|
Jcc_AboveOrEqual = 0x3,
|
|
Jcc_NotCarry = 0x3,
|
|
Jcc_Zero = 0x4,
|
|
Jcc_Equal = 0x4,
|
|
Jcc_NotZero = 0x5,
|
|
Jcc_NotEqual = 0x5,
|
|
Jcc_BelowOrEqual = 0x6,
|
|
Jcc_Above = 0x7,
|
|
Jcc_Signed = 0x8,
|
|
Jcc_Unsigned = 0x9,
|
|
Jcc_ParityEven = 0xa,
|
|
Jcc_ParityOdd = 0xb,
|
|
Jcc_Less = 0xc,
|
|
Jcc_GreaterOrEqual = 0xd,
|
|
Jcc_LessOrEqual = 0xe,
|
|
Jcc_Greater = 0xf,
|
|
};
|
|
|
|
// Not supported yet:
|
|
//E3 cb JECXZ rel8 Jump short if ECX register is 0.
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SSE2_ComparisonType - enumerated possibilities for SIMD data comparison!
|
|
//
|
|
enum SSE2_ComparisonType
|
|
{
|
|
SSE2_Equal = 0,
|
|
SSE2_Less,
|
|
SSE2_LessOrEqual,
|
|
SSE2_Unordered,
|
|
SSE2_NotEqual,
|
|
SSE2_NotLess,
|
|
SSE2_NotLessOrEqual,
|
|
SSE2_Ordered
|
|
};
|
|
|
|
static const int ModRm_UseSib = 4; // same index value as ESP (used in RM field)
|
|
static const int ModRm_UseDisp32 = 5; // same index value as EBP (used in Mod field)
|
|
static const int Sib_EIZ = 4; // same index value as ESP (used in Index field)
|
|
static const int Sib_UseDisp32 = 5; // same index value as EBP (used in Base field)
|
|
|
|
extern void xSetPtr(void* ptr);
|
|
extern void xAlignPtr(uint bytes);
|
|
extern void xAdvancePtr(uint bytes);
|
|
extern void xAlignCallTarget();
|
|
|
|
extern u8* xGetPtr();
|
|
extern u8* xGetAlignedCallTarget();
|
|
|
|
extern JccComparisonType xInvertCond(JccComparisonType src);
|
|
|
|
class xAddressVoid;
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// OperandSizedObject
|
|
// --------------------------------------------------------------------------------------
|
|
class OperandSizedObject
|
|
{
|
|
protected:
|
|
uint _operandSize = 0;
|
|
OperandSizedObject() = default;
|
|
OperandSizedObject(uint operandSize)
|
|
: _operandSize(operandSize)
|
|
{
|
|
}
|
|
|
|
public:
|
|
uint GetOperandSize() const
|
|
{
|
|
pxAssertMsg(_operandSize != 0, "Attempted to use operand size of uninitialized or void object");
|
|
return _operandSize;
|
|
}
|
|
|
|
bool Is8BitOp() const { return GetOperandSize() == 1; }
|
|
u8 GetPrefix16() const { return GetOperandSize() == 2 ? 0x66 : 0; }
|
|
void prefix16() const
|
|
{
|
|
if (GetOperandSize() == 2)
|
|
xWrite8(0x66);
|
|
}
|
|
|
|
int GetImmSize() const
|
|
{
|
|
switch (GetOperandSize())
|
|
{
|
|
case 1:
|
|
return 1;
|
|
case 2:
|
|
return 2;
|
|
case 4:
|
|
return 4;
|
|
case 8:
|
|
return 4; // Only mov's take 64-bit immediates
|
|
jNO_DEFAULT
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void xWriteImm(int imm) const
|
|
{
|
|
switch (GetImmSize())
|
|
{
|
|
case 1:
|
|
xWrite8(imm);
|
|
break;
|
|
case 2:
|
|
xWrite16(imm);
|
|
break;
|
|
case 4:
|
|
xWrite32(imm);
|
|
break;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
}
|
|
};
|
|
|
|
// Represents an unused or "empty" register assignment. If encountered by the emitter, this
|
|
// will be ignored (in some cases it is disallowed and generates an assertion)
|
|
static const int xRegId_Empty = -1;
|
|
|
|
// Represents an invalid or uninitialized register. If this is encountered by the emitter it
|
|
// will generate an assertion.
|
|
static const int xRegId_Invalid = -2;
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xRegisterBase - type-unsafe x86 register representation.
|
|
// --------------------------------------------------------------------------------------
|
|
// Unless doing some fundamental stuff, use the friendly xRegister32/16/8 and xRegisterSSE
|
|
// instead, which are built using this class and provide strict register type safety when
|
|
// passed into emitter instructions.
|
|
//
|
|
class xRegisterBase : public OperandSizedObject
|
|
{
|
|
protected:
|
|
xRegisterBase(uint operandSize, int regId)
|
|
: OperandSizedObject(operandSize)
|
|
, Id(regId)
|
|
{
|
|
// Note: to avoid tons of ifdef, the 32 bits build will instantiate
|
|
// all 16x64 bits registers.
|
|
pxAssert((Id >= xRegId_Empty) && (Id < 16));
|
|
}
|
|
|
|
public:
|
|
int Id;
|
|
|
|
xRegisterBase()
|
|
: OperandSizedObject(0)
|
|
, Id(xRegId_Invalid)
|
|
{
|
|
}
|
|
|
|
bool IsEmpty() const { return Id < 0; }
|
|
bool IsInvalid() const { return Id == xRegId_Invalid; }
|
|
bool IsExtended() const { return (Id >= 0 && (Id & 0x0F) > 7); } // Register 8-15 need an extra bit to be selected
|
|
bool IsExtended8Bit() const { return (Is8BitOp() && Id >= 0x10); }
|
|
bool IsMem() const { return false; }
|
|
bool IsReg() const { return true; }
|
|
|
|
// Returns true if the register is a valid accumulator: Eax, Ax, Al, XMM0.
|
|
bool IsAccumulator() const { return Id == 0; }
|
|
|
|
// IsSIMD: returns true if the register is a valid XMM register.
|
|
bool IsSIMD() const { return GetOperandSize() == 16; }
|
|
|
|
// IsWide: return true if the register is 64 bits (requires a wide op on the rex prefix)
|
|
bool IsWide() const
|
|
{
|
|
return GetOperandSize() == 8;
|
|
}
|
|
// return true if the register is a valid YMM register
|
|
bool IsWideSIMD() const { return GetOperandSize() == 32; }
|
|
|
|
// Diagnostics -- returns a string representation of this register. Return string
|
|
// is a valid non-null string for any Id, valid or invalid. No assertions are generated.
|
|
const char* GetName();
|
|
int GetId() const { return Id; }
|
|
|
|
/// Returns true if the specified register is caller-saved (volatile).
|
|
static inline bool IsCallerSaved(uint id);
|
|
};
|
|
|
|
class xRegisterInt : public xRegisterBase
|
|
{
|
|
typedef xRegisterBase _parent;
|
|
|
|
protected:
|
|
explicit xRegisterInt(uint operandSize, int regId)
|
|
: _parent(operandSize, regId)
|
|
{
|
|
}
|
|
|
|
public:
|
|
xRegisterInt() = default;
|
|
|
|
/// IDs in [4, 8) are h registers in 8-bit
|
|
int isIDSameInAllSizes() const
|
|
{
|
|
return Id < 4 || Id >= 8;
|
|
}
|
|
|
|
/// Checks if mapping the ID directly would be a good idea
|
|
bool canMapIDTo(int otherSize) const
|
|
{
|
|
if ((otherSize == 1) == (GetOperandSize() == 1))
|
|
return true;
|
|
return isIDSameInAllSizes();
|
|
}
|
|
|
|
/// Get a non-wide version of the register (for use with e.g. mov, where `mov eax, 3` and `mov rax, 3` are functionally identical but `mov eax, 3` is shorter)
|
|
xRegisterInt GetNonWide() const
|
|
{
|
|
return GetOperandSize() == 8 ? xRegisterInt(4, Id) : *this;
|
|
}
|
|
|
|
xRegisterInt MatchSizeTo(xRegisterInt other) const;
|
|
|
|
bool operator==(const xRegisterInt& src) const { return Id == src.Id && (GetOperandSize() == src.GetOperandSize()); }
|
|
bool operator!=(const xRegisterInt& src) const { return !operator==(src); }
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xRegister8/16/32/64 - Represents a basic 8/16/32/64 bit GPR on the x86
|
|
// --------------------------------------------------------------------------------------
|
|
class xRegister8 : public xRegisterInt
|
|
{
|
|
typedef xRegisterInt _parent;
|
|
|
|
public:
|
|
xRegister8() = default;
|
|
explicit xRegister8(int regId)
|
|
: _parent(1, regId)
|
|
{
|
|
}
|
|
explicit xRegister8(const xRegisterInt& other)
|
|
: _parent(1, other.Id)
|
|
{
|
|
if (!other.canMapIDTo(1))
|
|
Id |= 0x10;
|
|
}
|
|
xRegister8(int regId, bool ext8bit)
|
|
: _parent(1, regId)
|
|
{
|
|
if (ext8bit)
|
|
Id |= 0x10;
|
|
}
|
|
|
|
bool operator==(const xRegister8& src) const { return Id == src.Id; }
|
|
bool operator!=(const xRegister8& src) const { return Id != src.Id; }
|
|
};
|
|
|
|
class xRegister16 : public xRegisterInt
|
|
{
|
|
typedef xRegisterInt _parent;
|
|
|
|
public:
|
|
xRegister16() = default;
|
|
explicit xRegister16(int regId)
|
|
: _parent(2, regId)
|
|
{
|
|
}
|
|
explicit xRegister16(const xRegisterInt& other)
|
|
: _parent(2, other.Id)
|
|
{
|
|
pxAssertMsg(other.canMapIDTo(2), "Mapping h registers to higher registers can produce unexpected values");
|
|
}
|
|
|
|
bool operator==(const xRegister16& src) const { return this->Id == src.Id; }
|
|
bool operator!=(const xRegister16& src) const { return this->Id != src.Id; }
|
|
};
|
|
|
|
class xRegister32 : public xRegisterInt
|
|
{
|
|
typedef xRegisterInt _parent;
|
|
|
|
public:
|
|
xRegister32() = default;
|
|
explicit xRegister32(int regId)
|
|
: _parent(4, regId)
|
|
{
|
|
}
|
|
explicit xRegister32(const xRegisterInt& other)
|
|
: _parent(4, other.Id)
|
|
{
|
|
pxAssertMsg(other.canMapIDTo(4), "Mapping h registers to higher registers can produce unexpected values");
|
|
}
|
|
|
|
static const inline xRegister32& GetInstance(uint id);
|
|
|
|
bool operator==(const xRegister32& src) const { return this->Id == src.Id; }
|
|
bool operator!=(const xRegister32& src) const { return this->Id != src.Id; }
|
|
};
|
|
|
|
class xRegister64 : public xRegisterInt
|
|
{
|
|
typedef xRegisterInt _parent;
|
|
|
|
public:
|
|
xRegister64() = default;
|
|
explicit xRegister64(int regId)
|
|
: _parent(8, regId)
|
|
{
|
|
}
|
|
explicit xRegister64(const xRegisterInt& other)
|
|
: _parent(8, other.Id)
|
|
{
|
|
pxAssertMsg(other.canMapIDTo(8), "Mapping h registers to higher registers can produce unexpected values");
|
|
}
|
|
|
|
static const inline xRegister64& GetInstance(uint id);
|
|
|
|
bool operator==(const xRegister64& src) const { return this->Id == src.Id; }
|
|
bool operator!=(const xRegister64& src) const { return this->Id != src.Id; }
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xRegisterSSE - Represents either a 64 bit or 128 bit SIMD register
|
|
// --------------------------------------------------------------------------------------
|
|
// This register type is provided to allow legal syntax for instructions that accept
|
|
// an XMM register as a parameter, but do not allow for a GPR.
|
|
|
|
struct xRegisterYMMTag {};
|
|
|
|
class xRegisterSSE : public xRegisterBase
|
|
{
|
|
typedef xRegisterBase _parent;
|
|
|
|
public:
|
|
xRegisterSSE() = default;
|
|
explicit xRegisterSSE(int regId)
|
|
: _parent(16, regId)
|
|
{
|
|
}
|
|
xRegisterSSE(int regId, xRegisterYMMTag)
|
|
: _parent(32, regId)
|
|
{
|
|
}
|
|
|
|
bool operator==(const xRegisterSSE& src) const { return this->Id == src.Id; }
|
|
bool operator!=(const xRegisterSSE& src) const { return this->Id != src.Id; }
|
|
|
|
static const inline xRegisterSSE& GetInstance(uint id);
|
|
static const inline xRegisterSSE& GetYMMInstance(uint id);
|
|
|
|
/// Returns the register to use when calling a C function.
|
|
/// arg_number is the argument position from the left, starting with 0.
|
|
/// sse_number is the argument position relative to the number of vector registers.
|
|
static const inline xRegisterSSE& GetArgRegister(uint arg_number, uint sse_number, bool ymm = false);
|
|
|
|
/// Returns true if the specified register is caller-saved (volatile).
|
|
static inline bool IsCallerSaved(uint id);
|
|
};
|
|
|
|
class xRegisterCL : public xRegister8
|
|
{
|
|
public:
|
|
xRegisterCL()
|
|
: xRegister8(1)
|
|
{
|
|
}
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xAddressReg
|
|
// --------------------------------------------------------------------------------------
|
|
// Use 32/64 bit registers as our index registers (for ModSib-style memory address calculations).
|
|
// This type is implicitly exchangeable with xRegister32/64.
|
|
//
|
|
// Only xAddressReg provides operators for constructing xAddressInfo types. These operators
|
|
// could have been added to xRegister32/64 directly instead, however I think this design makes
|
|
// more sense and allows the programmer a little more type protection if needed.
|
|
//
|
|
|
|
#define xRegisterLong xRegister64
|
|
static const int wordsize = sizeof(sptr);
|
|
|
|
class xAddressReg : public xRegisterLong
|
|
{
|
|
public:
|
|
xAddressReg() = default;
|
|
explicit xAddressReg(xRegisterInt other)
|
|
: xRegisterLong(other)
|
|
{
|
|
}
|
|
explicit xAddressReg(int regId)
|
|
: xRegisterLong(regId)
|
|
{
|
|
}
|
|
|
|
// Returns true if the register is the stack pointer: ESP.
|
|
bool IsStackPointer() const { return Id == 4; }
|
|
|
|
/// Returns the register to use when calling a C function.
|
|
/// arg_number is the argument position from the left, starting with 0.
|
|
/// sse_number is the argument position relative to the number of vector registers.
|
|
static const inline xAddressReg& GetArgRegister(uint arg_number, uint gpr_number);
|
|
|
|
xAddressVoid operator+(const xAddressReg& right) const;
|
|
xAddressVoid operator+(sptr right) const;
|
|
xAddressVoid operator+(const void* right) const;
|
|
xAddressVoid operator-(sptr right) const;
|
|
xAddressVoid operator-(const void* right) const;
|
|
xAddressVoid operator*(int factor) const;
|
|
xAddressVoid operator<<(u32 shift) const;
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xRegisterEmpty
|
|
// --------------------------------------------------------------------------------------
|
|
struct xRegisterEmpty
|
|
{
|
|
operator xRegister8() const
|
|
{
|
|
return xRegister8(xRegId_Empty);
|
|
}
|
|
|
|
operator xRegister16() const
|
|
{
|
|
return xRegister16(xRegId_Empty);
|
|
}
|
|
|
|
operator xRegister32() const
|
|
{
|
|
return xRegister32(xRegId_Empty);
|
|
}
|
|
|
|
operator xRegisterSSE() const
|
|
{
|
|
return xRegisterSSE(xRegId_Empty);
|
|
}
|
|
|
|
operator xAddressReg() const
|
|
{
|
|
return xAddressReg(xRegId_Empty);
|
|
}
|
|
};
|
|
|
|
class xRegister16or32or64
|
|
{
|
|
protected:
|
|
const xRegisterInt& m_convtype;
|
|
|
|
public:
|
|
xRegister16or32or64(const xRegister64& src)
|
|
: m_convtype(src)
|
|
{
|
|
}
|
|
xRegister16or32or64(const xRegister32& src)
|
|
: m_convtype(src)
|
|
{
|
|
}
|
|
xRegister16or32or64(const xRegister16& src)
|
|
: m_convtype(src)
|
|
{
|
|
}
|
|
|
|
operator const xRegisterBase&() const { return m_convtype; }
|
|
|
|
const xRegisterInt* operator->() const
|
|
{
|
|
return &m_convtype;
|
|
}
|
|
};
|
|
|
|
class xRegister32or64
|
|
{
|
|
protected:
|
|
const xRegisterInt& m_convtype;
|
|
|
|
public:
|
|
xRegister32or64(const xRegister64& src)
|
|
: m_convtype(src)
|
|
{
|
|
}
|
|
xRegister32or64(const xRegister32& src)
|
|
: m_convtype(src)
|
|
{
|
|
}
|
|
|
|
operator const xRegisterBase&() const { return m_convtype; }
|
|
|
|
const xRegisterInt* operator->() const
|
|
{
|
|
return &m_convtype;
|
|
}
|
|
};
|
|
|
|
extern const xRegisterEmpty xEmptyReg;
|
|
|
|
// clang-format off
|
|
extern const xRegisterSSE
|
|
xmm0, xmm1, xmm2, xmm3,
|
|
xmm4, xmm5, xmm6, xmm7,
|
|
xmm8, xmm9, xmm10, xmm11,
|
|
xmm12, xmm13, xmm14, xmm15;
|
|
|
|
// TODO: This needs to be _M_SSE >= 0x500'ed, but we can't do it atm because common doesn't have variants.
|
|
extern const xRegisterSSE
|
|
ymm0, ymm1, ymm2, ymm3,
|
|
ymm4, ymm5, ymm6, ymm7,
|
|
ymm8, ymm9, ymm10, ymm11,
|
|
ymm12, ymm13, ymm14, ymm15;
|
|
|
|
extern const xAddressReg
|
|
rax, rbx, rcx, rdx,
|
|
rsi, rdi, rbp, rsp,
|
|
r8, r9, r10, r11,
|
|
r12, r13, r14, r15;
|
|
|
|
extern const xRegister32
|
|
eax, ebx, ecx, edx,
|
|
esi, edi, ebp, esp,
|
|
r8d, r9d, r10d, r11d,
|
|
r12d, r13d, r14d, r15d;
|
|
|
|
extern const xRegister16
|
|
ax, bx, cx, dx,
|
|
si, di, bp, sp;
|
|
|
|
extern const xRegister8
|
|
al, dl, bl,
|
|
ah, ch, dh, bh,
|
|
spl, bpl, sil, dil,
|
|
r8b, r9b, r10b, r11b,
|
|
r12b, r13b, r14b, r15b;
|
|
|
|
extern const xAddressReg
|
|
arg1reg, arg2reg,
|
|
arg3reg, arg4reg,
|
|
calleeSavedReg1,
|
|
calleeSavedReg2;
|
|
|
|
|
|
extern const xRegister32
|
|
arg1regd, arg2regd,
|
|
calleeSavedReg1d,
|
|
calleeSavedReg2d;
|
|
|
|
|
|
// clang-format on
|
|
|
|
extern const xRegisterCL cl; // I'm special!
|
|
|
|
bool xRegisterBase::IsCallerSaved(uint id)
|
|
{
|
|
#ifdef _WIN32
|
|
// The x64 ABI considers the registers RAX, RCX, RDX, R8, R9, R10, R11, and XMM0-XMM5 volatile.
|
|
return (id <= 2 || (id >= 8 && id <= 11));
|
|
#else
|
|
// rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 are scratch registers.
|
|
return (id <= 2 || id == 6 || id == 7 || (id >= 8 && id <= 11));
|
|
#endif
|
|
}
|
|
|
|
const xRegister32& xRegister32::GetInstance(uint id)
|
|
{
|
|
static const xRegister32* const m_tbl_x86Regs[] =
|
|
{
|
|
&eax, &ecx, &edx, &ebx,
|
|
&esp, &ebp, &esi, &edi,
|
|
&r8d, &r9d, &r10d, &r11d,
|
|
&r12d, &r13d, &r14d, &r15d,
|
|
};
|
|
|
|
pxAssert(id < iREGCNT_GPR);
|
|
return *m_tbl_x86Regs[id];
|
|
}
|
|
|
|
const xRegister64& xRegister64::GetInstance(uint id)
|
|
{
|
|
static const xRegister64* const m_tbl_x86Regs[] =
|
|
{
|
|
&rax, &rcx, &rdx, &rbx,
|
|
&rsp, &rbp, &rsi, &rdi,
|
|
&r8, &r9, &r10, &r11,
|
|
&r12, &r13, &r14, &r15
|
|
};
|
|
|
|
pxAssert(id < iREGCNT_GPR);
|
|
return *m_tbl_x86Regs[id];
|
|
}
|
|
|
|
bool xRegisterSSE::IsCallerSaved(uint id)
|
|
{
|
|
#ifdef _WIN32
|
|
// XMM6 through XMM15 are saved. Upper 128 bits is always volatile.
|
|
return (id < 6);
|
|
#else
|
|
// All vector registers are volatile.
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
const xRegisterSSE& xRegisterSSE::GetInstance(uint id)
|
|
{
|
|
static const xRegisterSSE* const m_tbl_xmmRegs[] =
|
|
{
|
|
&xmm0, &xmm1, &xmm2, &xmm3,
|
|
&xmm4, &xmm5, &xmm6, &xmm7,
|
|
&xmm8, &xmm9, &xmm10, &xmm11,
|
|
&xmm12, &xmm13, &xmm14, &xmm15};
|
|
|
|
pxAssert(id < iREGCNT_XMM);
|
|
return *m_tbl_xmmRegs[id];
|
|
}
|
|
|
|
const xRegisterSSE& xRegisterSSE::GetYMMInstance(uint id)
|
|
{
|
|
static const xRegisterSSE* const m_tbl_ymmRegs[] =
|
|
{
|
|
&ymm0, &ymm1, &ymm2, &ymm3,
|
|
&ymm4, &ymm5, &ymm6, &ymm7,
|
|
&ymm8, &ymm9, &ymm10, &ymm11,
|
|
&ymm12, &ymm13, &ymm14, &ymm15};
|
|
|
|
pxAssert(id < iREGCNT_XMM);
|
|
return *m_tbl_ymmRegs[id];
|
|
}
|
|
|
|
const xRegisterSSE& xRegisterSSE::GetArgRegister(uint arg_number, uint sse_number, bool ymm)
|
|
{
|
|
#ifdef _WIN32
|
|
// Windows passes arguments according to their position from the left.
|
|
return ymm ? GetYMMInstance(arg_number) : GetInstance(arg_number);
|
|
#else
|
|
// Linux counts the number of vector parameters.
|
|
return ymm ? GetYMMInstance(sse_number) : GetInstance(sse_number);
|
|
#endif
|
|
}
|
|
|
|
const xAddressReg& xAddressReg::GetArgRegister(uint arg_number, uint gpr_number)
|
|
{
|
|
#ifdef _WIN32
|
|
// Windows passes arguments according to their position from the left.
|
|
static constexpr const xAddressReg* regs[] = {&rcx, &rdx, &r8, &r9};
|
|
pxAssert(arg_number < std::size(regs));
|
|
return *regs[arg_number];
|
|
#else
|
|
// Linux counts the number of GPR parameters.
|
|
static constexpr const xAddressReg* regs[] = {&rdi, &rsi, &rdx, &rcx};
|
|
pxAssert(gpr_number < std::size(regs));
|
|
return *regs[gpr_number];
|
|
#endif
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xAddressVoid
|
|
// --------------------------------------------------------------------------------------
|
|
class xAddressVoid
|
|
{
|
|
public:
|
|
xAddressReg Base; // base register (no scale)
|
|
xAddressReg Index; // index reg gets multiplied by the scale
|
|
int Factor; // scale applied to the index register, in factor form (not a shift!)
|
|
sptr Displacement; // address displacement // 4B max even on 64 bits but keep rest for assertions
|
|
|
|
public:
|
|
xAddressVoid(const xAddressReg& base, const xAddressReg& index, int factor = 1, sptr displacement = 0);
|
|
|
|
xAddressVoid(const xAddressReg& index, sptr displacement = 0);
|
|
explicit xAddressVoid(const void* displacement);
|
|
explicit xAddressVoid(sptr displacement = 0);
|
|
|
|
public:
|
|
bool IsByteSizeDisp() const { return is_s8(Displacement); }
|
|
|
|
xAddressVoid& Add(sptr imm)
|
|
{
|
|
Displacement += imm;
|
|
return *this;
|
|
}
|
|
|
|
xAddressVoid& Add(const xAddressReg& src);
|
|
xAddressVoid& Add(const xAddressVoid& src);
|
|
|
|
__fi xAddressVoid operator+(const xAddressReg& right) const { return xAddressVoid(*this).Add(right); }
|
|
__fi xAddressVoid operator+(const xAddressVoid& right) const { return xAddressVoid(*this).Add(right); }
|
|
__fi xAddressVoid operator+(sptr imm) const { return xAddressVoid(*this).Add(imm); }
|
|
__fi xAddressVoid operator-(sptr imm) const { return xAddressVoid(*this).Add(-imm); }
|
|
__fi xAddressVoid operator+(const void* addr) const { return xAddressVoid(*this).Add((uptr)addr); }
|
|
|
|
__fi void operator+=(const xAddressReg& right) { Add(right); }
|
|
__fi void operator+=(sptr imm) { Add(imm); }
|
|
__fi void operator-=(sptr imm) { Add(-imm); }
|
|
};
|
|
|
|
static __fi xAddressVoid operator+(const void* addr, const xAddressVoid& right)
|
|
{
|
|
return right + addr;
|
|
}
|
|
|
|
static __fi xAddressVoid operator+(sptr addr, const xAddressVoid& right)
|
|
{
|
|
return right + addr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xImmReg< typename xRegType >
|
|
// --------------------------------------------------------------------------------------
|
|
// Used to represent an immediate value which can also be optimized to a register. Note
|
|
// that the immediate value represented by this structure is *always* legal. The register
|
|
// assignment is an optional optimization which can be implemented in cases where an
|
|
// immediate is used enough times to merit allocating it to a register.
|
|
//
|
|
// Note: not all instructions support this operand type (yet). You can always implement it
|
|
// manually by checking the status of IsReg() and generating the xOP conditionally.
|
|
//
|
|
template <typename xRegType>
|
|
class xImmReg
|
|
{
|
|
xRegType m_reg;
|
|
int m_imm;
|
|
|
|
public:
|
|
xImmReg()
|
|
: m_reg()
|
|
{
|
|
m_imm = 0;
|
|
}
|
|
|
|
xImmReg(int imm, const xRegType& reg = xEmptyReg)
|
|
{
|
|
m_reg = reg;
|
|
m_imm = imm;
|
|
}
|
|
|
|
const xRegType& GetReg() const { return m_reg; }
|
|
int GetImm() const { return m_imm; }
|
|
bool IsReg() const { return !m_reg.IsEmpty(); }
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xIndirectVoid - Internal low-level representation of the ModRM/SIB information.
|
|
// --------------------------------------------------------------------------------------
|
|
// This class serves two purposes: It houses 'reduced' ModRM/SIB info only, which means
|
|
// that the Base, Index, Scale, and Displacement values are all in the correct arrange-
|
|
// ments, and it serves as a type-safe layer between the xRegister's operators (which
|
|
// generate xAddressInfo types) and the emitter's ModSib instruction forms. Without this,
|
|
// the xRegister would pass as a ModSib type implicitly, and that would cause ambiguity
|
|
// on a number of instructions.
|
|
//
|
|
// End users should always use xAddressInfo instead.
|
|
//
|
|
class xIndirectVoid : public OperandSizedObject
|
|
{
|
|
public:
|
|
xAddressReg Base; // base register (no scale)
|
|
xAddressReg Index; // index reg gets multiplied by the scale
|
|
uint Scale; // scale applied to the index register, in scale/shift form
|
|
sptr Displacement; // offset applied to the Base/Index registers.
|
|
// Displacement is 8/32 bits even on x86_64
|
|
// However we need the whole pointer to calculate rip-relative offsets
|
|
|
|
public:
|
|
explicit xIndirectVoid(sptr disp);
|
|
explicit xIndirectVoid(const xAddressVoid& src);
|
|
xIndirectVoid(xAddressReg base, xAddressReg index, int scale = 0, sptr displacement = 0);
|
|
xIndirectVoid& Add(sptr imm);
|
|
|
|
bool IsByteSizeDisp() const { return is_s8(Displacement); }
|
|
bool IsMem() const { return true; }
|
|
bool IsReg() const { return false; }
|
|
bool IsExtended() const { return false; } // Non sense but ease template
|
|
bool IsWide() const { return _operandSize == 8; }
|
|
|
|
operator xAddressVoid()
|
|
{
|
|
return xAddressVoid(Base, Index, Scale, Displacement);
|
|
}
|
|
|
|
__fi xIndirectVoid operator+(const sptr imm) const { return xIndirectVoid(*this).Add(imm); }
|
|
__fi xIndirectVoid operator-(const sptr imm) const { return xIndirectVoid(*this).Add(-imm); }
|
|
|
|
protected:
|
|
void Reduce();
|
|
};
|
|
|
|
template <typename OperandType>
|
|
class xIndirect : public xIndirectVoid
|
|
{
|
|
typedef xIndirectVoid _parent;
|
|
|
|
public:
|
|
explicit xIndirect(sptr disp)
|
|
: _parent(disp)
|
|
{
|
|
_operandSize = sizeof(OperandType);
|
|
}
|
|
xIndirect(xAddressReg base, xAddressReg index, int scale = 0, sptr displacement = 0)
|
|
: _parent(base, index, scale, displacement)
|
|
{
|
|
_operandSize = sizeof(OperandType);
|
|
}
|
|
explicit xIndirect(const xIndirectVoid& other)
|
|
: _parent(other)
|
|
{
|
|
}
|
|
|
|
xIndirect<OperandType>& Add(sptr imm)
|
|
{
|
|
Displacement += imm;
|
|
return *this;
|
|
}
|
|
|
|
__fi xIndirect<OperandType> operator+(const sptr imm) const { return xIndirect(*this).Add(imm); }
|
|
__fi xIndirect<OperandType> operator-(const sptr imm) const { return xIndirect(*this).Add(-imm); }
|
|
|
|
bool operator==(const xIndirect<OperandType>& src) const
|
|
{
|
|
return (Base == src.Base) && (Index == src.Index) &&
|
|
(Scale == src.Scale) && (Displacement == src.Displacement);
|
|
}
|
|
|
|
bool operator!=(const xIndirect<OperandType>& src) const
|
|
{
|
|
return !operator==(src);
|
|
}
|
|
|
|
protected:
|
|
void Reduce();
|
|
};
|
|
|
|
typedef xIndirect<u128> xIndirect128;
|
|
typedef xIndirect<u64> xIndirect64;
|
|
typedef xIndirect<u32> xIndirect32;
|
|
typedef xIndirect<u16> xIndirect16;
|
|
typedef xIndirect<u8> xIndirect8;
|
|
typedef xIndirect<u64> xIndirectNative;
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xIndirect64orLess - base class 64, 32, 16, and 8 bit operand types
|
|
// --------------------------------------------------------------------------------------
|
|
class xIndirect64orLess : public xIndirectVoid
|
|
{
|
|
typedef xIndirectVoid _parent;
|
|
|
|
public:
|
|
xIndirect64orLess(const xIndirect8& src)
|
|
: _parent(src)
|
|
{
|
|
}
|
|
xIndirect64orLess(const xIndirect16& src)
|
|
: _parent(src)
|
|
{
|
|
}
|
|
xIndirect64orLess(const xIndirect32& src)
|
|
: _parent(src)
|
|
{
|
|
}
|
|
xIndirect64orLess(const xIndirect64& src)
|
|
: _parent(src)
|
|
{
|
|
}
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xAddressIndexer
|
|
// --------------------------------------------------------------------------------------
|
|
// This is a type-translation "interface class" which provisions our ptr[] syntax.
|
|
// xAddressReg types go in, and xIndirectVoid derived types come out.
|
|
//
|
|
template <typename xModSibType>
|
|
class xAddressIndexer
|
|
{
|
|
public:
|
|
// passthrough instruction, allows ModSib to pass silently through ptr translation
|
|
// without doing anything and without compiler error.
|
|
const xModSibType& operator[](const xModSibType& src) const { return src; }
|
|
|
|
xModSibType operator[](const xAddressReg& src) const
|
|
{
|
|
return xModSibType(src, xEmptyReg);
|
|
}
|
|
|
|
xModSibType operator[](const xAddressVoid& src) const
|
|
{
|
|
return xModSibType(src.Base, src.Index, src.Factor, src.Displacement);
|
|
}
|
|
|
|
xModSibType operator[](const void* src) const
|
|
{
|
|
return xModSibType((uptr)src);
|
|
}
|
|
};
|
|
|
|
// ptr[] - use this form for instructions which can resolve the address operand size from
|
|
// the other register operand sizes.
|
|
extern const xAddressIndexer<xIndirectVoid> ptr;
|
|
extern const xAddressIndexer<xIndirectNative> ptrNative;
|
|
extern const xAddressIndexer<xIndirect128> ptr128;
|
|
extern const xAddressIndexer<xIndirect64> ptr64;
|
|
extern const xAddressIndexer<xIndirect32> ptr32;
|
|
extern const xAddressIndexer<xIndirect16> ptr16;
|
|
extern const xAddressIndexer<xIndirect8> ptr8;
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// xForwardJump
|
|
// --------------------------------------------------------------------------------------
|
|
// Primary use of this class is through the various xForwardJA8/xForwardJLE32/etc. helpers
|
|
// defined later in this header. :)
|
|
//
|
|
|
|
class xForwardJumpBase
|
|
{
|
|
public:
|
|
// pointer to base of the instruction *Following* the jump. The jump address will be
|
|
// relative to this address.
|
|
s8* BasePtr;
|
|
|
|
public:
|
|
xForwardJumpBase(uint opsize, JccComparisonType cctype);
|
|
|
|
protected:
|
|
void _setTarget(uint opsize) const;
|
|
};
|
|
|
|
template <typename OperandType>
|
|
class xForwardJump : public xForwardJumpBase
|
|
{
|
|
public:
|
|
static const uint OperandSize = sizeof(OperandType);
|
|
|
|
// The jump instruction is emitted at the point of object construction. The conditional
|
|
// type must be valid (Jcc_Unknown generates an assertion).
|
|
xForwardJump(JccComparisonType cctype = Jcc_Unconditional)
|
|
: xForwardJumpBase(OperandSize, cctype)
|
|
{
|
|
}
|
|
|
|
// Sets the jump target by writing back the current x86Ptr to the jump instruction.
|
|
// This method can be called multiple times, re-writing the jump instruction's target
|
|
// in each case. (the the last call is the one that takes effect).
|
|
void SetTarget() const
|
|
{
|
|
_setTarget(OperandSize);
|
|
}
|
|
};
|
|
|
|
static __fi xAddressVoid operator+(const void* addr, const xAddressReg& reg)
|
|
{
|
|
return reg + (sptr)addr;
|
|
}
|
|
|
|
static __fi xAddressVoid operator+(sptr addr, const xAddressReg& reg)
|
|
{
|
|
return reg + (sptr)addr;
|
|
}
|
|
} // namespace x86Emitter
|
|
|
|
#include "implement/helpers.h"
|
|
|
|
#include "implement/simd_helpers.h"
|
|
#include "implement/simd_moremovs.h"
|
|
#include "implement/simd_arithmetic.h"
|
|
#include "implement/simd_comparisons.h"
|
|
#include "implement/simd_shufflepack.h"
|
|
|
|
#include "implement/group1.h"
|
|
#include "implement/group2.h"
|
|
#include "implement/group3.h"
|
|
#include "implement/movs.h" // cmov and movsx/zx
|
|
#include "implement/dwshift.h" // doubleword shifts!
|
|
#include "implement/incdec.h"
|
|
#include "implement/test.h"
|
|
#include "implement/jmpcall.h"
|
|
|
|
#include "implement/bmi.h"
|
|
#include "implement/avx.h" |