mirror of
https://github.com/PCSX2/pcsx2.git
synced 2025-12-16 04:08:48 +00:00
In AVX, MOVAPS is the same size as all the other instructions. In Nehalem (one of the main targets of SSE4), there are bypass penalties for using the wrong mov.
1065 lines
31 KiB
C++
1065 lines
31 KiB
C++
// SPDX-FileCopyrightText: 2002-2025 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
|
|
|
|
/// This will switch all SSE instructions to generate AVX instructions instead
|
|
extern bool use_avx;
|
|
|
|
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);
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// __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"
|