Files
l 8f5b5a7596 emitter: retire legacy_instructions.h; inline the cursor accessors
Two related removals, and one optimisation that fell out of the second.

The cursor accessors go. xGetPtr(), xSetPtr() and
xGetAlignedCallTarget() were macros over x86Ptr, kept -- per their own
comment -- against a future change of storage scheme that never came
in fifteen years; xGetAlignedCallTarget() did no alignment at all, it
was the cursor under a misleading name. 23 files now read and assign
x86Ptr directly.

legacy_instructions.h goes with them. Its jump half (J8Rel/J32Rel and
the JE8/JMP32/... macros over them, plus x86SetJ8/J32/J32A) duplicated
what the C89 core already does with xe_fwd_jcc8/32 and xe_fwd_set8/32:
145 call sites across iFPU, iFPUd, iMMI, iR5900Branch, iR3000Atables
and iR3000A convert mechanically. Its SSE half -- eight
SSE*_XMM_to_XMM wrappers -- had exact xe_* equivalents already, same
prefix and opcode.

Four things needed more than a rename, and they are the reason this is
one commit rather than a sed:

  * x86SetJ32A padded to a 16-byte boundary with NOPs before patching.
    The core had no equivalent, so xe_fwd_set32_aligned is new rather
    than the alignment being quietly dropped.
  * The branch bookkeeping was typed u32* because the legacy API
    returned that; the C89 slot is e_u8*. recDoBranchImm's signature,
    recSetBranchEQ/L's returns, _setupBranchTest's function-pointer
    parameter (now a Jcc code) and ~20 locals follow.
  * JNS8 mapped to a condition the enum does not spell: there is no
    Jcc_NotSigned, the member is Jcc_Unsigned = 0x9. Mapped explicitly
    with a comment, since it reads like a typo otherwise.
  * The SSE wrappers were stored in function-pointer tables
    (recComOpXMM_to_XMM in iFPU, recFPUOpXMM_to_XMM in iFPUd). Macros
    have no address, so the tables become switches -- and since op is
    a constant at every call site, that removes an indirect call from
    the recompiler's emit path and lets the emission inline. That is
    the optimisation: the tables were costing a call to save nothing.

tests/emitter/newbind.cpp oracled the eight C wrappers; it now oracles
the macros the recompilers actually call, which is the better test
anyway, and the reference tree drops its include of the deleted
header.

Verification: gates 36/36 in both configurations, byte suites green
(95,692 + 112,118 + 27,337, zero divergent -- the newbind figure is
unchanged because the same 8x16x16 cases are still covered, through
the macros now), Tekken and the 2048 ELF clean at 3000 real-BIOS
frames, and all four JIT region hashes identical to the previous
build. 145 jump sites and two dispatch tables rewritten with no
change to a single emitted byte.
2026-08-21 06:41:44 +02:00

669 lines
19 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2010 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
/*
* ix86 core v0.9.1
*
* Original Authors (v0.6.2 and prior):
* linuzappz <[email protected]>
* alexey silinov
* goldfinger
* zerofrog(@gmail.com)
*
* Authors of v0.9.1:
* Jake.Stine(@gmail.com)
* cottonvibes(@gmail.com)
* sudonim([email protected])
*/
#include "tests/emitter/reference/internal.h"
// ------------------------------------------------------------------------
// Notes on Thread Local Storage:
// * TLS is pretty simple, and "just works" from a programmer perspective, with only
// some minor additional computational overhead (see performance notes below).
//
// * MSVC and GCC handle TLS differently internally, but behavior to the programmer is
// generally identical.
//
// Performance Considerations:
// * GCC's implementation involves an extra dereference from normal storage (possibly
// applies to x86-32 only -- x86-64 is untested).
//
// * MSVC's implementation involves *two* extra dereferences from normal storage because
// it has to look up the TLS heap pointer from the Windows Thread Storage Area. (in
// generated ASM code, this dereference is denoted by access to the fs:[2ch] address),
//
// * However, in either case, the optimizer usually optimizes it to a register so the
// extra overhead is minimal over a series of instructions.
//
// MSVC Notes:
// * Important!! the Full Optimization [/Ox] option effectively disables TLS optimizations
// in MSVC 2008 and earlier, causing generally significant code bloat. Not tested in
// VC2010 yet.
//
// * VC2010 generally does a superior job of optimizing TLS across inlined functions and
// class methods, compared to predecessors.
//
#define ModRM(mod, reg, rm) xWrite8(((mod) << 6) | ((reg) << 3) | (rm))
#define SibSB(ss, index, base) xWrite8(((ss) << 6) | ((index) << 3) | (base))
namespace x86Emitter
{
// Empty initializers are due to frivolously pointless GCC errors (it demands the
// objects be initialized even though they have no actual variable members).
// ------------------------------------------------------------------------
// clang-format off
// clang-format on
//////////////////////////////////////////////////////////////////////////////////////////
// Performance note: VC++ wants to use byte/word register form for the following
// ModRM/SibSB constructors when we use xWrite<u8>, and furthermore unrolls the
// the shift using a series of ADDs for the following results:
// add cl,cl
// add cl,cl
// add cl,cl
// or cl,bl
// add cl,cl
// ... etc.
//
// This is unquestionably bad optimization by Core2 standard, an generates tons of
// register aliases and false dependencies. (although may have been ideal for early-
// brand P4s with a broken barrel shifter?). The workaround is to do our own manual
// x86Ptr access and update using a u32 instead of u8. Thanks to little endianness,
// the same end result is achieved and no false dependencies are generated. The draw-
// back is that it clobbers 3 bytes past the end of the write, which could cause a
// headache for someone who himself is doing some kind of headache-inducing amount of
// recompiler SMC. So we don't do a work-around, and just hope for the compiler to
// stop sucking someday instead. :)
//
// (btw, I know this isn't a critical performance item by any means, but it's
// annoying simply because it *should* be an easy thing to optimize)
void EmitSibMagic(uint regfield, const void* address, int extraRIPOffset)
{
// Leaf encoder: cache the thread-local emit cursor in a local so the
// 2-3 writes below don't each re-resolve x86Ptr's TLS address. No
// nested emit calls occur between the load and the write-back, so the
// local can't go stale.
u8* p = x86Ptr;
sptr displacement = (sptr)address;
sptr ripRelative = (sptr)address - ((sptr)p + sizeof(s8) + sizeof(s32) + extraRIPOffset);
// Can we use a rip-relative address? (Prefer this over eiz because it's a byte shorter)
if (ripRelative == (s32)ripRelative)
{
*p++ = (u8)((0 << 6) | (regfield << 3) | ModRm_UseDisp32);
displacement = ripRelative;
}
else
{
*p++ = (u8)((0 << 6) | (regfield << 3) | ModRm_UseSib);
*p++ = (u8)((0 << 6) | (Sib_EIZ << 3) | Sib_UseDisp32);
}
{
const s32 disp32_ = (s32)displacement;
memcpy(p, &disp32_, sizeof(s32));
}
p += sizeof(s32);
x86Ptr = p;
}
//////////////////////////////////////////////////////////////////////////////////////////
// returns TRUE if this instruction requires SIB to be encoded, or FALSE if the
// instruction ca be encoded as ModRm alone.
static __fi bool NeedsSibMagic(const xIndirectVoid& info)
{
// no registers? no sibs!
// (xIndirectVoid::Reduce always places a register in Index, and optionally leaves
// Base empty if only register is specified)
if (!info.Index.IsEmpty())
{
// A scaled register needs a SIB
if (info.Scale != 0)
return true;
// two registers needs a SIB
if (!info.Base.IsEmpty())
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////////////////////
// Conditionally generates Sib encoding information!
//
// regfield - register field to be written to the ModRm. This is either a register specifier
// or an opcode extension. In either case, the instruction determines the value for us.
//
void EmitSibMagic(uint regfield, const xIndirectVoid& info, int extraRIPOffset)
{
// 3 bits also on x86_64 (so max is 8)
// We might need to mask it on x86_64
int displacement_size = (info.Displacement == 0) ? 0 :
((is_s8(info.Displacement)) ? 1 : 2);
if (!NeedsSibMagic(info))
{
// Use ModRm-only encoding, with the rm field holding an index/base register, if
// one has been specified. If neither register is specified then use Disp32 form,
// which is encoded as "EBP w/o displacement" (which is why EBP must always be
// encoded *with* a displacement of 0, if it would otherwise not have one).
if (info.Index.IsEmpty())
{
EmitSibMagic(regfield, (void*)info.Displacement, extraRIPOffset);
return;
}
else
{
// Compare the low 3 bits, not the register id: r13 encodes as
// rm=101 just like rbp and needs the same forced displacement,
// otherwise mod=00/rm=101 means RIP-relative and the disp32 it
// requires is never written.
if ((info.Index.Id & 7) == 5 && displacement_size == 0)
displacement_size = 1; // forces [ebp]/[r13] to be encoded as [reg+0]!
ModRM(displacement_size, regfield, info.Index.Id & 7);
// Likewise rm=100 always means "a SIB byte follows". rsp never
// reaches here (Reduce diverts it into the SIB path below), but
// r12 does, and without this the encoding is truncated and the
// next instruction's bytes are consumed as the SIB.
if ((info.Index.Id & 7) == 4)
SibSB(0, Sib_EIZ, info.Index.Id & 7);
}
}
else
{
// In order to encode "just" index*scale (and no base), we have to encode
// it as a special [index*scale + displacement] form, which is done by
// specifying EBP as the base register and setting the displacement field
// to zero. (same as ModRm w/o SIB form above, basically, except the
// ModRm_UseDisp flag is specified in the SIB instead of the ModRM field).
if (info.Base.IsEmpty())
{
ModRM(0, regfield, ModRm_UseSib);
SibSB(info.Scale, info.Index.Id, Sib_UseDisp32);
*(s32*)x86Ptr = info.Displacement;
x86Ptr += sizeof(s32);
return;
}
else
{
if ((info.Base.Id & 7) == 5 && displacement_size == 0)
displacement_size = 1; // forces [ebp]/[r13] to be encoded as [reg+0]!
ModRM(displacement_size, regfield, ModRm_UseSib);
SibSB(info.Scale, info.Index.Id & 7, info.Base.Id & 7);
}
}
if (displacement_size != 0)
{
if (displacement_size == 1)
{
*(s8*)x86Ptr = info.Displacement;
x86Ptr += sizeof(s8);
}
else
{
const s32 disp32_ = info.Displacement;
memcpy(x86Ptr, &disp32_, sizeof(s32));
x86Ptr += sizeof(s32);
}
}
}
// Writes a ModRM byte for "Direct" register access forms, which is used for all
// instructions taking a form of [reg,reg].
void EmitSibMagic(uint reg1, const xRegisterBase& reg2, int)
{
xWrite8((Mod_Direct << 6) | (reg1 << 3) | (reg2.Id & 7));
}
void EmitSibMagic(const xRegisterBase& reg1, const xRegisterBase& reg2, int)
{
xWrite8((Mod_Direct << 6) | ((reg1.Id & 7) << 3) | (reg2.Id & 7));
}
void EmitSibMagic(const xRegisterBase& reg1, const void* src, int extraRIPOffset)
{
EmitSibMagic(reg1.Id & 7, src, extraRIPOffset);
}
void EmitSibMagic(const xRegisterBase& reg1, const xIndirectVoid& sib, int extraRIPOffset)
{
EmitSibMagic(reg1.Id & 7, sib, extraRIPOffset);
}
//////////////////////////////////////////////////////////////////////////////////////////
void EmitRex(uint regfield, const void* address)
{
}
void EmitRex(uint regfield, const xIndirectVoid& info)
{
bool w = info._operandSize == 8;
bool r = false;
bool x = info.Index.IsExtended();
bool b = info.Base.IsExtended();
if (!NeedsSibMagic(info))
{
b = x;
x = false;
}
const u8 rex = 0x40 | (w << 3) | (r << 2) | (x << 1) | (u8)b;
if (rex != 0x40)
xWrite8(rex);
}
void EmitRex(uint reg1, const xRegisterBase& reg2)
{
bool w = reg2._operandSize == 8;
bool b = reg2.IsExtended();
bool ext8bit = (reg2._operandSize == 1 && reg2.Id >= 0x10);
const u8 rex = 0x40 | (w << 3) | (u8)b;
if (rex != 0x40 || ext8bit)
xWrite8(rex);
}
void EmitRex(const xRegisterBase& reg1, const xRegisterBase& reg2)
{
bool w = (reg1._operandSize == 8) || (reg2._operandSize == 8);
bool r = reg1.IsExtended();
bool b = reg2.IsExtended();
const u8 rex = 0x40 | (w << 3) | (r << 2) | (u8)b;
// Either operand can be spl/bpl/sil/dil, and those need a REX byte
// present even when it would otherwise be an empty 0x40 -- without one
// the same encoding names ah/ch/dh/bh instead.
bool ext8bit = (reg1._operandSize == 1 && reg1.Id >= 0x10) ||
(reg2._operandSize == 1 && reg2.Id >= 0x10);
if (rex != 0x40 || ext8bit)
xWrite8(rex);
}
void EmitRex(const xRegisterBase& reg1, const void* src)
{
bool w = reg1._operandSize == 8;
bool r = reg1.IsExtended();
const u8 rex = 0x40 | (w << 3) | (r << 2);
bool ext8bit = (reg1._operandSize == 1 && reg1.Id >= 0x10);
if (rex != 0x40 || ext8bit)
xWrite8(rex);
}
void EmitRex(const xRegisterBase& reg1, const xIndirectVoid& sib)
{
bool w = reg1._operandSize == 8 || sib._operandSize == 8;
bool r = reg1.IsExtended();
bool x = sib.Index.IsExtended();
bool b = sib.Base.IsExtended();
if (!NeedsSibMagic(sib))
{
b = x;
x = false;
}
const u8 rex = 0x40 | (w << 3) | (r << 2) | (x << 1) | (u8)b;
bool ext8bit = (reg1._operandSize == 1 && reg1.Id >= 0x10);
if (rex != 0x40 || ext8bit)
xWrite8(rex);
}
// For use by instructions that are implicitly wide
void EmitRexImplicitlyWide(const xRegisterBase& reg)
{
const u8 rex = 0x40 | (u8)reg.IsExtended();
if (rex != 0x40)
xWrite8(rex);
}
void EmitRexImplicitlyWide(const xIndirectVoid& sib)
{
bool x = sib.Index.IsExtended();
bool b = sib.Base.IsExtended();
if (!NeedsSibMagic(sib))
{
b = x;
x = false;
}
const u8 rex = 0x40 | (x << 1) | (u8)b;
if (rex != 0x40)
xWrite8(rex);
}
// --------------------------------------------------------------------------------------
// xRegisterInt (method implementations)
// --------------------------------------------------------------------------------------
xRegisterInt xRegisterInt::MatchSizeTo(xRegisterInt other) const
{
return other._operandSize == 1 ? xRegisterInt(xRegister8(*this)) : xRegisterInt(other._operandSize, Id);
}
xIndirectVoid& xIndirectVoid::Add(sptr imm)
{
Displacement += imm;
return *this;
}
// ------------------------------------------------------------------------
// Internal implementation of EmitSibMagic which has been custom tailored
// to optimize special forms of the Lea instructions accordingly, such
// as when a LEA can be replaced with a "MOV reg,imm" or "MOV reg,reg".
//
// preserve_flags - set to ture to disable use of SHL on [Index*Base] forms
// of LEA, which alters flags states.
//
static void EmitLeaMagic(const xRegisterInt& to, const xIndirectVoid& src, bool preserve_flags)
{
int displacement_size = (src.Displacement == 0) ? 0 :
((is_s8(src.Displacement)) ? 1 : 2);
// See EmitSibMagic for commenting on SIB encoding.
if (!NeedsSibMagic(src) && src.Displacement == (s32)src.Displacement)
{
// LEA Land: means we have either 1-register encoding or just an offset.
// offset is encodable as an immediate MOV, and a register is encodable
// as a register MOV.
if (src.Index.IsEmpty())
{
xMOV(to, src.Displacement);
return;
}
else if (displacement_size == 0)
{
_xMovRtoR(to, src.Index.MatchSizeTo(to));
return;
}
else if (!preserve_flags)
{
// encode as MOV and ADD combo. Make sure to use the immediate on the
// ADD since it can encode as an 8-bit sign-extended value.
_xMovRtoR(to, src.Index.MatchSizeTo(to));
xADD(to, src.Displacement);
return;
}
}
else
{
if (src.Base.IsEmpty())
{
if (!preserve_flags && (displacement_size == 0))
{
// Encode [Index*Scale] as a combination of Mov and Shl.
// This is more efficient because of the bloated LEA format which requires
// a 32 bit displacement, and the compact nature of the alternative.
//
// (this does not apply to older model P4s with the broken barrel shifter,
// but we currently aren't optimizing for that target anyway).
_xMovRtoR(to, src.Index.MatchSizeTo(to));
xSHL(to, src.Scale);
return;
}
}
else
{
if (src.Scale == 0)
{
if (!preserve_flags)
{
if (src.Index == rsp)
{
// ESP is not encodable as an index (ix86 ignores it), thus:
_xMovRtoR(to, src.Base.MatchSizeTo(to)); // will do the trick!
if (src.Displacement)
xADD(to, src.Displacement);
return;
}
else if (src.Displacement == 0)
{
_xMovRtoR(to, src.Base.MatchSizeTo(to));
xADD(to, src.Index.MatchSizeTo(to));
return;
}
}
else if ((src.Index == rsp) && (src.Displacement == 0))
{
// special case handling of ESP as Index, which is replaceable with
// a single MOV even when preserve_flags is set! :D
_xMovRtoR(to, src.Base.MatchSizeTo(to));
return;
}
}
}
}
xOpWrite(0, 0x8d, to, src);
}
#ifndef PCSX2_C89_EMITTER
__fi void xLEA(xRegister64 to, const xIndirectVoid& src, bool preserve_flags)
{
EmitLeaMagic(to, src, preserve_flags);
}
__fi void xLEA(xRegister32 to, const xIndirectVoid& src, bool preserve_flags)
{
EmitLeaMagic(to, src, preserve_flags);
}
#endif
// Always built: the 16-bit form is not switched (see instructions.h).
__fi void xLEA(xRegister16 to, const xIndirectVoid& src, bool preserve_flags)
{
xWrite8(0x66);
EmitLeaMagic(to, src, preserve_flags);
}
// =====================================================================================================
// TEST / INC / DEC
// =====================================================================================================
void xImpl_Test::operator()(const xRegisterInt& to, const xRegisterInt& from) const
{
xOpWrite(to.GetPrefix16(), to.Is8BitOp() ? 0x84 : 0x85, from, to);
}
void xImpl_Test::operator()(const xIndirect64orLess& dest, int imm) const
{
xOpWrite(dest.GetPrefix16(), dest.Is8BitOp() ? 0xf6 : 0xf7, 0, dest, dest.GetImmSize());
dest.xWriteImm(imm);
}
void xImpl_Test::operator()(const xRegisterInt& to, int imm) const
{
if (to.Id == 0)
{
xOpAccWrite(to.GetPrefix16(), to.Is8BitOp() ? 0xa8 : 0xa9, 0, to);
}
else
{
xOpWrite(to.GetPrefix16(), to.Is8BitOp() ? 0xf6 : 0xf7, 0, to);
}
to.xWriteImm(imm);
}
void xImpl_BitScan::operator()(const xRegister16or32or64& to, const xRegister16or32or64& from) const
{
xOpWrite0F(from->GetPrefix16(), Opcode, to, from);
}
void xImpl_BitScan::operator()(const xRegister16or32or64& to, const xIndirectVoid& sibsrc) const
{
xOpWrite0F(to->GetPrefix16(), Opcode, to, sibsrc);
}
void xImpl_IncDec::operator()(const xRegisterInt& to) const
{
if (to.Is8BitOp())
{
u8 regfield = isDec ? 1 : 0;
xOpWrite(to.GetPrefix16(), 0xfe, regfield, to);
}
else
{
xOpWrite(to.GetPrefix16(), 0xff, isDec ? 1 : 0, to);
}
}
void xImpl_IncDec::operator()(const xIndirect64orLess& to) const
{
if (to._operandSize == 2)
xWrite8(0x66);
xWrite8(to.Is8BitOp() ? 0xfe : 0xff);
EmitSibMagic(isDec ? 1 : 0, to);
}
#ifndef PCSX2_C89_EMITTER
const xImpl_Test xTEST = {};
#endif
#ifndef PCSX2_C89_EMITTER
const xImpl_BitScan xBSR = {0xbd};
#endif
#ifndef PCSX2_C89_EMITTER
const xImpl_IncDec xINC = {false};
const xImpl_IncDec xDEC = {true};
#endif
//////////////////////////////////////////////////////////////////////////////////////////
// Push / Pop Emitters
//
// Note: pushad/popad implementations are intentionally left out. The instructions are
// invalid in x64, and are super slow on x32. Use multiple Push/Pop instructions instead.
#ifndef PCSX2_C89_EMITTER
__fi void xPOP(const xIndirectVoid& from)
{
EmitRexImplicitlyWide(from);
xWrite8(0x8f);
EmitSibMagic(0, from);
}
#endif
#ifndef PCSX2_C89_EMITTER
__fi void xPUSH(const xIndirectVoid& from)
{
EmitRexImplicitlyWide(from);
xWrite8(0xff);
EmitSibMagic(6, from);
}
#endif
#ifndef PCSX2_C89_EMITTER
__fi void xPOP(xRegister32or64 from)
{
EmitRexImplicitlyWide(from);
xWrite8(0x58 | (from->Id & 7));
}
#endif
#ifndef PCSX2_C89_EMITTER
__fi void xPUSH(u32 imm)
{
if (is_s8(imm))
{
xWrite8(0x6a);
xWrite8(imm);
}
else
{
xWrite8(0x68);
xWrite32(imm);
}
}
#endif
#ifndef PCSX2_C89_EMITTER
__fi void xPUSH(xRegister32or64 from)
{
EmitRexImplicitlyWide(from);
xWrite8(0x50 | (from->Id & 7));
}
#endif
//////////////////////////////////////////////////////////////////////////////////////////
//
#ifndef PCSX2_C89_EMITTER
xAddressVoid xComplexAddress(const xAddressReg& tmpRegister, void* base, const xAddressVoid& offset)
{
if ((sptr)base == (s32)(sptr)base)
{
return offset + base;
}
else
{
xLEA(tmpRegister, ptr[base]);
return offset + tmpRegister;
}
}
void xLoadFarAddr(const xAddressReg& dst, void* addr)
{
sptr iaddr = (sptr)addr;
sptr rip = (sptr)x86Ptr + 7; // LEA will be 7 bytes
sptr disp = iaddr - rip;
if (disp == (s32)disp)
{
xLEA(dst, ptr[addr]);
}
else
{
xMOV64(dst, iaddr);
}
}
void xWriteImm64ToMem(u64* addr, const xAddressReg& tmp, u64 imm)
{
xImm64Op(xMOV, ptr64[addr], tmp, imm);
}
#endif
} // End namespace x86Emitter