mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-07 00:00:17 +02:00
Suggested by Alyssa. Adding an IR operation can be a little tedious
since you need to add the definition to JIT.cpp for the dispatch switch,
JITClass.h for the function declared, and then actually defining the
implementation in the correct file.
Instead support the common case where an IR operation just gets
dispatched through to the regular handler. This lets the developer just
put the function definition in to the json and the relevent cpp file and
it just gets picked up.
Some minor things:
- Needs to support dynamic dispatch for {Load,Store}Register and
{Load,Store}Mem
- This is just a bool in the json
- It needs to not output JIT dispatch for some IR operations
- SSE4.2 string instructions and x87 operations
- These go down the "Unhandled" path
- Needs to support a Dispatcher function override
- This is just for handling NoOp IR operations that get used for
other reasons.
- Finally removes VSMul and VUMul, consolidating to VMul
- Unlike V{U,S}Mull, signed or unsigned doesn't change behaviour here
- Fixed a couple random handler names not matching the IR operation
name.
7669 lines
298 KiB
C++
7669 lines
298 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
tags: frontend|x86-to-ir, opcodes|dispatcher-implementations
|
|
desc: Handles x86/64 ops to IR, no-pf opt, local-flags opt
|
|
$end_info$
|
|
*/
|
|
|
|
#include "FEXCore/Utils/Telemetry.h"
|
|
#include "Interface/Context/Context.h"
|
|
#include "Interface/Core/OpcodeDispatcher.h"
|
|
#include "Interface/Core/X86Tables/X86Tables.h"
|
|
|
|
#include <FEXCore/Config/Config.h>
|
|
#include <FEXCore/Core/Context.h>
|
|
#include <FEXCore/Core/CoreState.h>
|
|
#include <FEXCore/Core/X86Enums.h>
|
|
#include <FEXCore/HLE/SyscallHandler.h>
|
|
#include <FEXCore/IR/IR.h>
|
|
#include <FEXCore/IR/IREmitter.h>
|
|
#include <FEXCore/IR/IntrusiveIRList.h>
|
|
#include <FEXCore/Utils/EnumUtils.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <tuple>
|
|
|
|
namespace FEXCore::IR {
|
|
|
|
using X86Tables::OpToIndex;
|
|
|
|
#define OpcodeArgs [[maybe_unused]] FEXCore::X86Tables::DecodedOp Op
|
|
|
|
void OpDispatchBuilder::SyscallOp(OpcodeArgs) {
|
|
constexpr size_t SyscallArgs = 7;
|
|
using SyscallArray = std::array<uint64_t, SyscallArgs>;
|
|
|
|
size_t NumArguments{};
|
|
const SyscallArray *GPRIndexes {};
|
|
static constexpr SyscallArray GPRIndexes_64 = {
|
|
FEXCore::X86State::REG_RAX,
|
|
FEXCore::X86State::REG_RDI,
|
|
FEXCore::X86State::REG_RSI,
|
|
FEXCore::X86State::REG_RDX,
|
|
FEXCore::X86State::REG_R10,
|
|
FEXCore::X86State::REG_R8,
|
|
FEXCore::X86State::REG_R9,
|
|
};
|
|
static constexpr SyscallArray GPRIndexes_32 = {
|
|
FEXCore::X86State::REG_RAX,
|
|
FEXCore::X86State::REG_RBX,
|
|
FEXCore::X86State::REG_RCX,
|
|
FEXCore::X86State::REG_RDX,
|
|
FEXCore::X86State::REG_RSI,
|
|
FEXCore::X86State::REG_RDI,
|
|
FEXCore::X86State::REG_RBP,
|
|
};
|
|
|
|
static constexpr SyscallArray GPRIndexes_Hangover = {
|
|
FEXCore::X86State::REG_RCX,
|
|
};
|
|
|
|
static constexpr SyscallArray GPRIndexes_Win64 = {
|
|
FEXCore::X86State::REG_RAX,
|
|
FEXCore::X86State::REG_R10,
|
|
FEXCore::X86State::REG_RDX,
|
|
FEXCore::X86State::REG_R8,
|
|
FEXCore::X86State::REG_R9,
|
|
FEXCore::X86State::REG_RSP,
|
|
};
|
|
|
|
SyscallFlags DefaultSyscallFlags = FEXCore::IR::SyscallFlags::DEFAULT;
|
|
|
|
const auto OSABI = CTX->SyscallHandler->GetOSABI();
|
|
if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX64) {
|
|
NumArguments = GPRIndexes_64.size();
|
|
GPRIndexes = &GPRIndexes_64;
|
|
}
|
|
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_LINUX32) {
|
|
NumArguments = GPRIndexes_32.size();
|
|
GPRIndexes = &GPRIndexes_32;
|
|
}
|
|
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_WIN64) {
|
|
NumArguments = 6;
|
|
GPRIndexes = &GPRIndexes_Win64;
|
|
DefaultSyscallFlags = FEXCore::IR::SyscallFlags::NORETURNEDRESULT;
|
|
}
|
|
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_WIN32) {
|
|
// Since the whole context is going to be saved at entry anyway, theres no need to do additional work to pass in args
|
|
NumArguments = 0;
|
|
GPRIndexes = nullptr;
|
|
DefaultSyscallFlags = FEXCore::IR::SyscallFlags::NORETURNEDRESULT;
|
|
}
|
|
else if (OSABI == FEXCore::HLE::SyscallOSABI::OS_HANGOVER) {
|
|
NumArguments = 1;
|
|
GPRIndexes = &GPRIndexes_Hangover;
|
|
}
|
|
else {
|
|
LogMan::Msg::DFmt("Unhandled OSABI syscall");
|
|
}
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
auto NewRIP = GetRelocatedPC(Op, -Op->InstSize);
|
|
_StoreContext(GPRSize, GPRClass, NewRIP, offsetof(FEXCore::Core::CPUState, rip));
|
|
|
|
const auto& GPRIndicesRef = *GPRIndexes;
|
|
|
|
OrderedNode *Arguments[SyscallArgs] {
|
|
InvalidNode,
|
|
InvalidNode,
|
|
InvalidNode,
|
|
InvalidNode,
|
|
InvalidNode,
|
|
InvalidNode,
|
|
InvalidNode,
|
|
};
|
|
for (size_t i = 0; i < NumArguments; ++i) {
|
|
Arguments[i] = LoadGPRRegister(GPRIndicesRef[i]);
|
|
}
|
|
|
|
auto SyscallOp = _Syscall(
|
|
Arguments[0],
|
|
Arguments[1],
|
|
Arguments[2],
|
|
Arguments[3],
|
|
Arguments[4],
|
|
Arguments[5],
|
|
Arguments[6],
|
|
DefaultSyscallFlags);
|
|
|
|
if (OSABI != FEXCore::HLE::SyscallOSABI::OS_HANGOVER &&
|
|
(DefaultSyscallFlags & FEXCore::IR::SyscallFlags::NORETURNEDRESULT) != FEXCore::IR::SyscallFlags::NORETURNEDRESULT) {
|
|
// Hangover doesn't want us returning a result here
|
|
// syscall is being abused as a thunk for now.
|
|
StoreGPRRegister(X86State::REG_RAX, SyscallOp);
|
|
}
|
|
|
|
if (Op->TableInfo->Flags & X86Tables::InstFlags::FLAGS_BLOCK_END) {
|
|
// RIP could have been updated after coming back from the Syscall.
|
|
NewRIP = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, rip));
|
|
CalculateDeferredFlags();
|
|
_ExitFunction(NewRIP);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::ThunkOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
uint8_t *sha256 = (uint8_t *)(Op->PC + 2);
|
|
|
|
if (CTX->Config.Is64BitMode) {
|
|
// x86-64 ABI puts the function argument in RDI
|
|
_Thunk(
|
|
LoadGPRRegister(X86State::REG_RDI),
|
|
*reinterpret_cast<SHA256Sum*>(sha256)
|
|
);
|
|
}
|
|
else {
|
|
// x86 fastcall ABI puts the function argument in ECX
|
|
_Thunk(
|
|
LoadGPRRegister(X86State::REG_RCX),
|
|
*reinterpret_cast<SHA256Sum*>(sha256)
|
|
);
|
|
}
|
|
|
|
auto Constant = _Constant(GPRSize);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewRIP = _LoadMem(GPRClass, GPRSize, OldSP, GPRSize);
|
|
OrderedNode *NewSP = _Add(IR::SizeToOpSize(GPRSize), OldSP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
CalculateDeferredFlags();
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
BlockSetRIP = true;
|
|
}
|
|
|
|
void OpDispatchBuilder::LEAOp(OpcodeArgs) {
|
|
// LEA specifically ignores segment prefixes
|
|
const auto SrcSize = GetSrcSize(Op);
|
|
|
|
if (CTX->Config.Is64BitMode) {
|
|
const uint32_t DstSize = X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0) == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? 2 :
|
|
X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0) == X86Tables::DecodeFlags::FLAG_WIDENING_SIZE_LAST ? 8 : 4;
|
|
|
|
auto Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], SrcSize, Op->Flags, -1, false);
|
|
if (DstSize != SrcSize) {
|
|
// If the SrcSize isn't the DstSize then we need to zero extend.
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
Src = _Bfe(IR::SizeToOpSize(GPRSize), SrcSize * 8, 0, Src);
|
|
}
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Src, DstSize, -1);
|
|
}
|
|
else {
|
|
uint32_t DstSize = X86Tables::DecodeFlags::GetOpAddr(Op->Flags, 0) == X86Tables::DecodeFlags::FLAG_OPERAND_SIZE_LAST ? 2 : 4;
|
|
|
|
auto Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], SrcSize, Op->Flags, -1, false);
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Src, DstSize, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::NOPOp(OpcodeArgs) {
|
|
}
|
|
|
|
void OpDispatchBuilder::RETOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// ABI Optimization: Flags don't survive calls or rets
|
|
if (CTX->Config.ABILocalFlags) {
|
|
_InvalidateFlags(~0UL); // all flags
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
}
|
|
else {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
}
|
|
|
|
auto Constant = _Constant(GPRSize);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewRIP = _LoadMem(GPRClass, GPRSize, OldSP, GPRSize);
|
|
|
|
OrderedNode *NewSP;
|
|
if (Op->OP == 0xC2) {
|
|
auto Offset = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
NewSP = _Add(IR::SizeToOpSize(GPRSize), _Add(IR::SizeToOpSize(GPRSize), OldSP, Constant), Offset);
|
|
}
|
|
else {
|
|
NewSP = _Add(IR::SizeToOpSize(GPRSize), OldSP, Constant);
|
|
}
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
CalculateDeferredFlags();
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
BlockSetRIP = true;
|
|
}
|
|
|
|
/*
|
|
stack contains:
|
|
Size of each member is 64-bit, 32-bit, or 16-bit depending on operating size
|
|
RIP
|
|
CS
|
|
EFLAGS
|
|
RSP
|
|
SS
|
|
*/
|
|
void OpDispatchBuilder::IRETOp(OpcodeArgs) {
|
|
// Operand Size override unsupported!
|
|
if ((Op->Flags & X86Tables::DecodeFlags::FLAG_OPERAND_SIZE) != 0) {
|
|
LogMan::Msg::EFmt("IRET only implemented for 64bit and 32bit sizes");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
auto Constant = _Constant(GPRSize);
|
|
|
|
auto SP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
// RIP (64/32/16 bits)
|
|
auto NewRIP = _LoadMem(GPRClass, GPRSize, SP, GPRSize);
|
|
SP = _Add(IR::SizeToOpSize(GPRSize), SP, Constant);
|
|
// CS (lower 16 used)
|
|
auto NewSegmentCS = _LoadMem(GPRClass, GPRSize, SP, GPRSize);
|
|
_StoreContext(2, GPRClass, NewSegmentCS, offsetof(FEXCore::Core::CPUState, cs_idx));
|
|
UpdatePrefixFromSegment(NewSegmentCS, FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX);
|
|
|
|
SP = _Add(IR::SizeToOpSize(GPRSize), SP, Constant);
|
|
//eflags (lower 16 used)
|
|
auto eflags = _LoadMem(GPRClass, GPRSize, SP, GPRSize);
|
|
SetPackedRFLAG(false, eflags);
|
|
SP = _Add(IR::SizeToOpSize(GPRSize), SP, Constant);
|
|
|
|
if (CTX->Config.Is64BitMode) {
|
|
// RSP and SS only happen in 64-bit mode or if this is a CPL mode jump!
|
|
// FEX doesn't support a CPL mode switch, so don't need to worry about this on 32-bit
|
|
StoreGPRRegister(X86State::REG_RSP, _LoadMem(GPRClass, GPRSize, SP, GPRSize));
|
|
|
|
SP = _Add(IR::SizeToOpSize(GPRSize), SP, Constant);
|
|
// ss
|
|
auto NewSegmentSS = _LoadMem(GPRClass, GPRSize, SP, GPRSize);
|
|
_StoreContext(2, GPRClass, NewSegmentSS, offsetof(FEXCore::Core::CPUState, ss_idx));
|
|
UpdatePrefixFromSegment(NewSegmentSS, FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX);
|
|
|
|
SP = _Add(IR::SizeToOpSize(GPRSize), SP, Constant);
|
|
}
|
|
else {
|
|
// Store the stack in 32-bit mode
|
|
StoreGPRRegister(X86State::REG_RSP, SP);
|
|
}
|
|
|
|
CalculateDeferredFlags();
|
|
_ExitFunction(NewRIP);
|
|
BlockSetRIP = true;
|
|
}
|
|
|
|
void OpDispatchBuilder::CallbackReturnOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
// Store the new RIP
|
|
_CallbackReturn();
|
|
auto NewRIP = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, rip));
|
|
CalculateDeferredFlags();
|
|
// This ExitFunction won't actually get hit but needs to exist
|
|
_ExitFunction(NewRIP);
|
|
BlockSetRIP = true;
|
|
}
|
|
|
|
void OpDispatchBuilder::SecondaryALUOp(OpcodeArgs) {
|
|
FEXCore::IR::IROps IROp;
|
|
#define OPD(group, prefix, Reg) (((group - FEXCore::X86Tables::TYPE_GROUP_1) << 6) | (prefix) << 3 | (Reg))
|
|
switch (Op->OP) {
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 0):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 0):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 0):
|
|
IROp = FEXCore::IR::IROps::OP_ADD;
|
|
break;
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 1):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 1):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 1):
|
|
IROp = FEXCore::IR::IROps::OP_OR;
|
|
break;
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 4):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 4):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 4):
|
|
IROp = FEXCore::IR::IROps::OP_AND;
|
|
break;
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 5):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 5):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 5):
|
|
IROp = FEXCore::IR::IROps::OP_SUB;
|
|
break;
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 6):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 6):
|
|
case OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 6):
|
|
IROp = FEXCore::IR::IROps::OP_XOR;
|
|
break;
|
|
default:
|
|
IROp = FEXCore::IR::IROps::OP_LAST;
|
|
LOGMAN_MSG_A_FMT("Unknown ALU Op: 0x{:x}", Op->OP);
|
|
break;
|
|
};
|
|
#undef OPD
|
|
// X86 basic ALU ops just do the operation between the destination and a single source
|
|
auto Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
uint8_t Size = GetDstSize(Op);
|
|
OrderedNode *Result{};
|
|
OrderedNode *Dest{};
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
auto DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
switch (IROp) {
|
|
case FEXCore::IR::IROps::OP_ADD: {
|
|
Dest = _AtomicFetchAdd(IR::SizeToOpSize(Size), Src, DestMem);
|
|
Result = _Add(IR::SizeToOpSize(std::max<uint8_t>(4u, std::max(GetOpSize(Dest), GetOpSize(Src)))), Dest, Src);
|
|
break;
|
|
}
|
|
case FEXCore::IR::IROps::OP_SUB: {
|
|
Dest = _AtomicFetchSub(IR::SizeToOpSize(Size), Src, DestMem);
|
|
Result = _Sub(IR::SizeToOpSize(std::max<uint8_t>(4u, std::max(GetOpSize(Dest), GetOpSize(Src)))), Dest, Src);
|
|
break;
|
|
}
|
|
case FEXCore::IR::IROps::OP_OR: {
|
|
Dest = _AtomicFetchOr(IR::SizeToOpSize(Size), Src, DestMem);
|
|
Result = _Or(IR::SizeToOpSize(std::max<uint8_t>(4u, std::max(GetOpSize(Dest), GetOpSize(Src)))), Dest, Src);
|
|
break;
|
|
}
|
|
case FEXCore::IR::IROps::OP_AND: {
|
|
Dest = _AtomicFetchAnd(IR::SizeToOpSize(Size), Src, DestMem);
|
|
Result = _And(IR::SizeToOpSize(std::max<uint8_t>(4u, std::max(GetOpSize(Dest), GetOpSize(Src)))), Dest, Src);
|
|
break;
|
|
}
|
|
case FEXCore::IR::IROps::OP_XOR: {
|
|
Dest = _AtomicFetchXor(IR::SizeToOpSize(Size), Src, DestMem);
|
|
Result = _Xor(IR::SizeToOpSize(std::max<uint8_t>(4u, std::max(GetOpSize(Dest), GetOpSize(Src)))), Dest, Src);
|
|
break;
|
|
}
|
|
default:
|
|
LOGMAN_MSG_A_FMT("Unknown Atomic IR Op: {}", ToUnderlying(IROp));
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
auto ALUOp = _Add(IR::SizeToOpSize(std::max<uint8_t>(4u, Size)), Dest, Src);
|
|
// Overwrite our IR's op type
|
|
ALUOp.first->Header.Op = IROp;
|
|
|
|
Result = ALUOp;
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
// Flags set
|
|
{
|
|
switch (IROp) {
|
|
case FEXCore::IR::IROps::OP_ADD:
|
|
GenerateFlags_ADD(Op, Result, Dest, Src);
|
|
break;
|
|
case FEXCore::IR::IROps::OP_SUB:
|
|
GenerateFlags_SUB(Op, Result, Dest, Src);
|
|
break;
|
|
case FEXCore::IR::IROps::OP_AND:
|
|
case FEXCore::IR::IROps::OP_XOR:
|
|
case FEXCore::IR::IROps::OP_OR: {
|
|
GenerateFlags_Logical(Op, Result, Dest, Src);
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::ADCOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
uint8_t Size = GetDstSize(Op);
|
|
const auto OpSize = IR::SizeToOpSize(std::max<uint8_t>(4u, Size));
|
|
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
auto ALUOp = _Add(OpSize, Src, CF);
|
|
|
|
OrderedNode *Result{};
|
|
OrderedNode *Before{};
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
Before = _AtomicFetchAdd(IR::SizeToOpSize(Size), ALUOp, DestMem);
|
|
Result = _Add(OpSize, Before, ALUOp);
|
|
}
|
|
else {
|
|
Before = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
Result = _Add(OpSize, Before, ALUOp);
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
if (Size < 4)
|
|
Result = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, Size)), Size * 8, 0, Result);
|
|
GenerateFlags_ADC(Op, Result, Before, Src, CF);
|
|
}
|
|
|
|
template<uint32_t SrcIndex, bool SetFlags>
|
|
void OpDispatchBuilder::SBBOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
auto Size = GetDstSize(Op);
|
|
const auto OpSize = IR::SizeToOpSize(std::max<uint8_t>(4u, Size));
|
|
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
auto ALUOp = _Add(OpSize, Src, CF);
|
|
|
|
OrderedNode *Result{};
|
|
OrderedNode *Before{};
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
Before = _AtomicFetchSub(IR::SizeToOpSize(Size), ALUOp, DestMem);
|
|
Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Before, ALUOp);
|
|
}
|
|
else {
|
|
Before = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Before, ALUOp);
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
if (SetFlags) {
|
|
if (Size < 4) {
|
|
Result = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, Size)), Size * 8, 0, Result);
|
|
}
|
|
GenerateFlags_SBB(Op, Result, Before, Src, CF);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::PUSHOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
auto NewSP = _Push(GPRSize, Size, Src, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
}
|
|
|
|
void OpDispatchBuilder::PUSHREGOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
auto NewSP = _Push(GPRSize, Size, Src, OldSP);
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
}
|
|
|
|
void OpDispatchBuilder::PUSHAOp(OpcodeArgs) {
|
|
// 32bit only
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
// PUSHA order:
|
|
// Tmp = SP
|
|
// push EAX
|
|
// push ECX
|
|
// push EDX
|
|
// push EBX
|
|
// push Tmp
|
|
// push EBP
|
|
// push ESI
|
|
// push EDI
|
|
|
|
OrderedNode *Src{};
|
|
OrderedNode *NewSP = OldSP;
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RAX);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RCX);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RDX);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RBX);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
// Push old-sp
|
|
NewSP = _Push(GPRSize, Size, OldSP, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RBP);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RSI);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
Src = LoadGPRRegister(X86State::REG_RDI);
|
|
NewSP = _Push(GPRSize, Size, Src, NewSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP, 4);
|
|
}
|
|
|
|
template<uint32_t SegmentReg>
|
|
void OpDispatchBuilder::PUSHSegmentOp(OpcodeArgs) {
|
|
const uint8_t SrcSize = GetSrcSize(Op);
|
|
const uint8_t DstSize = GetDstSize(Op);
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
OrderedNode *Src{};
|
|
if (!CTX->Config.Is64BitMode()) {
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, es_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, cs_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, ss_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, ds_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, fs_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, gs_idx));
|
|
break;
|
|
default: break; // Do nothing
|
|
}
|
|
}
|
|
else {
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, es_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, cs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, ss_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, ds_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX:
|
|
Src = _LoadContext(SrcSize, GPRClass, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
break;
|
|
default: break; // Do nothing
|
|
}
|
|
}
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
// Store our value to the new stack location
|
|
// AMD hardware zexts segment selector to 32bit
|
|
// Intel hardware inserts segment selector
|
|
auto NewSP = _Push(GPRSize, DstSize, Src, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
}
|
|
|
|
void OpDispatchBuilder::POPOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
auto Constant = _Constant(Size);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewGPR = _LoadMem(GPRClass, Size, OldSP, Size);
|
|
auto NewSP = _Add(OpSize::i64Bit, OldSP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
// Store what we loaded from the stack
|
|
StoreResult(GPRClass, Op, NewGPR, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::POPAOp(OpcodeArgs) {
|
|
// 32bit only
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
auto Constant = _Constant(Size);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
// POPA order:
|
|
// pop EDI
|
|
// pop ESI
|
|
// pop EBP
|
|
// ESP += 4; // Skip RSP because it'll be correct at the end
|
|
// pop EBX
|
|
// pop EDX
|
|
// pop ECX
|
|
// pop EAX
|
|
|
|
OrderedNode *Src{};
|
|
OrderedNode *NewSP = OldSP;
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RDI, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RSI, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RBP, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, _Constant(Size * 2));
|
|
|
|
// Skip SP loading
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RBX, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RDX, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RCX, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
Src = _LoadMem(GPRClass, Size, NewSP, Size);
|
|
StoreGPRRegister(X86State::REG_RAX, Src, Size);
|
|
NewSP = _Add(OpSize::i64Bit, NewSP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
}
|
|
|
|
template<uint32_t SegmentReg>
|
|
void OpDispatchBuilder::POPSegmentOp(OpcodeArgs) {
|
|
const uint8_t SrcSize = GetSrcSize(Op);
|
|
const uint8_t DstSize = GetDstSize(Op);
|
|
|
|
auto Constant = _Constant(SrcSize);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewSegment = _LoadMem(GPRClass, SrcSize, OldSP, SrcSize);
|
|
auto NewSP = _Add(OpSize::i64Bit, OldSP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, es_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, cs_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, ss_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, ds_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, fs_idx));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX:
|
|
_StoreContext(DstSize, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, gs_idx));
|
|
break;
|
|
default: break; // Do nothing
|
|
}
|
|
|
|
UpdatePrefixFromSegment(NewSegment, SegmentReg);
|
|
}
|
|
|
|
void OpDispatchBuilder::LEAVEOp(OpcodeArgs) {
|
|
// First we move RBP in to RSP and then behave effectively like a pop
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
auto Constant = _Constant(Size);
|
|
auto OldBP = LoadGPRRegister(X86State::REG_RBP);
|
|
|
|
auto NewGPR = _LoadMem(GPRClass, Size, OldBP, Size);
|
|
auto NewSP = _Add(IR::SizeToOpSize(Size), OldBP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
// Store what we loaded to RBP
|
|
StoreGPRRegister(X86State::REG_RBP, NewGPR);
|
|
}
|
|
|
|
void OpDispatchBuilder::CALLOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
BlockSetRIP = true;
|
|
|
|
// ABI Optimization: Flags don't survive calls or rets
|
|
if (CTX->Config.ABILocalFlags) {
|
|
_InvalidateFlags(~0UL); // all flags
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
}
|
|
else {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
}
|
|
|
|
auto ConstantPC = GetRelocatedPC(Op);
|
|
|
|
OrderedNode *JMPPCOffset = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
OrderedNode *NewRIP = _Add(IR::SizeToOpSize(GPRSize), ConstantPC, JMPPCOffset);
|
|
|
|
// Push the return address.
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewSP = _Push(GPRSize, GPRSize, ConstantPC, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
const uint64_t NextRIP = Op->PC + Op->InstSize;
|
|
LOGMAN_THROW_A_FMT(Op->Src[0].IsLiteral(), "Had wrong operand type");
|
|
const uint64_t TargetRIP = Op->PC + Op->InstSize + Op->Src[0].Data.Literal.Value;
|
|
|
|
CalculateDeferredFlags();
|
|
if (NextRIP != TargetRIP) {
|
|
// Store the RIP
|
|
_ExitFunction(NewRIP); // If we get here then leave the function now
|
|
}
|
|
else {
|
|
NeedsBlockEnd = true;
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CALLAbsoluteOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
BlockSetRIP = true;
|
|
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
OrderedNode *JMPPCOffset = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
auto ConstantPCReturn = GetRelocatedPC(Op);
|
|
|
|
// Push the return address.
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewSP = _Push(Size, Size, ConstantPCReturn, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
// Store the RIP
|
|
CalculateDeferredFlags();
|
|
_ExitFunction(JMPPCOffset); // If we get here then leave the function now
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::SelectMask(OrderedNode *Cmp, uint64_t Mask, bool TrueIsNonzero, IR::OpSize ResultSize, OrderedNode *TrueValue, OrderedNode *FalseValue) {
|
|
return _Select(ResultSize, OpSize::i32Bit,
|
|
TrueIsNonzero ? CondClassType{COND_ANDNZ} : CondClassType{COND_ANDZ},
|
|
Cmp, _Constant(Mask),
|
|
TrueValue, FalseValue);
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::SelectNZCV(unsigned BitOffset, bool TrueIsNonzero, IR::OpSize ResultSize, OrderedNode *TrueValue, OrderedNode *FalseValue) {
|
|
return SelectMask(GetNZCV(), 1u << IndexNZCV(BitOffset), TrueIsNonzero, ResultSize, TrueValue, FalseValue);
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::SelectCC(uint8_t OP, IR::OpSize ResultSize, OrderedNode *TrueValue, OrderedNode *FalseValue) {
|
|
OrderedNode *SrcCond = nullptr;
|
|
|
|
auto ZeroConst = _Constant(0);
|
|
auto OneConst = _Constant(1);
|
|
|
|
switch (OP) {
|
|
case 0x0: { // JO - Jump if OF == 1
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_OF_LOC, true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x1:{ // JNO - Jump if OF == 0
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_OF_LOC, false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x2: { // JC - Jump if CF == 1
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_CF_LOC, true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x3: { // JNC - Jump if CF == 0
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_CF_LOC, false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x4: { // JE - Jump if ZF == 1
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_ZF_LOC, true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x5: { // JNE - Jump if ZF == 0
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_ZF_LOC, false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x6: { // JNA - Jump if CF == 1 || ZC == 1
|
|
SrcCond = SelectMask(GetNZCV(), (1u << IndexNZCV(FEXCore::X86State::RFLAG_CF_LOC)) |
|
|
(1u << IndexNZCV(FEXCore::X86State::RFLAG_ZF_LOC)),
|
|
true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x7: { // JA - Jump if CF == 0 && ZF == 0
|
|
SrcCond = SelectMask(GetNZCV(), (1u << IndexNZCV(FEXCore::X86State::RFLAG_CF_LOC)) |
|
|
(1u << IndexNZCV(FEXCore::X86State::RFLAG_ZF_LOC)),
|
|
false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x8: { // JS - Jump if SF == 1
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_SF_LOC, true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0x9: { // JNS - Jump if SF == 0
|
|
SrcCond = SelectNZCV(FEXCore::X86State::RFLAG_SF_LOC, false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xA: { // JP - Jump if PF == 1
|
|
// Raw value contains inverted PF in bottom bit
|
|
SrcCond = SelectMask(LoadPFRaw(), 0x1, false, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xB: { // JNP - Jump if PF == 0
|
|
SrcCond = SelectMask(LoadPFRaw(), 0x1, true, ResultSize, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xC: { // SF <> OF
|
|
auto Flag1 = GetRFLAG(FEXCore::X86State::RFLAG_SF_LOC);
|
|
auto Flag2 = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
SrcCond = _Select(ResultSize, OpSize::i32Bit, CondClassType{COND_NEQ},
|
|
Flag1, Flag2, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xD: { // SF = OF
|
|
auto Flag1 = GetRFLAG(FEXCore::X86State::RFLAG_SF_LOC);
|
|
auto Flag2 = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
SrcCond = _Select(ResultSize, OpSize::i32Bit, CondClassType{COND_EQ},
|
|
Flag1, Flag2, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xE: {// ZF = 1 || SF <> OF
|
|
auto Select1 = SelectNZCV(FEXCore::X86State::RFLAG_ZF_LOC, true, OpSize::i32Bit,
|
|
OneConst, ZeroConst);
|
|
|
|
auto Flag2 = GetRFLAG(FEXCore::X86State::RFLAG_SF_LOC);
|
|
auto Flag3 = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
auto Select2 = _Select(OpSize::i32Bit, OpSize::i32Bit, CondClassType{COND_NEQ},
|
|
Flag2, Flag3, OneConst, ZeroConst);
|
|
|
|
auto Check = _Or(OpSize::i32Bit, Select1, Select2);
|
|
SrcCond = _Select(ResultSize, OpSize::i32Bit, CondClassType{COND_EQ},
|
|
Check, OneConst, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
case 0xF: {// ZF = 0 && SF = OF
|
|
auto Select1 = SelectNZCV(FEXCore::X86State::RFLAG_ZF_LOC, false, OpSize::i32Bit,
|
|
OneConst, ZeroConst);
|
|
|
|
auto Flag2 = GetRFLAG(FEXCore::X86State::RFLAG_SF_LOC);
|
|
auto Flag3 = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
auto Select2 = _Select(OpSize::i32Bit, OpSize::i32Bit, CondClassType{COND_EQ},
|
|
Flag2, Flag3, OneConst, ZeroConst);
|
|
|
|
auto Check = _And(OpSize::i32Bit, Select1, Select2);
|
|
SrcCond = _Select(ResultSize, OpSize::i32Bit, CondClassType{COND_EQ},
|
|
Check, OneConst, TrueValue, FalseValue);
|
|
break;
|
|
}
|
|
default:
|
|
LOGMAN_MSG_A_FMT("Unknown CC Op: 0x{:x}\n", OP);
|
|
return nullptr;
|
|
}
|
|
|
|
// Try folding the flags generation in the select op
|
|
if (flagsOp == SelectionFlag::CMP) {
|
|
switch(OP) {
|
|
// SGT
|
|
case 0xF: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_SGT}, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue); break;
|
|
// SLE
|
|
case 0xE: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_SLE}, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue); break;
|
|
// SGE
|
|
case 0xD: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_SGE}, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue); break;
|
|
// SL
|
|
case 0xC: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_SLT}, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue); break;
|
|
|
|
// not sign
|
|
//case 0x99: SrcCond = _Select(FEXCore::IR::COND_, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue, flagsOpSize); break;
|
|
// sign
|
|
//case 0x98: SrcCond = _Select(FEXCore::IR::COND_, flagsOpDestSigned, flagsOpSrcSigned, TrueValue, FalseValue, flagsOpSize); break;
|
|
|
|
// UABove
|
|
case 0x7: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_UGT}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
// UBE
|
|
case 0x6: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_ULE}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
// NE
|
|
case 0x5: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_NEQ}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
// EQ/Zero
|
|
case 0x4: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_EQ}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
// UAE
|
|
case 0x3: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_UGE}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
// UBelow
|
|
case 0x2: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_ULT}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue); break;
|
|
|
|
//default: printf("Missed Condition %04X OP_CMP\n", OP); break;
|
|
}
|
|
}
|
|
else if (flagsOp == SelectionFlag::AND) {
|
|
switch(OP) {
|
|
case 0x4: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_EQ}, flagsOpDest, ZeroConst, TrueValue, FalseValue); break;
|
|
case 0x5: SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_NEQ}, flagsOpDest, ZeroConst, TrueValue, FalseValue); break;
|
|
//default: printf("Missed Condition %04X OP_AND\n", OP); break;
|
|
}
|
|
} else if (flagsOp == SelectionFlag::FCMP) {
|
|
/*
|
|
x86:ZCP
|
|
unordered { 11 1 }
|
|
greater { 00 0 }
|
|
less { 01 0 }
|
|
equal { 10 0 }
|
|
aarch64: NZCV
|
|
unordered { 0 01 1 }
|
|
greater { 0 01 0 }
|
|
less { 1 00 0 }
|
|
equal { 0 11 0 }
|
|
*/
|
|
|
|
/*
|
|
eq = 0, // Z set Equal.
|
|
ne = 1, // Z clear Not equal.
|
|
cs = 2, // C set Carry set.
|
|
cc = 3, // C clear Carry clear.
|
|
mi = 4, // N set Negative.
|
|
pl = 5, // N clear Positive or zero.
|
|
vs = 6, // V set Overflow.
|
|
vc = 7, // V clear No overflow.
|
|
hi = 8, // C set, Z clear Unsigned higher.
|
|
ls = 9, // C clear or Z set Unsigned lower or same.
|
|
ge = 10, // N == V Greater or equal.
|
|
lt = 11, // N != V Less than.
|
|
gt = 12, // Z clear, N == V Greater than.
|
|
le = 13, // Z set or N != V Less then or equal
|
|
*/
|
|
switch(OP) {
|
|
case 0x2: // CF == 1 // less or unordered // N==1 OR V==1 // lt
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FLU}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
case 0x3: // CF == 0 // greater or equal (and not unordered) // N==V // ge
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FGE}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
case 0x6: // CF == 1 || ZF == 1 // less or equal or unordered // Z==1 OR N!=V // le
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FLEU}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
case 0x7: // CF == 0 && ZF == 0 // greater (and not unordered) // C==1 AND V=0 // hi
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FGT}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
case 0xA: // PF = 1 // unordered // V==1 // vs
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FU}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
case 0xB: // PF = 0 // not unordered // V==0 // vc
|
|
SrcCond = _Select(ResultSize, IR::SizeToOpSize(flagsOpSize), CondClassType{COND_FNU}, flagsOpDest, flagsOpSrc, TrueValue, FalseValue);
|
|
break;
|
|
default:
|
|
// TODO: Add more optimized cases
|
|
break;
|
|
}
|
|
}
|
|
|
|
return SrcCond;
|
|
}
|
|
|
|
void OpDispatchBuilder::SETccOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
auto ZeroConst = _Constant(0);
|
|
auto OneConst = _Constant(1);
|
|
|
|
auto SrcCond = SelectCC(Op->OP & 0xF, OpSize::i64Bit, OneConst, ZeroConst);
|
|
|
|
StoreResult(GPRClass, Op, SrcCond, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::CMOVOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
// Destination is always a GPR.
|
|
OrderedNode *Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, GPRSize, Op->Flags, -1);
|
|
OrderedNode *Src{};
|
|
if (Op->Src[0].IsGPR()) {
|
|
Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], GPRSize, Op->Flags, -1);
|
|
}
|
|
else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
}
|
|
|
|
auto SrcCond = SelectCC(Op->OP & 0xF, IR::SizeToOpSize(std::max<uint8_t>(4u, GetSrcSize(Op))), Src, Dest);
|
|
|
|
StoreResult(GPRClass, Op, SrcCond, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::CondJUMPOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
BlockSetRIP = true;
|
|
|
|
auto TakeBranch = _Constant(1);
|
|
auto DoNotTakeBranch = _Constant(0);
|
|
|
|
auto SrcCond = SelectCC(Op->OP & 0xF, OpSize::i64Bit, TakeBranch, DoNotTakeBranch);
|
|
|
|
// Jump instruction only uses up to 32-bit signed displacement
|
|
LOGMAN_THROW_A_FMT(Op->Src[0].IsLiteral(), "Src1 needs to be literal here");
|
|
int64_t TargetOffset = Op->Src[0].Data.Literal.Value;
|
|
uint64_t InstRIP = Op->PC + Op->InstSize;
|
|
uint64_t Target = InstRIP + TargetOffset;
|
|
|
|
if (CTX->GetGPRSize() == 4) {
|
|
// If the GPRSize is 4 then we need to be careful about PC wrapping
|
|
if (TargetOffset < 0 && -TargetOffset > InstRIP) {
|
|
// Invert the signed value if we are underflowing
|
|
TargetOffset = 0x1'0000'0000ULL + TargetOffset;
|
|
}
|
|
else if (TargetOffset >= 0 && Target >= 0x1'0000'0000ULL) {
|
|
// We are overflowing, wrap around
|
|
TargetOffset = TargetOffset - 0x1'0000'0000ULL;
|
|
}
|
|
Target &= 0xFFFFFFFFU;
|
|
}
|
|
|
|
CalculateDeferredFlags();
|
|
auto TrueBlock = JumpTargets.find(Target);
|
|
auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize);
|
|
|
|
auto CurrentBlock = GetCurrentBlock();
|
|
|
|
// Fallback
|
|
{
|
|
auto CondJump = _CondJump(SrcCond);
|
|
|
|
// Taking branch block
|
|
if (TrueBlock != JumpTargets.end()) {
|
|
SetTrueJumpTarget(CondJump, TrueBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
auto JumpTarget = CreateNewCodeBlockAtEnd();
|
|
SetTrueJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
auto NewRIP = GetRelocatedPC(Op, TargetOffset);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
}
|
|
|
|
// Failure to take branch
|
|
if (FalseBlock != JumpTargets.end()) {
|
|
SetFalseJumpTarget(CondJump, FalseBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
// Place it after this block for fallthrough optimization
|
|
auto JumpTarget = CreateNewCodeBlockAfter(CurrentBlock);
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
// Leave block
|
|
auto RIPTargetConst = GetRelocatedPC(Op);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(RIPTargetConst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CondJUMPRCXOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
BlockSetRIP = true;
|
|
uint8_t JcxGPRSize = CTX->GetGPRSize();
|
|
JcxGPRSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) ? (JcxGPRSize >> 1) : JcxGPRSize;
|
|
|
|
IRPair<IROp_Constant> TakeBranch;
|
|
IRPair<IROp_Constant> DoNotTakeBranch;
|
|
TakeBranch = _Constant(1);
|
|
DoNotTakeBranch = _Constant(0);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[0].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Target = Op->PC + Op->InstSize + Op->Src[0].Data.Literal.Value;
|
|
|
|
OrderedNode *CondReg = LoadGPRRegister(X86State::REG_RCX, JcxGPRSize);
|
|
|
|
auto TrueBlock = JumpTargets.find(Target);
|
|
auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize);
|
|
|
|
auto CurrentBlock = GetCurrentBlock();
|
|
|
|
{
|
|
auto CondJump = _CondJump(CondReg, {COND_EQ});
|
|
|
|
// Taking branch block
|
|
if (TrueBlock != JumpTargets.end()) {
|
|
SetTrueJumpTarget(CondJump, TrueBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
auto JumpTarget = CreateNewCodeBlockAtEnd();
|
|
SetTrueJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
auto NewRIP = GetRelocatedPC(Op, Op->Src[0].Data.Literal.Value);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
}
|
|
|
|
// Failure to take branch
|
|
if (FalseBlock != JumpTargets.end()) {
|
|
SetFalseJumpTarget(CondJump, FalseBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
// Place it after the current block for fallthrough behavior
|
|
auto JumpTarget = CreateNewCodeBlockAfter(CurrentBlock);
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
// Leave block
|
|
auto RIPTargetConst = GetRelocatedPC(Op);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(RIPTargetConst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::LoopOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
bool CheckZF = Op->OP != 0xE2;
|
|
bool ZFTrue = Op->OP == 0xE1;
|
|
|
|
BlockSetRIP = true;
|
|
auto ZeroConst = _Constant(0);
|
|
IRPair<IROp_Header> SrcCond;
|
|
|
|
IRPair<IROp_Constant> TakeBranch = _Constant(1);
|
|
IRPair<IROp_Constant> DoNotTakeBranch = _Constant(0);
|
|
|
|
uint32_t SrcSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) ? 4 : 8;
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Target = Op->PC + Op->InstSize + Op->Src[1].Data.Literal.Value;
|
|
|
|
OrderedNode *CondReg = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
CondReg = _Sub(SrcSize == 8 ? OpSize::i64Bit : OpSize::i32Bit, CondReg, _Constant(SrcSize * 8, 1));
|
|
StoreResult(GPRClass, Op, Op->Src[0], CondReg, -1);
|
|
|
|
SrcCond = _Select(FEXCore::IR::COND_NEQ,
|
|
CondReg, ZeroConst, TakeBranch, DoNotTakeBranch);
|
|
|
|
// If LOOPE then jumps to target if RCX != 0 && ZF == 1
|
|
// If LOOPNE then jumps to target if RCX != 0 && ZF == 0
|
|
if (CheckZF) {
|
|
OrderedNode *ZF = GetRFLAG(FEXCore::X86State::RFLAG_ZF_LOC);
|
|
if (!ZFTrue) {
|
|
ZF = _Xor(OpSize::i64Bit, ZF, _Constant(1));
|
|
}
|
|
SrcCond = _And(OpSize::i64Bit, SrcCond, ZF);
|
|
}
|
|
|
|
CalculateDeferredFlags();
|
|
auto TrueBlock = JumpTargets.find(Target);
|
|
auto FalseBlock = JumpTargets.find(Op->PC + Op->InstSize);
|
|
|
|
{
|
|
auto CondJump = _CondJump(SrcCond);
|
|
|
|
// Taking branch block
|
|
if (TrueBlock != JumpTargets.end()) {
|
|
SetTrueJumpTarget(CondJump, TrueBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
auto JumpTarget = CreateNewCodeBlockAtEnd();
|
|
SetTrueJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
auto NewRIP = GetRelocatedPC(Op, Op->Src[1].Data.Literal.Value);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
}
|
|
|
|
// Failure to take branch
|
|
if (FalseBlock != JumpTargets.end()) {
|
|
SetFalseJumpTarget(CondJump, FalseBlock->second.BlockEntry);
|
|
}
|
|
else {
|
|
// Make sure to start a new block after ending this one
|
|
// Place after this block for fallthrough behavior
|
|
auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
// Leave block
|
|
auto RIPTargetConst = GetRelocatedPC(Op);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(RIPTargetConst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::JUMPOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
BlockSetRIP = true;
|
|
|
|
// Jump instruction only uses up to 32-bit signed displacement
|
|
LOGMAN_THROW_A_FMT(Op->Src[0].IsLiteral(), "Src1 needs to be literal here");
|
|
int64_t TargetOffset = Op->Src[0].Data.Literal.Value;
|
|
uint64_t InstRIP = Op->PC + Op->InstSize;
|
|
uint64_t TargetRIP = InstRIP + TargetOffset;
|
|
|
|
if (CTX->GetGPRSize() == 4) {
|
|
// If the GPRSize is 4 then we need to be careful about PC wrapping
|
|
if (TargetOffset < 0 && -TargetOffset > InstRIP) {
|
|
// Invert the signed value if we are underflowing
|
|
TargetOffset = 0x1'0000'0000ULL + TargetOffset;
|
|
}
|
|
else if (TargetOffset >= 0 && TargetRIP >= 0x1'0000'0000ULL) {
|
|
// We are overflowing, wrap around
|
|
TargetOffset = TargetOffset - 0x1'0000'0000ULL;
|
|
}
|
|
|
|
TargetRIP &= 0xFFFFFFFFU;
|
|
}
|
|
|
|
CalculateDeferredFlags();
|
|
// This is just an unconditional relative literal jump
|
|
if (Multiblock) {
|
|
auto JumpBlock = JumpTargets.find(TargetRIP);
|
|
if (JumpBlock != JumpTargets.end()) {
|
|
_Jump(GetNewJumpBlock(TargetRIP));
|
|
}
|
|
else {
|
|
// If the block isn't a jump target then we need to create an exit block
|
|
auto Jump = _Jump();
|
|
|
|
// Place after this block for fallthrough behavior
|
|
auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetJumpTarget(Jump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
_ExitFunction(GetRelocatedPC(Op, TargetOffset));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Fallback
|
|
{
|
|
auto RIPTargetConst = GetRelocatedPC(Op);
|
|
auto NewRIP = _Add(OpSize::i64Bit, _Constant(TargetOffset), RIPTargetConst);
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(NewRIP);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::JUMPAbsoluteOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
BlockSetRIP = true;
|
|
// This is just an unconditional jump
|
|
// This uses ModRM to determine its location
|
|
// No way to use this effectively in multiblock
|
|
auto RIPOffset = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
CalculateDeferredFlags();
|
|
|
|
// Store the new RIP
|
|
_ExitFunction(RIPOffset);
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::TESTOp(OpcodeArgs) {
|
|
// TEST is an instruction that does an AND between the sources
|
|
// Result isn't stored in result, only writes to flags
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1, true, false, MemoryAccessType::ACCESS_DEFAULT, true);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, true, false, MemoryAccessType::ACCESS_DEFAULT, true);
|
|
|
|
auto Size = GetDstSize(Op);
|
|
|
|
auto ALUOp = _And(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
GenerateFlags_Logical(Op, ALUOp, Dest, Src);
|
|
|
|
flagsOp = SelectionFlag::AND;
|
|
flagsOpDest = ALUOp;
|
|
if (Size >= 4) {
|
|
flagsOpSize = Size;
|
|
} else {
|
|
flagsOpSize = 4; // assuming ZEXT semantics here
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVSXDOp(OpcodeArgs) {
|
|
// This instruction is a bit special
|
|
// if SrcSize == 2
|
|
// Then lower 16 bits of destination is written without changing the upper 48 bits
|
|
// else /* Size == 4 */
|
|
// if REX_WIDENING:
|
|
// Sext(32, Src)
|
|
// else
|
|
// Zext(32, Src)
|
|
//
|
|
uint8_t Size = std::min(static_cast<uint8_t>(4), GetSrcSize(Op));
|
|
|
|
OrderedNode *Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], Size, Op->Flags, -1);
|
|
if (Size == 2) {
|
|
// This'll make sure to insert in to the lower 16bits without modifying upper bits
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Src, Size, -1);
|
|
}
|
|
else if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING) {
|
|
// With REX.W then Sext
|
|
Src = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src);
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
}
|
|
else {
|
|
// Without REX.W then Zext (store result implicitly zero extends)
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVSXOp(OpcodeArgs) {
|
|
// This will ZExt the loaded size
|
|
// We want to Sext it
|
|
uint8_t Size = GetSrcSize(Op);
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
Src = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src);
|
|
StoreResult(GPRClass, Op, Op->Dest, Src, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVZXOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
// Store result implicitly zero extends
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::CMPOp(OpcodeArgs) {
|
|
// CMP is an instruction that does a SUB between the sources
|
|
// Result isn't stored in result, only writes to flags
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
auto Size = GetDstSize(Op);
|
|
|
|
auto ALUOp = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
OrderedNode *Result = ALUOp;
|
|
GenerateFlags_SUB(Op, Result, Dest, Src);
|
|
|
|
flagsOp = SelectionFlag::CMP;
|
|
if (Size >= 4) {
|
|
flagsOpSize = Size;
|
|
flagsOpDestSigned = flagsOpDest = Dest;
|
|
flagsOpSrcSigned = flagsOpSrc = Src;
|
|
} else {
|
|
flagsOpSize = 4;
|
|
flagsOpDestSigned = _Sbfe(OpSize::i64Bit, Size * 8, 0, flagsOpDest = Dest);
|
|
flagsOpSrcSigned = _Sbfe(OpSize::i64Bit, Size * 8, 0, flagsOpSrc = Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CQOOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto Size = GetSrcSize(Op);
|
|
OrderedNode *Upper = _Sbfe(OpSize::i64Bit, 1, Size * 8 - 1, Src);
|
|
|
|
StoreResult(GPRClass, Op, Upper, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::XCHGOp(OpcodeArgs) {
|
|
// Load both the source and the destination
|
|
if (Op->OP == 0x90 &&
|
|
GetSrcSize(Op) >= 4 &&
|
|
Op->Src[0].IsGPR() && Op->Src[0].Data.GPR.GPR == FEXCore::X86State::REG_RAX &&
|
|
Op->Dest.IsGPR() && Op->Dest.Data.GPR.GPR == FEXCore::X86State::REG_RAX) {
|
|
// This is one heck of a sucky special case
|
|
// If we are the 0x90 XCHG opcode (Meaning source is GPR RAX)
|
|
// and destination register is ALSO RAX
|
|
// and in this very specific case we are 32bit or above
|
|
// Then this is a no-op
|
|
// This is because 0x90 without a prefix is technically `xchg eax, eax`
|
|
// But this would result in a zext on 64bit, which would ruin the no-op nature of the instruction
|
|
// So x86-64 spec mandates this special case that even though it is a 32bit instruction and
|
|
// is supposed to zext the result, it is a true no-op
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX) {
|
|
// If this instruction has a REP prefix then this is architectually defined to be a `PAUSE` instruction.
|
|
// On older processors this ends up being a true `REP NOP` which is why they stuck this here.
|
|
_Yield();
|
|
}
|
|
return;
|
|
}
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
if (DestIsMem(Op)) {
|
|
HandledLock = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK;
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
|
|
auto Result = _AtomicSwap(OpSizeFromSrc(Op), Src, Dest);
|
|
StoreResult(GPRClass, Op, Op->Src[0], Result, -1);
|
|
}
|
|
else {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
// Swap the contents
|
|
// Order matters here since we don't want to swap context contents for one that effects the other
|
|
StoreResult(GPRClass, Op, Op->Dest, Src, -1);
|
|
StoreResult(GPRClass, Op, Op->Src[0], Dest, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CDQOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
uint8_t DstSize = GetDstSize(Op);
|
|
uint8_t SrcSize = DstSize >> 1;
|
|
|
|
Src = _Sbfe(OpSize::i64Bit, SrcSize * 8, 0, Src);
|
|
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Src, DstSize, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::SAHFOp(OpcodeArgs) {
|
|
// Extract AH
|
|
OrderedNode *Src = LoadGPRRegister(X86State::REG_RAX, 1, 8);
|
|
|
|
// Clear bits that aren't supposed to be set
|
|
Src = _Andn(OpSize::i64Bit, Src, _Constant(0b101000));
|
|
|
|
// Set the bit that is always set here
|
|
Src = _Or(OpSize::i64Bit, Src, _Constant(0b10));
|
|
|
|
// Store the lower 8 bits in to RFLAGS
|
|
SetPackedRFLAG(true, Src);
|
|
}
|
|
void OpDispatchBuilder::LAHFOp(OpcodeArgs) {
|
|
// Load the lower 8 bits of the Rflags register
|
|
auto RFLAG = GetPackedRFLAG(0xFF);
|
|
|
|
// Store the lower 8 bits of the rflags register in to AH
|
|
StoreGPRRegister(X86State::REG_RAX, RFLAG, 1, 8);
|
|
}
|
|
|
|
void OpDispatchBuilder::FLAGControlOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
enum class OpType {
|
|
Clear,
|
|
Set,
|
|
};
|
|
OpType Type;
|
|
uint64_t Flag;
|
|
switch (Op->OP) {
|
|
case 0xF5: {// CMC
|
|
uint32_t Bit = 1u << IndexNZCV(X86State::RFLAG_CF_LOC);
|
|
SetNZCV(_Xor(OpSize::i32Bit, GetNZCV(), _Constant(Bit)));
|
|
PossiblySetNZCVBits |= Bit;
|
|
return;
|
|
}
|
|
|
|
case 0xF8: // CLC
|
|
Flag= FEXCore::X86State::RFLAG_CF_LOC;
|
|
Type = OpType::Clear;
|
|
break;
|
|
case 0xF9: // STC
|
|
Flag= FEXCore::X86State::RFLAG_CF_LOC;
|
|
Type = OpType::Set;
|
|
break;
|
|
case 0xFC: // CLD
|
|
Flag= FEXCore::X86State::RFLAG_DF_LOC;
|
|
Type = OpType::Clear;
|
|
break;
|
|
case 0xFD: // STD
|
|
Flag= FEXCore::X86State::RFLAG_DF_LOC;
|
|
Type = OpType::Set;
|
|
break;
|
|
}
|
|
|
|
// AF would need special handling here. It doesn't matter.
|
|
LOGMAN_THROW_AA_FMT(Flag != FEXCore::X86State::RFLAG_AF_LOC,
|
|
"No AF complement instruction in x86");
|
|
|
|
OrderedNode *Result{};
|
|
switch (Type) {
|
|
case OpType::Clear: {
|
|
Result = _Constant(0);
|
|
break;
|
|
}
|
|
case OpType::Set: {
|
|
Result = _Constant(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SetRFLAG(Result, Flag);
|
|
}
|
|
|
|
|
|
template<bool ToSeg>
|
|
void OpDispatchBuilder::MOVSegOp(OpcodeArgs) {
|
|
// In x86-64 mode the accesses to the segment registers end up being constant zero moves
|
|
// Aside from FS/GS
|
|
// In x86-64 mode the accesses to segment registers can actually still touch the segments
|
|
// These write to the selector portion of the register
|
|
//
|
|
// FS and GS are specially handled here though
|
|
// AMD documentation is /wrong/ in this regard
|
|
// AMD documentation claims that the MOV to SReg and POP SReg registers will load a 32bit
|
|
// value in to the HIDDEN portions of the FS and GS registers /OR/ ignored if a null selector is
|
|
// selected for the registers
|
|
// This statement is actually untrue, the instructions will /actually/ load 16bits in to the selector portion of the register!
|
|
// Tested on a Zen+ CPU, the selector is the portion that is modified!
|
|
// We don't currently support FS/GS selector modifying, so this needs to be asserted out
|
|
// The loads here also load the selector, NOT the base
|
|
|
|
if constexpr (ToSeg) {
|
|
OrderedNode *Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], 2, Op->Flags, -1);
|
|
|
|
switch (Op->Dest.Data.GPR.GPR) {
|
|
case FEXCore::X86State::REG_RAX: // ES
|
|
case FEXCore::X86State::REG_R8: // ES
|
|
_StoreContext(2, GPRClass, Src, offsetof(FEXCore::Core::CPUState, es_idx));
|
|
UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX);
|
|
break;
|
|
case FEXCore::X86State::REG_RBX: // DS
|
|
case FEXCore::X86State::REG_R11: // DS
|
|
_StoreContext(2, GPRClass, Src, offsetof(FEXCore::Core::CPUState, ds_idx));
|
|
UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
break;
|
|
case FEXCore::X86State::REG_RCX: // CS
|
|
case FEXCore::X86State::REG_R9: // CS
|
|
// CPL3 can't write to this
|
|
_Break(FEXCore::IR::BreakDefinition {
|
|
.ErrorRegister = 0,
|
|
.Signal = SIGILL,
|
|
.TrapNumber = 0,
|
|
.si_code = 0,
|
|
});
|
|
break;
|
|
case FEXCore::X86State::REG_RDX: // SS
|
|
case FEXCore::X86State::REG_R10: // SS
|
|
_StoreContext(2, GPRClass, Src, offsetof(FEXCore::Core::CPUState, ss_idx));
|
|
UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX);
|
|
break;
|
|
case FEXCore::X86State::REG_RBP: // GS
|
|
case FEXCore::X86State::REG_R13: // GS
|
|
if (!CTX->Config.Is64BitMode) {
|
|
_StoreContext(2, GPRClass, Src, offsetof(FEXCore::Core::CPUState, gs_idx));
|
|
UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX);
|
|
} else {
|
|
LogMan::Msg::EFmt("We don't support modifying GS selector in 64bit mode!");
|
|
DecodeFailure = true;
|
|
}
|
|
break;
|
|
case FEXCore::X86State::REG_RSP: // FS
|
|
case FEXCore::X86State::REG_R12: // FS
|
|
if (!CTX->Config.Is64BitMode) {
|
|
_StoreContext(2, GPRClass, Src, offsetof(FEXCore::Core::CPUState, fs_idx));
|
|
UpdatePrefixFromSegment(Src, FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX);
|
|
} else {
|
|
LogMan::Msg::EFmt("We don't support modifying FS selector in 64bit mode!");
|
|
DecodeFailure = true;
|
|
}
|
|
break;
|
|
default:
|
|
LogMan::Msg::EFmt("Unknown segment register: {}", Op->Dest.Data.GPR.GPR);
|
|
DecodeFailure = true;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
OrderedNode *Segment{};
|
|
|
|
switch (Op->Src[0].Data.GPR.GPR) {
|
|
case FEXCore::X86State::REG_RAX: // ES
|
|
case FEXCore::X86State::REG_R8: // ES
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, es_idx));
|
|
break;
|
|
case FEXCore::X86State::REG_RBX: // DS
|
|
case FEXCore::X86State::REG_R11: // DS
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, ds_idx));
|
|
break;
|
|
case FEXCore::X86State::REG_RCX: // CS
|
|
case FEXCore::X86State::REG_R9: // CS
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, cs_idx));
|
|
break;
|
|
case FEXCore::X86State::REG_RDX: // SS
|
|
case FEXCore::X86State::REG_R10: // SS
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, ss_idx));
|
|
break;
|
|
case FEXCore::X86State::REG_RBP: // GS
|
|
case FEXCore::X86State::REG_R13: // GS
|
|
if (CTX->Config.Is64BitMode) {
|
|
Segment = _Constant(0);
|
|
}
|
|
else {
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, gs_idx));
|
|
}
|
|
break;
|
|
case FEXCore::X86State::REG_RSP: // FS
|
|
case FEXCore::X86State::REG_R12: // FS
|
|
if (CTX->Config.Is64BitMode) {
|
|
Segment = _Constant(0);
|
|
}
|
|
else {
|
|
Segment = _LoadContext(2, GPRClass, offsetof(FEXCore::Core::CPUState, fs_idx));
|
|
}
|
|
break;
|
|
default:
|
|
LogMan::Msg::EFmt("Unknown segment register: {}", Op->Dest.Data.GPR.GPR);
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
if (DestIsMem(Op)) {
|
|
// If the destination is memory then we always store 16-bits only
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Segment, 2, -1);
|
|
}
|
|
else {
|
|
// If the destination is a GPR then we follow register storing rules
|
|
StoreResult(GPRClass, Op, Segment, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVOffsetOp(OpcodeArgs) {
|
|
OrderedNode *Src;
|
|
|
|
switch (Op->OP) {
|
|
case 0xA0:
|
|
case 0xA1:
|
|
// Source is memory(literal)
|
|
// Dest is GPR
|
|
Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1, true, true);
|
|
StoreResult(GPRClass, Op, Op->Dest, Src, -1);
|
|
break;
|
|
case 0xA2:
|
|
case 0xA3:
|
|
// Source is GPR
|
|
// Dest is memory(literal)
|
|
Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
// This one is a bit special since the destination is a literal
|
|
// So the destination gets stored in Src[1]
|
|
StoreResult(GPRClass, Op, Op->Src[1], Src, -1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CPUIDOp(OpcodeArgs) {
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
|
|
OrderedNode *Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], GPRSize, Op->Flags, -1);
|
|
OrderedNode *Leaf = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
auto Res = _CPUID(Src, Leaf);
|
|
|
|
OrderedNode *Result_Lower = _ExtractElementPair(OpSize::i64Bit, Res, 0);
|
|
OrderedNode *Result_Upper = _ExtractElementPair(OpSize::i64Bit, Res, 1);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, _Bfe(OpSize::i64Bit, 32, 0, Result_Lower));
|
|
StoreGPRRegister(X86State::REG_RBX, _Bfe(OpSize::i64Bit, 32, 32, Result_Lower));
|
|
StoreGPRRegister(X86State::REG_RDX, _Bfe(OpSize::i64Bit, 32, 32, Result_Upper));
|
|
StoreGPRRegister(X86State::REG_RCX, _Bfe(OpSize::i64Bit, 32, 0, Result_Upper));
|
|
}
|
|
|
|
void OpDispatchBuilder::XGetBVOp(OpcodeArgs) {
|
|
OrderedNode *Function = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
auto Res = _XGetBV(Function);
|
|
|
|
OrderedNode *Result_Lower = _ExtractElementPair(OpSize::i32Bit, Res, 0);
|
|
OrderedNode *Result_Upper = _ExtractElementPair(OpSize::i32Bit, Res, 1);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, Result_Lower);
|
|
StoreGPRRegister(X86State::REG_RDX, Result_Upper);
|
|
}
|
|
|
|
template<bool SHL1Bit>
|
|
void OpDispatchBuilder::SHLOp(OpcodeArgs) {
|
|
OrderedNode *Src{};
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
if constexpr (SHL1Bit) {
|
|
Src = _Constant(1);
|
|
}
|
|
else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
}
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
OrderedNode *Result = _Lshl(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
if (Size < 32) {
|
|
Result = _Bfe(OpSize::i32Bit, Size, 0, Result);
|
|
}
|
|
|
|
if constexpr (SHL1Bit) {
|
|
GenerateFlags_ShiftLeftImmediate(Op, Result, Dest, 1);
|
|
}
|
|
else {
|
|
GenerateFlags_ShiftLeft(Op, Result, Dest, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::SHLImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
OrderedNode *Src = _Constant(Size, Shift);
|
|
OrderedNode *Result = _Lshl(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
if (Size < 32) {
|
|
Result = _Bfe(OpSize::i32Bit, Size, 0, Result);
|
|
}
|
|
|
|
GenerateFlags_ShiftLeftImmediate(Op, Result, Dest, Shift);
|
|
}
|
|
|
|
template<bool SHR1Bit>
|
|
void OpDispatchBuilder::SHROp(OpcodeArgs) {
|
|
OrderedNode *Src;
|
|
auto Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
if constexpr (SHR1Bit) {
|
|
Src = _Constant(1);
|
|
}
|
|
else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
}
|
|
|
|
auto ALUOp = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4, GetSrcSize(Op))), Dest, Src);
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
|
|
if constexpr (SHR1Bit) {
|
|
GenerateFlags_ShiftRightImmediate(Op, ALUOp, Dest, 1);
|
|
}
|
|
else {
|
|
GenerateFlags_ShiftRight(Op, ALUOp, Dest, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::SHRImmediateOp(OpcodeArgs) {
|
|
auto Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
OrderedNode *Src = _Constant(Size, Shift);
|
|
auto ALUOp = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
GenerateFlags_ShiftRightImmediate(Op, ALUOp, Dest, Shift);
|
|
}
|
|
|
|
void OpDispatchBuilder::SHLDOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
OrderedNode *Shift = LoadSource_WithOpSize(GPRClass, Op, Op->Src[1], 1, Op->Flags, -1);
|
|
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift = _And(OpSize::i64Bit, Shift, _Constant(0x3F));
|
|
} else {
|
|
Shift = _And(OpSize::i64Bit, Shift, _Constant(0x1F));
|
|
}
|
|
|
|
auto ShiftRight = _Sub(OpSize::i64Bit, _Constant(Size), Shift);
|
|
|
|
auto Tmp1 = _Lshl(OpSize::i64Bit, Dest, Shift);
|
|
auto Tmp2 = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Src, ShiftRight);
|
|
|
|
OrderedNode *Res = _Or(OpSize::i64Bit, Tmp1, Tmp2);
|
|
|
|
// If shift count was zero then output doesn't change
|
|
// Needs to be checked for the 32bit operand case
|
|
// where shift = 0 and the source register still gets Zext
|
|
Res = _Select(FEXCore::IR::COND_EQ,
|
|
Shift, _Constant(0),
|
|
Dest, Res);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
auto CondJump = _CondJump(Shift, {COND_EQ});
|
|
|
|
auto CurrentBlock = GetCurrentBlock();
|
|
|
|
// Do nothing if shift count is zero
|
|
auto JumpTarget = CreateNewCodeBlockAfter(CurrentBlock);
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
if (Size != 64) {
|
|
Res = _Bfe(OpSize::i64Bit, Size, 0, Res);
|
|
}
|
|
GenerateFlags_ShiftLeft(Op, Res, Dest, Shift);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
auto Jump = _Jump();
|
|
auto NextJumpTarget = CreateNewCodeBlockAfter(JumpTarget);
|
|
SetJumpTarget(Jump, NextJumpTarget);
|
|
SetTrueJumpTarget(CondJump, NextJumpTarget);
|
|
SetCurrentCodeBlock(NextJumpTarget);
|
|
StartNewBlock();
|
|
}
|
|
|
|
void OpDispatchBuilder::SHLDImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
if (Shift != 0) {
|
|
OrderedNode *Res{};
|
|
if (Size < 32) {
|
|
OrderedNode *ShiftLeft = _Constant(Shift);
|
|
auto ShiftRight = _Constant(Size - Shift);
|
|
|
|
auto Tmp1 = _Lshl(OpSize::i64Bit, Dest, ShiftLeft);
|
|
auto Tmp2 = _Lshr(OpSize::i32Bit, Src, ShiftRight);
|
|
|
|
Res = _Or(OpSize::i64Bit, Tmp1, Tmp2);
|
|
}
|
|
else {
|
|
// 32-bit and 64-bit SHLD behaves like an EXTR where the lower bits are filled from the source.
|
|
Res = _Extr(OpSizeFromSrc(Op), Dest, Src, Size - Shift);
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
GenerateFlags_ShiftLeftImmediate(Op, Res, Dest, Shift);
|
|
}
|
|
else if (Shift == 0 && Size == 32) {
|
|
// Ensure Zext still occurs
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::SHRDOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
// This instruction conditionally generates flags so we need to insure sane state going in.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
OrderedNode *Shift = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
const auto Size = GetDstBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift = _And(OpSize::i64Bit, Shift, _Constant(0x3F));
|
|
} else {
|
|
Shift = _And(OpSize::i64Bit, Shift, _Constant(0x1F));
|
|
}
|
|
|
|
auto ShiftLeft = _Sub(OpSize::i64Bit, _Constant(Size), Shift);
|
|
|
|
auto Tmp1 = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Shift);
|
|
auto Tmp2 = _Lshl(OpSize::i64Bit, Src, ShiftLeft);
|
|
|
|
OrderedNode *Res = _Or(OpSize::i64Bit, Tmp1, Tmp2);
|
|
|
|
// If shift count was zero then output doesn't change
|
|
// Needs to be checked for the 32bit operand case
|
|
// where shift = 0 and the source register still gets Zext
|
|
Res = _Select(FEXCore::IR::COND_EQ,
|
|
Shift, _Constant(0),
|
|
Dest, Res);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
auto CondJump = _CondJump(Shift, {COND_EQ});
|
|
|
|
// Do not change flags if shift count is zero
|
|
auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
if (Size != 64) {
|
|
Res = _Bfe(OpSize::i64Bit, Size, 0, Res);
|
|
}
|
|
GenerateFlags_ShiftRight(Op, Res, Dest, Shift);
|
|
// Calculate deferred flags immediately.
|
|
// This block is ending so it needs to serialize
|
|
CalculateDeferredFlags();
|
|
|
|
auto Jump = _Jump();
|
|
auto NextJumpTarget = CreateNewCodeBlockAfter(JumpTarget);
|
|
SetJumpTarget(Jump, NextJumpTarget);
|
|
SetTrueJumpTarget(CondJump, NextJumpTarget);
|
|
SetCurrentCodeBlock(NextJumpTarget);
|
|
StartNewBlock();
|
|
}
|
|
|
|
void OpDispatchBuilder::SHRDImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
if (Shift != 0) {
|
|
|
|
OrderedNode *Res{};
|
|
if (Size < 32) {
|
|
OrderedNode *ShiftRight = _Constant(Shift);
|
|
auto ShiftLeft = _Constant(Size - Shift);
|
|
|
|
auto Tmp1 = _Lshr(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, ShiftRight);
|
|
auto Tmp2 = _Lshl(OpSize::i64Bit, Src, ShiftLeft);
|
|
|
|
Res = _Or(OpSize::i64Bit, Tmp1, Tmp2);
|
|
}
|
|
else {
|
|
// 32-bit and 64-bit SHRD behaves like an EXTR where the upper bits are filled from the source.
|
|
Res = _Extr(OpSizeFromSrc(Op), Src, Dest, Shift);
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
GenerateFlags_ShiftRightDoubleImmediate(Op, Res, Dest, Shift);
|
|
}
|
|
else if (Shift == 0 && Size == 32) {
|
|
// Ensure Zext still occurs
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
}
|
|
}
|
|
|
|
template<bool SHR1Bit>
|
|
void OpDispatchBuilder::ASHROp(OpcodeArgs) {
|
|
OrderedNode *Src;
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
if constexpr (SHR1Bit) {
|
|
Src = _Constant(Size, 1);
|
|
} else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
}
|
|
|
|
if (Size < 32) {
|
|
Dest = _Sbfe(OpSize::i64Bit, Size, 0, Dest);
|
|
}
|
|
|
|
OrderedNode *Result = _Ashr(IR::SizeToOpSize(std::max<uint8_t>(4, GetSrcSize(Op))), Dest, Src);
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
if constexpr (SHR1Bit) {
|
|
GenerateFlags_SignShiftRightImmediate(Op, Result, Dest, 1);
|
|
} else {
|
|
GenerateFlags_SignShiftRight(Op, Result, Dest, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::ASHRImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
if (Size < 32) {
|
|
Dest = _Sbfe(OpSize::i64Bit, Size, 0, Dest);
|
|
}
|
|
|
|
OrderedNode *Src = _Constant(Size, Shift);
|
|
OrderedNode *Result = _Ashr(IR::SizeToOpSize(std::max<uint8_t>(4, GetOpSize(Dest))), Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
GenerateFlags_SignShiftRightImmediate(Op, Result, Dest, Shift);
|
|
}
|
|
|
|
template<bool Is1Bit>
|
|
void OpDispatchBuilder::ROROp(OpcodeArgs) {
|
|
OrderedNode *Src;
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
const uint32_t Size = GetSrcBitSize(Op);
|
|
if constexpr (Is1Bit) {
|
|
Src = _Constant(std::max(32U, Size), 1);
|
|
} else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
}
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Src = _And(OpSize::i64Bit, Src, _Constant(Size, 0x3F));
|
|
} else {
|
|
Src = _And(OpSize::i32Bit, Src, _Constant(Size, 0x1F));
|
|
}
|
|
|
|
if (Size < 32) {
|
|
// ARM doesn't support 8/16bit rotates. Emulate with an insert
|
|
// StoreResult truncates back to a 8/16 bit value
|
|
Dest = _Bfi(OpSize::i32Bit, Size, Size, Dest, Dest);
|
|
if (Size == 8 && !Is1Bit) {
|
|
// And because the shift size isn't masked to 8 bits, we need to fill the
|
|
// the full 32bits to get the correct result.
|
|
Dest = _Bfi(OpSize::i32Bit, 16, 16, Dest, Dest);
|
|
}
|
|
}
|
|
|
|
auto ALUOp = _Ror(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
|
|
if constexpr (Is1Bit) {
|
|
GenerateFlags_RotateRightImmediate(Op, ALUOp, Dest, 1);
|
|
} else {
|
|
GenerateFlags_RotateRight(Op, ALUOp, Dest, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RORImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const uint32_t Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
OrderedNode *Src = _Constant(std::max(32U, Size), Shift);
|
|
|
|
if (Size < 32) {
|
|
// ARM doesn't support 8/16bit rotates. Emulate with an insert
|
|
// StoreResult truncates back to a 8/16 bit value
|
|
Dest = _Bfi(OpSize::i32Bit, Size, Size, Dest, Dest);
|
|
if (Size == 8 && Shift > 8) {
|
|
// And because the shift size isn't masked to 8 bits, we need to fill the
|
|
// the full 32bits to get the correct result.
|
|
Dest = _Bfi(OpSize::i32Bit, 16, 16, Dest, Dest);
|
|
}
|
|
}
|
|
|
|
auto ALUOp = _Ror(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
|
|
GenerateFlags_RotateRightImmediate(Op, ALUOp, Dest, Shift);
|
|
}
|
|
|
|
template<bool Is1Bit>
|
|
void OpDispatchBuilder::ROLOp(OpcodeArgs) {
|
|
OrderedNode *Src;
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
const uint32_t Size = GetSrcBitSize(Op);
|
|
|
|
// Need to negate the shift so we can use ROR instead
|
|
if constexpr (Is1Bit) {
|
|
Src = _Constant(Size, 1);
|
|
} else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
}
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Src = _And(OpSize::i64Bit, Src, _Constant(Size, 0x3F));
|
|
} else {
|
|
Src = _And(OpSize::i32Bit, Src, _Constant(Size, 0x1F));
|
|
}
|
|
|
|
if (Size < 32) {
|
|
// ARM doesn't support 8/16bit rotates. Emulate with an insert
|
|
// StoreResult truncates back to a 8/16 bit value
|
|
Dest = _Bfi(OpSize::i32Bit, Size, Size, Dest, Dest);
|
|
if (Size == 8) {
|
|
// And because the shift size isn't masked to 8 bits, we need to fill the
|
|
// the full 32bits to get the correct result.
|
|
Dest = _Bfi(OpSize::i32Bit, 16, 16, Dest, Dest);
|
|
}
|
|
}
|
|
|
|
auto ALUOp = _Ror(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit,
|
|
Dest,
|
|
_Sub(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, _Constant(Size, std::max(32U, Size)), Src));
|
|
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
|
|
if constexpr (Is1Bit) {
|
|
GenerateFlags_RotateLeftImmediate(Op, ALUOp, Dest, 1);
|
|
} else {
|
|
GenerateFlags_RotateLeft(Op, ALUOp, Dest, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::ROLImmediateOp(OpcodeArgs) {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src1 needs to be literal here");
|
|
|
|
uint64_t Shift = Op->Src[1].Data.Literal.Value;
|
|
const uint32_t Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
if (Size == 64) {
|
|
Shift &= 0x3F;
|
|
} else {
|
|
Shift &= 0x1F;
|
|
}
|
|
|
|
// We also negate the shift so we can emulate Rol with Ror.
|
|
const auto NegatedShift = std::max(32U, Size) - Shift;
|
|
OrderedNode *Src = _Constant(Size, NegatedShift);
|
|
|
|
if (Size < 32) {
|
|
// ARM doesn't support 8/16bit rotates. Emulate with an insert
|
|
// StoreResult truncates back to a 8/16 bit value
|
|
Dest = _Bfi(OpSize::i32Bit, Size, Size, Dest, Dest);
|
|
if (Size == 8) {
|
|
// And because the shift size isn't masked to 8 bits, we need to fill the
|
|
// the full 32bits to get the correct result.
|
|
Dest = _Bfi(OpSize::i32Bit, 16, 16, Dest, Dest);
|
|
}
|
|
}
|
|
|
|
auto ALUOp = _Ror(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, Src);
|
|
|
|
StoreResult(GPRClass, Op, ALUOp, -1);
|
|
|
|
GenerateFlags_RotateLeftImmediate(Op, ALUOp, Dest, Shift);
|
|
}
|
|
|
|
void OpDispatchBuilder::ANDNBMIOp(OpcodeArgs) {
|
|
auto* Src1 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Src2 = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
|
|
auto Dest = _Andn(OpSizeFromSrc(Op), Src2, Src1);
|
|
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
GenerateFlags_Logical(Op, Dest, Src1, Src2);
|
|
}
|
|
|
|
void OpDispatchBuilder::BEXTRBMIOp(OpcodeArgs) {
|
|
// Essentially (Src1 >> Start) & ((1 << Length) - 1)
|
|
// along with some edge-case handling and flag setting.
|
|
|
|
auto* Src1 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Src2 = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
|
|
const auto Size = GetSrcSize(Op);
|
|
const auto SrcSize = Size * 8;
|
|
const auto MaxSrcBit = SrcSize - 1;
|
|
auto MaxSrcBitOp = _Constant(SrcSize, MaxSrcBit);
|
|
|
|
// Shift the operand down to the starting bit
|
|
auto Start = _Bfe(OpSizeFromSrc(Op), 8, 0, Src2);
|
|
auto Shifted = _Lshr(IR::SizeToOpSize(Size), Src1, Start);
|
|
|
|
// Shifts larger than operand size need to be set to zero.
|
|
auto SanitizedShifted = _Select(IR::COND_ULE,
|
|
Start, MaxSrcBitOp,
|
|
Shifted, _Constant(SrcSize, 0));
|
|
|
|
// Now handle the length specifier.
|
|
auto Length = _Bfe(OpSizeFromSrc(Op), 8, 8, Src2);
|
|
auto SanitizedLength = _Select(IR::COND_ULE,
|
|
Length, MaxSrcBitOp,
|
|
Length, MaxSrcBitOp);
|
|
|
|
// Now build up the mask
|
|
// (1 << SanitizedLength) - 1
|
|
auto One = _Constant(SrcSize, 1);
|
|
auto Mask = _Sub(IR::SizeToOpSize(Size), _Lshl(IR::SizeToOpSize(Size), One, SanitizedLength), One);
|
|
|
|
// Now put it all together and make the result.
|
|
auto Dest = _And(IR::SizeToOpSize(Size), SanitizedShifted, Mask);
|
|
|
|
// Finally store the result.
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
|
|
GenerateFlags_BEXTR(Op, Dest);
|
|
}
|
|
|
|
void OpDispatchBuilder::BLSIBMIOp(OpcodeArgs) {
|
|
// Equivalent to performing: SRC & -SRC
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto NegatedSrc = _Neg(OpSizeFromSrc(Op), Src);
|
|
auto Result = _And(OpSizeFromSrc(Op), Src, NegatedSrc);
|
|
|
|
// ...and we're done. Painless!
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
GenerateFlags_BLSI(Op, Result);
|
|
}
|
|
|
|
void OpDispatchBuilder::BLSMSKBMIOp(OpcodeArgs) {
|
|
// Equivalent to: (Src - 1) ^ Src
|
|
auto One = _Constant(1);
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto Result = _Xor(OpSize::i64Bit, _Sub(OpSize::i64Bit, Src, One), Src);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
GenerateFlags_BLSMSK(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::BLSRBMIOp(OpcodeArgs) {
|
|
// Equivalent to: (Src - 1) & Src
|
|
auto One = _Constant(1);
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto Result = _And(OpSize::i64Bit, _Sub(OpSize::i64Bit, Src, One), Src);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
GenerateFlags_BLSR(Op, Result, Src);
|
|
}
|
|
|
|
// Handles SARX, SHLX, and SHRX
|
|
void OpDispatchBuilder::BMI2Shift(OpcodeArgs) {
|
|
// In the event the source is a memory operand, use the
|
|
// exact width instead of the GPR size.
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
const auto Size = GetSrcSize(Op);
|
|
const auto SrcSize = Op->Src[0].IsGPR() ? GPRSize : Size;
|
|
|
|
auto* Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], SrcSize, Op->Flags, -1);
|
|
auto* Shift = LoadSource_WithOpSize(GPRClass, Op, Op->Src[1], GPRSize, Op->Flags, -1);
|
|
|
|
auto* Result = [&]() -> OrderedNode* {
|
|
// SARX
|
|
if (Op->OP == 0x6F7) {
|
|
return _Ashr(IR::SizeToOpSize(Size), Src, Shift);
|
|
}
|
|
// SHLX
|
|
if (Op->OP == 0x5F7) {
|
|
return _Lshl(IR::SizeToOpSize(Size), Src, Shift);
|
|
}
|
|
|
|
// SHRX
|
|
return _Lshr(IR::SizeToOpSize(Size), Src, Shift);
|
|
}();
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::BZHI(OpcodeArgs) {
|
|
const auto Size = GetSrcSize(Op);
|
|
const auto OperandSize = Size * 8;
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Index = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
|
|
// Mask off the index so we only consider the lower byte.
|
|
auto MaskedIndex = _And(OpSize::i64Bit, Index, _Constant(0xFF));
|
|
|
|
// Now clear the high bits specified by the index.
|
|
auto NegOne = _Constant(OperandSize, -1);
|
|
auto Mask = _Lshl(IR::SizeToOpSize(Size), NegOne, MaskedIndex);
|
|
auto MaskResult = _Andn(IR::SizeToOpSize(Size), Src, Mask);
|
|
|
|
// If the index is above OperandSize, we don't clear anything.
|
|
auto Bounds = _Constant(OperandSize - 1);
|
|
auto Result = _Select(IR::COND_UGT,
|
|
MaskedIndex, Bounds,
|
|
Src, MaskResult);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
GenerateFlags_BZHI(Op, Result, MaskedIndex);
|
|
}
|
|
|
|
void OpDispatchBuilder::RORX(OpcodeArgs) {
|
|
LOGMAN_THROW_A_FMT(Op->Src[1].IsLiteral(), "Src[1] needs to be literal here");
|
|
const auto Amount = Op->Src[1].Data.Literal.Value;
|
|
const auto SrcSize = GetSrcSize(Op);
|
|
const auto SrcSizeBits = SrcSize * 8;
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
|
|
const auto DoRotation = Amount != 0 && Amount < SrcSizeBits;
|
|
const auto IsSameGPR = Op->Src[0].IsGPR() && Op->Dest.IsGPR() &&
|
|
Op->Src[0].Data.GPR.GPR == Op->Dest.Data.GPR.GPR;
|
|
const auto SrcSizeIsGPRSize = SrcSize == GPRSize;
|
|
|
|
// If we don't need to rotate and our source is the same as the destination
|
|
// then we don't need to do anything at all. We still need to be careful,
|
|
// since 32-bit operations on 64-bit mode still need to zero-extend the
|
|
// destination register. So also compare source size and GPR size.
|
|
//
|
|
// Very unlikely, but hey, we can do nothing faster.
|
|
if (!DoRotation && IsSameGPR && SrcSizeIsGPRSize) [[unlikely]] {
|
|
return;
|
|
}
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Result = Src;
|
|
if (DoRotation) [[likely]] {
|
|
Result = _Ror(OpSizeFromSrc(Op), Src, _Constant(Amount));
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::MULX(OpcodeArgs) {
|
|
// RDX is the implied source operand in the instruction
|
|
const auto OperandSize = GetSrcSize(Op);
|
|
const auto OpSize = IR::SizeToOpSize(OperandSize);
|
|
|
|
// Src1 can be a memory operand, so ensure we constrain to the
|
|
// absolute width of the access in that scenario.
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
const auto Src1Size = Op->Src[1].IsGPR() ? GPRSize : OperandSize;
|
|
|
|
OrderedNode* Src1 = LoadSource_WithOpSize(GPRClass, Op, Op->Src[1], Src1Size, Op->Flags, -1);
|
|
OrderedNode* Src2 = LoadGPRRegister(X86State::REG_RDX, GPRSize);
|
|
|
|
// As per the Intel Software Development Manual, if the destination and
|
|
// first operand correspond to the same register, then the result
|
|
// will be the high half of the multiplication result.
|
|
if (Op->Dest.Data.GPR.GPR == Op->Src[0].Data.GPR.GPR) {
|
|
OrderedNode* ResultHi = _UMulH(OpSize, Src1, Src2);
|
|
StoreResult(GPRClass, Op, Op->Dest, ResultHi, -1);
|
|
} else {
|
|
OrderedNode* ResultLo = _UMul(OpSize, Src1, Src2);
|
|
OrderedNode* ResultHi = _UMulH(OpSize, Src1, Src2);
|
|
|
|
StoreResult(GPRClass, Op, Op->Src[0], ResultLo, -1);
|
|
StoreResult(GPRClass, Op, Op->Dest, ResultHi, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::PDEP(OpcodeArgs) {
|
|
auto* Input = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Mask = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
auto Result = _PDep(OpSizeFromSrc(Op), Input, Mask);
|
|
|
|
StoreResult(GPRClass, Op, Op->Dest, Result, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::PEXT(OpcodeArgs) {
|
|
auto* Input = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Mask = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
auto Result = _PExt(OpSizeFromSrc(Op), Input, Mask);
|
|
|
|
StoreResult(GPRClass, Op, Op->Dest, Result, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::ADXOp(OpcodeArgs) {
|
|
const auto OpSize = OpSizeFromSrc(Op);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
// Handles ADCX and ADOX
|
|
|
|
const bool IsADCX = Op->OP == 0x1F6;
|
|
|
|
auto* Flag = [&]() -> OrderedNode* {
|
|
if (IsADCX) {
|
|
return GetRFLAG(X86State::RFLAG_CF_LOC);
|
|
} else {
|
|
return GetRFLAG(X86State::RFLAG_OF_LOC);
|
|
}
|
|
}();
|
|
|
|
auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto* Before = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
auto ALUOp = _Add(OpSize, Src, Flag);
|
|
auto Result = _Add(OpSize, Before, ALUOp);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
auto Zero = _Constant(0);
|
|
auto One = _Constant(1);
|
|
auto SelectOpLT = _Select(IR::COND_ULT, Result, Src, One, Zero);
|
|
auto SelectOpLE = _Select(IR::COND_ULE, Result, Src, One, Zero);
|
|
auto SelectFlag = _Select(IR::COND_EQ, Flag, One, SelectOpLE, SelectOpLT);
|
|
|
|
if (IsADCX) {
|
|
SetRFLAG<X86State::RFLAG_CF_LOC>(SelectFlag);
|
|
} else {
|
|
SetRFLAG<X86State::RFLAG_OF_LOC>(SelectFlag);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RCROp1Bit(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
const auto Size = GetSrcBitSize(Op);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
uint32_t Shift = 1;
|
|
|
|
if (Size == 32 || Size == 64) {
|
|
// Rotate and insert CF in the upper bit
|
|
auto Res = _Extr(OpSizeFromSrc(Op), CF, Dest, Shift);
|
|
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto NewCF = _Bfe(OpSizeFromSrc(Op), 1, Shift - 1, Dest);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(NewCF);
|
|
|
|
if (Shift == 1) {
|
|
// OF is the top two MSBs XOR'd together
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(_Xor(OpSizeFromSrc(Op), _Bfe(OpSizeFromSrc(Op), 1, Size - 1, Res), _Bfe(OpSizeFromSrc(Op), 1, Size - 2, Res)));
|
|
}
|
|
}
|
|
else {
|
|
// Res = Src >> Shift
|
|
OrderedNode *Res = _Bfe(OpSize::i32Bit, Size - Shift, Shift, Dest);
|
|
|
|
// inject the CF
|
|
Res = _Or(OpSize::i32Bit, Res, _Lshl(OpSize::i32Bit, CF, _Constant(Size, Size - Shift)));
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
// CF only changes if we actually shifted
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto NewCF = _Bfe(OpSize::i32Bit, 1, Shift - 1, Dest);
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(NewCF);
|
|
|
|
// OF is the top two MSBs XOR'd together
|
|
// Only when Shift == 1, it is undefined otherwise
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(_Xor(OpSize::i32Bit, _Bfe(OpSize::i32Bit, 1, Size - 1, Res), _Bfe(OpSize::i32Bit, 1, Size - 2, Res)));
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RCROp8x1Bit(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
const auto SizeBit = GetSrcBitSize(Op);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
uint32_t Shift = 1;
|
|
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto NewCF = _Bfe(OpSize::i32Bit, 1, Shift - 1, Dest);
|
|
|
|
// Rotate and insert CF in the upper bit
|
|
OrderedNode *Res = _Bfe(OpSize::i32Bit, 7, 1, Dest);
|
|
Res = _Bfi(OpSize::i32Bit, 1, 7, Res, CF);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(NewCF);
|
|
|
|
if (Shift == 1) {
|
|
// OF is the top two MSBs XOR'd together
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(_Xor(OpSize::i32Bit, _Bfe(OpSize::i32Bit, 1, SizeBit - 1, Res), _Bfe(OpSize::i32Bit, 1, SizeBit - 2, Res)));
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RCROp(OpcodeArgs) {
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
if (Size == 8 || Size == 16) {
|
|
RCRSmallerOp(Op);
|
|
return;
|
|
}
|
|
const auto OpSize = OpSizeFromSrc(Op);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
// Res = Src >> Shift
|
|
OrderedNode *Res = _Lshr(OpSize, Dest, Src);
|
|
|
|
// Res |= (Src << (Size - Shift + 1));
|
|
OrderedNode *SrcShl = _Sub(OpSize, _Constant(Size, Size + 1), Src);
|
|
auto TmpHigher = _Lshl(OpSize, Dest, SrcShl);
|
|
|
|
auto One = _Constant(Size, 1);
|
|
auto Zero = _Constant(Size, 0);
|
|
|
|
auto CompareResult = _Select(FEXCore::IR::COND_UGT,
|
|
Src, One,
|
|
TmpHigher, Zero);
|
|
|
|
Res = _Or(OpSize, Res, CompareResult);
|
|
|
|
// If Shift != 0 then we can inject the CF
|
|
OrderedNode *CFShl = _Sub(OpSize, _Constant(Size, Size), Src);
|
|
auto TmpCF = _Lshl(OpSize::i64Bit, CF, CFShl);
|
|
|
|
CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, One,
|
|
TmpCF, Zero);
|
|
|
|
Res = _Or(OpSize, Res, CompareResult);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
// CF only changes if we actually shifted
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto NewCF = _Bfe(OpSize, 1, 0, _Lshr(OpSize, Dest, _Sub(OpSize, Src, One)));
|
|
CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, One,
|
|
NewCF, CF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(CompareResult);
|
|
|
|
// OF is the top two MSBs XOR'd together
|
|
// Only when Shift == 1, it is undefined otherwise
|
|
// Only changed if shift isn't zero
|
|
auto OF = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
auto NewOF = _Xor(OpSize, _Bfe(OpSize, 1, Size - 1, Res), _Bfe(OpSize, 1, Size - 2, Res));
|
|
CompareResult = _Select(FEXCore::IR::COND_EQ,
|
|
Src, _Constant(0),
|
|
OF, NewOF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(CompareResult);
|
|
}
|
|
|
|
void OpDispatchBuilder::RCRSmallerOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
Src = _And(OpSize::i32Bit, Src, _Constant(Size, 0x1F));
|
|
|
|
OrderedNode *Tmp{};
|
|
|
|
// Insert the incoming value across the temporary 64bit source
|
|
// Make sure to insert at <BitSize> + 1 offsets
|
|
// We need to cover 32bits plus the amount that could rotate in
|
|
|
|
if (Size == 8) {
|
|
// 8-bit optimal cascade
|
|
// Cascade: 0
|
|
// Data: -> [7:0]
|
|
// CF: -> [8:8]
|
|
// Cascade: 1
|
|
// Data: -> [16:9]
|
|
// CF: -> [17:17]
|
|
// Cascade: 2
|
|
// Data: -> [25:18]
|
|
// CF: -> [26:26]
|
|
// Cascade: 3
|
|
// Data: -> [34:27]
|
|
// CF: -> [35:35]
|
|
// Cascade: 4
|
|
// Data: -> [43:36]
|
|
// CF: -> [44:44]
|
|
|
|
// Insert CF, Destination already at [7:0]
|
|
Tmp = _Bfi(OpSize::i64Bit, 1, 8, Dest, CF);
|
|
|
|
// First Cascade, copies 9 bits from itself.
|
|
Tmp = _Bfi(OpSize::i64Bit, 9, 9, Tmp, Tmp);
|
|
|
|
// Second cascade, copies 18 bits from itself.
|
|
Tmp = _Bfi(OpSize::i64Bit, 18, 18, Tmp, Tmp);
|
|
|
|
// Final cascade, copies 9 bits again from itself.
|
|
Tmp = _Bfi(OpSize::i64Bit, 9, 36, Tmp, Tmp);
|
|
}
|
|
else {
|
|
// 16-bit optimal cascade
|
|
// Cascade: 0
|
|
// Data: -> [15:0]
|
|
// CF: -> [16:16]
|
|
// Cascade: 1
|
|
// Data: -> [32:17]
|
|
// CF: -> [33:33]
|
|
// Cascade: 2
|
|
// Data: -> [49:34]
|
|
// CF: -> [50:50]
|
|
|
|
// Insert CF, Destination already at [15:0]
|
|
Tmp = _Bfi(OpSize::i64Bit, 1, 16, Dest, CF);
|
|
|
|
// First Cascade, copies 17 bits from itself.
|
|
Tmp = _Bfi(OpSize::i64Bit, 17, 17, Tmp, Tmp);
|
|
|
|
// Final Cascade, copies 17 bits from itself again.
|
|
Tmp = _Bfi(OpSize::i64Bit, 17, 34, Tmp, Tmp);
|
|
}
|
|
|
|
// Entire bitfield has been setup
|
|
// Just extract the 8 or 16bits we need
|
|
OrderedNode *Res = _Lshr(OpSize::i64Bit, Tmp, Src);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
// CF only changes if we actually shifted
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto One = _Constant(Size, 1);
|
|
auto NewCF = _Bfe(OpSize::i64Bit, 1, 0, _Lshr(OpSize::i64Bit, Tmp, _Sub(OpSize::i32Bit, Src, One)));
|
|
auto CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, One,
|
|
NewCF, CF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(CompareResult);
|
|
|
|
// OF is the top two MSBs XOR'd together
|
|
// Only when Shift == 1, it is undefined otherwise
|
|
// Make it easier, just store it regardless
|
|
auto NewOF = _Xor(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Res))), _Bfe(OpSize::i64Bit, 1, Size - 1, Res), _Bfe(OpSize::i64Bit, 1, Size - 2, Res));
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(NewOF);
|
|
}
|
|
|
|
void OpDispatchBuilder::RCLOp1Bit(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
const auto Size = GetSrcBitSize(Op);
|
|
const auto OpSize = Size == 64 ? OpSize::i64Bit : OpSize::i32Bit;
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
uint32_t Shift = 1;
|
|
|
|
// Our new CF will be the top bit of the source
|
|
auto NewCF = _Bfe(OpSize, 1, Size - 1, Dest);
|
|
|
|
// Rotate left and insert CF in to lowest bit
|
|
OrderedNode *Res = _Lshl(OpSize, Dest, _Constant(Size, 1));
|
|
Res = _Or(OpSize, Res, CF);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(NewCF);
|
|
|
|
if (Shift == 1) {
|
|
// OF is the top two MSBs XOR'd together
|
|
// Top two MSBs is CF and top bit of result
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(_Xor(OpSize::i32Bit, _Bfe(OpSize, 1, Size - 1, Res), NewCF));
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RCLOp(OpcodeArgs) {
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
if (Size == 8 || Size == 16) {
|
|
RCLSmallerOp(Op);
|
|
return;
|
|
}
|
|
const auto OpSize = OpSizeFromSrc(Op);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
// Res = Src << Shift
|
|
OrderedNode *Res = _Lshl(OpSize, Dest, Src);
|
|
|
|
// Res |= (Src << (Size - Shift + 1));
|
|
OrderedNode *SrcShl = _Sub(OpSize, _Constant(Size, Size + 1), Src);
|
|
auto TmpHigher = _Lshr(OpSize, Dest, SrcShl);
|
|
|
|
auto One = _Constant(Size, 1);
|
|
auto Zero = _Constant(Size, 0);
|
|
|
|
auto CompareResult = _Select(FEXCore::IR::COND_UGT,
|
|
Src, One,
|
|
TmpHigher, Zero);
|
|
|
|
Res = _Or(OpSize, Res, CompareResult);
|
|
|
|
// If Shift != 0 then we can inject the CF
|
|
OrderedNode *CFShl = _Sub(OpSize, Src, _Constant(Size, 1));
|
|
auto TmpCF = _Lshl(OpSize::i64Bit, CF, CFShl);
|
|
|
|
CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, One,
|
|
TmpCF, Zero);
|
|
|
|
Res = _Or(OpSize, Res, CompareResult);
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
{
|
|
// CF only changes if we actually shifted
|
|
// Our new CF will be bit (Shift - 1) of the source
|
|
auto NewCF = _Bfe(OpSize, 1, 0, _Lshr(OpSize, Dest, _Sub(OpSize, _Constant(Size, Size), Src)));
|
|
CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, One,
|
|
NewCF, CF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(CompareResult);
|
|
|
|
// OF is the top two MSBs XOR'd together
|
|
// Only when Shift == 1, it is undefined otherwise
|
|
// Only changed if shift isn't zero
|
|
auto OF = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
auto NewOF = _Xor(OpSize, _Bfe(OpSize, 1, Size - 1, Res), NewCF);
|
|
CompareResult = _Select(FEXCore::IR::COND_EQ,
|
|
Src, _Constant(0),
|
|
OF, NewOF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(CompareResult);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::RCLSmallerOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
|
|
const auto Size = GetSrcBitSize(Op);
|
|
|
|
// x86 masks the shift by 0x3F or 0x1F depending on size of op
|
|
Src = _And(OpSize::i32Bit, Src, _Constant(Size, 0x1F));
|
|
|
|
OrderedNode *Tmp = _Constant(64, 0);
|
|
|
|
for (size_t i = 0; i < (32 + Size + 1); i += (Size + 1)) {
|
|
// Insert incoming value
|
|
Tmp = _Bfi(OpSize::i64Bit, Size, 63 - i - Size, Tmp, Dest);
|
|
|
|
// Insert CF
|
|
Tmp = _Bfi(OpSize::i64Bit, 1, 63 - i, Tmp, CF);
|
|
}
|
|
|
|
// Insert incoming value
|
|
Tmp = _Bfi(OpSize::i64Bit, Size, 0, Tmp, Dest);
|
|
|
|
// The data is now set up like this
|
|
// [Data][CF]:[Data][CF]:[Data][CF]:[Data][CF]
|
|
// Shift 1 more bit that expected to get our result
|
|
// Shifting to the right will now behave like a rotate to the left
|
|
// Which we emulate with a _Ror
|
|
OrderedNode *Res = _Ror(OpSize::i64Bit, Tmp, _Sub(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, _Constant(Size, 64), Src));
|
|
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
|
|
{
|
|
// Our new CF is now at the bit position that we are shifting
|
|
// Either 0 if CF hasn't changed (CF is living in bit 0)
|
|
// or higher
|
|
auto NewCF = _Bfe(OpSize::i64Bit, 1, 0, _Ror(OpSize::i64Bit, Tmp, _Sub(OpSize::i64Bit, _Constant(63), Src)));
|
|
auto CompareResult = _Select(FEXCore::IR::COND_UGE,
|
|
Src, _Constant(1),
|
|
NewCF, CF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(CompareResult);
|
|
|
|
// OF is only defined for 1 bit shifts
|
|
// To make it easy, just always store a result
|
|
// OF is the XOR of the NewCF and the MSB of the result
|
|
// Only changed if shift isn't zero
|
|
auto OF = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
auto NewOF = _Xor(OpSize::i64Bit, _Bfe(OpSize::i64Bit, 1, Size - 1, Res), NewCF);
|
|
CompareResult = _Select(FEXCore::IR::COND_EQ,
|
|
Src, _Constant(0),
|
|
OF, NewOF);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_OF_LOC>(CompareResult);
|
|
}
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::BTOp(OpcodeArgs) {
|
|
OrderedNode *Result;
|
|
OrderedNode *Src{};
|
|
bool AlreadyMasked{};
|
|
|
|
const uint32_t Size = GetDstBitSize(Op);
|
|
const uint32_t Mask = Size - 1;
|
|
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
|
|
if (Op->Src[SrcIndex].IsGPR()) {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
} else {
|
|
// Can only be an immediate
|
|
// Masked by operand size
|
|
Src = _Constant(Size, Op->Src[SrcIndex].Data.Literal.Value & Mask);
|
|
AlreadyMasked = true;
|
|
}
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
// When the destination is a GPR, we don't care about garbage in the upper bits.
|
|
// Load the full register.
|
|
auto Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, CTX->GetGPRSize(), Op->Flags, -1);
|
|
|
|
OrderedNode *BitSelect{};
|
|
if (AlreadyMasked) {
|
|
BitSelect = Src;
|
|
} else {
|
|
OrderedNode *SizeMask = _Constant(Mask);
|
|
|
|
// Get the bit selection from the src
|
|
BitSelect = _And(OpSize::i64Bit, Src, SizeMask);
|
|
}
|
|
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Dest))), Dest, BitSelect);
|
|
} else {
|
|
// Load the address to the memory location
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
// Get the bit selection from the src
|
|
OrderedNode *BitSelect = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Src))), 3, 0, Src);
|
|
|
|
// Address is provided as bits we want BYTE offsets
|
|
// Extract Signed offset
|
|
Src = _Sbfe(OpSize::i64Bit, Size - 3, 3, Src);
|
|
|
|
// Get the address offset by shifting out the size of the op (To shift out the bit selection)
|
|
// Then use that to index in to the memory location by size of op
|
|
|
|
// Now add the addresses together and load the memory
|
|
OrderedNode *MemoryLocation = _Add(OpSize::i64Bit, Dest, Src);
|
|
Result = _LoadMemAutoTSO(GPRClass, 1, MemoryLocation, 1);
|
|
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Result))), Result, BitSelect);
|
|
}
|
|
|
|
// OF/SF/ZF/AF/PF undefined.
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Bfe(IR::SizeToOpSize(GetOpSize(Result)), 1, 0, Result));
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::BTROp(OpcodeArgs) {
|
|
OrderedNode *Result;
|
|
OrderedNode *Src{};
|
|
bool AlreadyMasked{};
|
|
|
|
const uint32_t Size = GetDstBitSize(Op);
|
|
const uint32_t Mask = Size - 1;
|
|
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
|
|
if (Op->Src[SrcIndex].IsGPR()) {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
} else {
|
|
// Can only be an immediate
|
|
// Masked by operand size
|
|
Src = _Constant(Size, Op->Src[SrcIndex].Data.Literal.Value & Mask);
|
|
AlreadyMasked = true;
|
|
}
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
// When the destination is a GPR, we don't care about garbage in the upper bits.
|
|
// Load the full register.
|
|
auto Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, CTX->GetGPRSize(), Op->Flags, -1);
|
|
|
|
OrderedNode *BitSelect{};
|
|
if (AlreadyMasked) {
|
|
BitSelect = Src;
|
|
} else {
|
|
OrderedNode *SizeMask = _Constant(Mask);
|
|
|
|
// Get the bit selection from the src
|
|
BitSelect = _And(OpSize::i64Bit, Src, SizeMask);
|
|
}
|
|
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Dest))), Dest, BitSelect);
|
|
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
Dest = _Andn(OpSize::i64Bit, Dest, BitMask);
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
} else {
|
|
// Load the address to the memory location
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
|
|
// Get the bit selection from the src
|
|
OrderedNode *BitSelect = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Src))), 3, 0, Src);
|
|
|
|
// Address is provided as bits we want BYTE offsets
|
|
// Extract Signed offset
|
|
Src = _Sbfe(OpSize::i64Bit, Size - 3, 3, Src);
|
|
|
|
// Get the address offset by shifting out the size of the op (To shift out the bit selection)
|
|
// Then use that to index in to the memory location by size of op
|
|
|
|
// Now add the addresses together and load the memory
|
|
OrderedNode *MemoryLocation = _Add(OpSize::i64Bit, Dest, Src);
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
// We don't current support this IR op though
|
|
Result = _AtomicFetchCLR(OpSize::i8Bit, BitMask, MemoryLocation);
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Result))), Result, BitSelect);
|
|
} else {
|
|
OrderedNode *Value = _LoadMemAutoTSO(GPRClass, 1, MemoryLocation, 1);
|
|
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Value))), Value, BitSelect);
|
|
Value = _Andn(OpSize::i64Bit, Value, BitMask);
|
|
_StoreMemAutoTSO(GPRClass, 1, MemoryLocation, Value, 1);
|
|
}
|
|
}
|
|
|
|
// OF/SF/ZF/AF/PF undefined.
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Bfe(IR::SizeToOpSize(GetOpSize(Result)), 1, 0, Result));
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::BTSOp(OpcodeArgs) {
|
|
OrderedNode *Result;
|
|
OrderedNode *Src{};
|
|
bool AlreadyMasked{};
|
|
|
|
const uint32_t Size = GetDstBitSize(Op);
|
|
const uint32_t Mask = Size - 1;
|
|
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
|
|
if (Op->Src[SrcIndex].IsGPR()) {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
} else {
|
|
// Can only be an immediate
|
|
// Masked by operand size
|
|
Src = _Constant(Size, Op->Src[SrcIndex].Data.Literal.Value & Mask);
|
|
AlreadyMasked = true;
|
|
}
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
// When the destination is a GPR, we don't care about garbage in the upper bits.
|
|
// Load the full register.
|
|
auto Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, CTX->GetGPRSize(), Op->Flags, -1);
|
|
|
|
OrderedNode *BitSelect{};
|
|
if (AlreadyMasked) {
|
|
BitSelect = Src;
|
|
} else {
|
|
OrderedNode *SizeMask = _Constant(Mask);
|
|
|
|
// Get the bit selection from the src
|
|
BitSelect = _And(OpSize::i64Bit, Src, SizeMask);
|
|
}
|
|
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Dest))), Dest, BitSelect);
|
|
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
Dest = _Or(OpSize::i64Bit, Dest, BitMask);
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
} else {
|
|
// Load the address to the memory location
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
// Get the bit selection from the src
|
|
OrderedNode *BitSelect = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Src))), 3, 0, Src);
|
|
|
|
// Address is provided as bits we want BYTE offsets
|
|
// Extract Signed offset
|
|
Src = _Sbfe(OpSize::i64Bit, Size - 3, 3, Src);
|
|
|
|
// Get the address offset by shifting out the size of the op (To shift out the bit selection)
|
|
// Then use that to index in to the memory location by size of op
|
|
|
|
// Now add the addresses together and load the memory
|
|
OrderedNode *MemoryLocation = _Add(OpSize::i64Bit, Dest, Src);
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
Result = _AtomicFetchOr(OpSize::i8Bit, BitMask, MemoryLocation);
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Result))), Result, BitSelect);
|
|
} else {
|
|
OrderedNode *Value = _LoadMemAutoTSO(GPRClass, 1, MemoryLocation, 1);
|
|
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Value))), Value, BitSelect);
|
|
Value = _Or(OpSize::i64Bit, Value, BitMask);
|
|
_StoreMemAutoTSO(GPRClass, 1, MemoryLocation, Value, 1);
|
|
}
|
|
}
|
|
|
|
// OF/SF/ZF/AF/PF undefined.
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Bfe(IR::SizeToOpSize(GetOpSize(Result)), 1, 0, Result));
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::BTCOp(OpcodeArgs) {
|
|
OrderedNode *Result;
|
|
OrderedNode *Src{};
|
|
bool AlreadyMasked{};
|
|
|
|
const uint32_t Size = GetDstBitSize(Op);
|
|
const uint32_t Mask = Size - 1;
|
|
|
|
// Deferred flags are invalidated now
|
|
InvalidateDeferredFlags();
|
|
|
|
if (Op->Src[SrcIndex].IsGPR()) {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, -1);
|
|
} else {
|
|
// Can only be an immediate
|
|
// Masked by operand size
|
|
Src = _Constant(Size, Op->Src[SrcIndex].Data.Literal.Value & Mask);
|
|
AlreadyMasked = true;
|
|
}
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
// When the destination is a GPR, we don't care about garbage in the upper bits.
|
|
// Load the full register.
|
|
auto Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, CTX->GetGPRSize(), Op->Flags, -1);
|
|
|
|
OrderedNode *BitSelect{};
|
|
if (AlreadyMasked) {
|
|
BitSelect = Src;
|
|
} else {
|
|
OrderedNode *SizeMask = _Constant(Mask);
|
|
|
|
// Get the bit selection from the src
|
|
BitSelect = _And(OpSize::i64Bit, Src, SizeMask);
|
|
}
|
|
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Dest))), Dest, BitSelect);
|
|
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
Dest = _Xor(OpSize::i64Bit, Dest, BitMask);
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
} else {
|
|
// Load the address to the memory location
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
// Get the bit selection from the src
|
|
OrderedNode *BitSelect = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Src))), 3, 0, Src);
|
|
|
|
// Address is provided as bits we want BYTE offsets
|
|
// Extract Signed offset
|
|
Src = _Sbfe(OpSize::i64Bit, Size - 3, 3, Src);
|
|
|
|
// Get the address offset by shifting out the size of the op (To shift out the bit selection)
|
|
// Then use that to index in to the memory location by size of op
|
|
|
|
// Now add the addresses together and load the memory
|
|
OrderedNode *MemoryLocation = _Add(OpSize::i64Bit, Dest, Src);
|
|
OrderedNode *BitMask = _Lshl(OpSize::i64Bit, _Constant(1), BitSelect);
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
Result = _AtomicFetchXor(OpSize::i8Bit, BitMask, MemoryLocation);
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Result))), Result, BitSelect);
|
|
} else {
|
|
OrderedNode *Value = _LoadMemAutoTSO(GPRClass, 1, MemoryLocation, 1);
|
|
|
|
// Now shift in to the correct bit location
|
|
Result = _Lshr(IR::SizeToOpSize(std::max<uint8_t>(4u, GetOpSize(Value))), Value, BitSelect);
|
|
Value = _Xor(OpSize::i64Bit, Value, BitMask);
|
|
_StoreMemAutoTSO(GPRClass, 1, MemoryLocation, Value, 1);
|
|
}
|
|
}
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Bfe(IR::SizeToOpSize(GetOpSize(Result)), 1, 0, Result));
|
|
}
|
|
|
|
void OpDispatchBuilder::IMUL1SrcOp(OpcodeArgs) {
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
OrderedNode *Src2 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
uint8_t Size = GetSrcSize(Op);
|
|
if (Size != 8) {
|
|
Src1 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src1);
|
|
Src2 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src2);
|
|
}
|
|
|
|
auto Dest = _Mul(OpSize::i64Bit, Src1, Src2);
|
|
OrderedNode *ResultHigh{};
|
|
if (Size < 8) {
|
|
ResultHigh = _Sbfe(OpSize::i64Bit, Size * 8, Size * 8, Dest);
|
|
}
|
|
else {
|
|
ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2);
|
|
}
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
GenerateFlags_MUL(Op, Dest, ResultHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::IMUL2SrcOp(OpcodeArgs) {
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Src2 = LoadSource(GPRClass, Op, Op->Src[1], Op->Flags, -1);
|
|
|
|
uint8_t Size = GetSrcSize(Op);
|
|
if (Size != 8) {
|
|
Src1 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src1);
|
|
Src2 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src2);
|
|
}
|
|
|
|
auto Dest = _Mul(OpSize::i64Bit, Src1, Src2);
|
|
OrderedNode *ResultHigh{};
|
|
if (Size < 8) {
|
|
ResultHigh = _Sbfe(OpSize::i64Bit, Size * 8, Size * 8, Dest);
|
|
}
|
|
else {
|
|
ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2);
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
GenerateFlags_MUL(Op, Dest, ResultHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::IMULOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
OrderedNode* Src2 = LoadGPRRegister(X86State::REG_RAX);
|
|
|
|
if (Size != 8) {
|
|
Src1 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src1);
|
|
Src2 = _Sbfe(OpSize::i64Bit, Size * 8, 0, Src2);
|
|
}
|
|
|
|
OrderedNode *Result = _Mul(OpSize::i64Bit, Src1, Src2);
|
|
OrderedNode *ResultHigh{};
|
|
if (Size == 1) {
|
|
// Result is stored in AX
|
|
StoreGPRRegister(X86State::REG_RAX, Result, 2);
|
|
ResultHigh = _Sbfe(OpSize::i64Bit, 8, 8, Result);
|
|
}
|
|
else if (Size == 2) {
|
|
// 16bits stored in AX
|
|
// 16bits stored in DX
|
|
StoreGPRRegister(X86State::REG_RAX, Result, Size);
|
|
ResultHigh = _Sbfe(OpSize::i64Bit, 16, 16, Result);
|
|
StoreGPRRegister(X86State::REG_RDX, ResultHigh, Size);
|
|
}
|
|
else if (Size == 4) {
|
|
// 32bits stored in EAX
|
|
// 32bits stored in EDX
|
|
// Make sure they get Zext correctly
|
|
auto LocalResult = _Bfe(OpSize::i64Bit, 32, 0, Result);
|
|
auto LocalResultHigh = _Bfe(OpSize::i64Bit, 32, 32, Result);
|
|
ResultHigh = _Sbfe(OpSize::i64Bit, 32, 32, Result);
|
|
Result = _Sbfe(OpSize::i64Bit, 32, 0, Result);
|
|
StoreGPRRegister(X86State::REG_RAX, LocalResult);
|
|
StoreGPRRegister(X86State::REG_RDX, LocalResultHigh);
|
|
}
|
|
else if (Size == 8) {
|
|
if (!CTX->Config.Is64BitMode) {
|
|
LogMan::Msg::EFmt("Doesn't exist in 32bit mode");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
// 64bits stored in RAX
|
|
// 64bits stored in RDX
|
|
ResultHigh = _MulH(OpSize::i64Bit, Src1, Src2);
|
|
StoreGPRRegister(X86State::REG_RAX, Result);
|
|
StoreGPRRegister(X86State::REG_RDX, ResultHigh);
|
|
}
|
|
|
|
GenerateFlags_MUL(Op, Result, ResultHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::MULOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
OrderedNode* Src2 = LoadGPRRegister(X86State::REG_RAX);
|
|
|
|
if (Size != 8) {
|
|
Src1 = _Bfe(OpSize::i64Bit, Size * 8, 0, Src1);
|
|
Src2 = _Bfe(OpSize::i64Bit, Size * 8, 0, Src2);
|
|
}
|
|
OrderedNode *Result = _UMul(OpSize::i64Bit, Src1, Src2);
|
|
OrderedNode *ResultHigh{};
|
|
|
|
if (Size == 1) {
|
|
// Result is stored in AX
|
|
StoreGPRRegister(X86State::REG_RAX, Result, 2);
|
|
ResultHigh = _Bfe(OpSize::i64Bit, 8, 8, Result);
|
|
}
|
|
else if (Size == 2) {
|
|
// 16bits stored in AX
|
|
// 16bits stored in DX
|
|
StoreGPRRegister(X86State::REG_RAX, Result, Size);
|
|
ResultHigh = _Bfe(OpSize::i64Bit, 16, 16, Result);
|
|
StoreGPRRegister(X86State::REG_RDX, ResultHigh, Size);
|
|
}
|
|
else if (Size == 4) {
|
|
// 32bits stored in EAX
|
|
// 32bits stored in EDX
|
|
OrderedNode *ResultLow = _Bfe(OpSize::i64Bit, 32, 0, Result);
|
|
ResultHigh = _Bfe(OpSize::i64Bit, 32, 32, Result);
|
|
StoreGPRRegister(X86State::REG_RAX, ResultLow);
|
|
StoreGPRRegister(X86State::REG_RDX, ResultHigh);
|
|
}
|
|
else if (Size == 8) {
|
|
if (!CTX->Config.Is64BitMode) {
|
|
LogMan::Msg::EFmt("Doesn't exist in 32bit mode");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
// 64bits stored in RAX
|
|
// 64bits stored in RDX
|
|
ResultHigh = _UMulH(OpSize::i64Bit, Src1, Src2);
|
|
StoreGPRRegister(X86State::REG_RAX, Result);
|
|
StoreGPRRegister(X86State::REG_RDX, ResultHigh);
|
|
}
|
|
|
|
GenerateFlags_UMUL(Op, ResultHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::NOTOp(OpcodeArgs) {
|
|
uint8_t Size = GetSrcSize(Op);
|
|
OrderedNode *MaskConst{};
|
|
if (Size == 8) {
|
|
MaskConst = _Constant(~0ULL);
|
|
}
|
|
else {
|
|
MaskConst = _Constant((1ULL << (Size * 8)) - 1);
|
|
}
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
_AtomicXor(IR::SizeToOpSize(Size), MaskConst, DestMem);
|
|
}
|
|
else {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
Src = _Xor(OpSize::i64Bit, Src, MaskConst);
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::XADDOp(OpcodeArgs) {
|
|
OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Result;
|
|
|
|
const auto Size = GetSrcBitSize(Op);
|
|
const auto OpSize = Size == 64 ? OpSize::i64Bit : OpSize::i32Bit;
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
// If this is a GPR then we can just do an Add
|
|
Result = _Add(OpSize, Dest, Src);
|
|
|
|
// Previous value in dest gets stored in src
|
|
StoreResult(GPRClass, Op, Op->Src[0], Dest, -1);
|
|
|
|
// Calculated value gets stored in dst (order is important if dst is same as src)
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
|
|
GenerateFlags_ADD(Op, Result, Dest, Src);
|
|
}
|
|
else {
|
|
HandledLock = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK;
|
|
Dest = AppendSegmentOffset(Dest, Op->Flags);
|
|
auto Before = _AtomicFetchAdd(OpSizeFromSrc(Op), Src, Dest);
|
|
StoreResult(GPRClass, Op, Op->Src[0], Before, -1);
|
|
Result = _Add(OpSize, Before, Src); // Seperate result just for flags
|
|
|
|
GenerateFlags_ADD(Op, Result, Before, Src);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::PopcountOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
Src = _Popcount(OpSizeFromSrc(Op), Src);
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
|
|
GenerateFlags_POPCOUNT(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::DAAOp(OpcodeArgs) {
|
|
CalculateDeferredFlags();
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
auto AF = LoadAF();
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(0));
|
|
CalculateDeferredFlags();
|
|
|
|
auto Cond = _Or(OpSize::i64Bit, AF, _Select(FEXCore::IR::COND_UGT, _And(OpSize::i64Bit, AL, _Constant(0xF)), _Constant(9), _Constant(1), _Constant(0)));
|
|
auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
auto TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
auto EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
|
|
CalculateDeferredFlags();
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
SetAF(0);
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
{
|
|
auto NewAL = _Add(OpSize::i64Bit, AL, _Constant(0x6));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAL, 1);
|
|
CalculateDeferredFlags();
|
|
auto NewCF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
// XXX: I don't think this is correct. Needs Investigation.
|
|
// The `CF` variable is the original CF from the start of the operation
|
|
// The `NewCF` will be _Constant(0) stored aboved.
|
|
// So Or(CF, _Constant(0)) ill mean CF gets updated to the old value in the true case?
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Or(OpSize::i64Bit, CF, NewCF));
|
|
SetAF(1);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
|
|
Cond = _Or(OpSize::i64Bit, CF, _Select(FEXCore::IR::COND_UGT, AL, _Constant(0x99), _Constant(1), _Constant(0)));
|
|
FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(0));
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
{
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
|
|
auto NewAL = _Add(OpSize::i64Bit, AL, _Constant(0x60));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAL, 1);
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(1));
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
// Update Flags
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_SF_LOC>(_Select(FEXCore::IR::COND_UGE, _And(OpSize::i64Bit, AL, _Constant(0x80)), _Constant(0), _Constant(1), _Constant(0)));
|
|
SetRFLAG<FEXCore::X86State::RFLAG_ZF_LOC>(_Select(FEXCore::IR::COND_EQ, _And(OpSize::i64Bit, AL, _Constant(0xFF)), _Constant(0), _Constant(1), _Constant(0)));
|
|
CalculatePF(AL);
|
|
FixupAF();
|
|
}
|
|
|
|
void OpDispatchBuilder::DASOp(OpcodeArgs) {
|
|
CalculateDeferredFlags();
|
|
auto CF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
auto AF = LoadAF();
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(0));
|
|
CalculateDeferredFlags();
|
|
|
|
auto Cond = _Or(OpSize::i64Bit, AF, _Select(FEXCore::IR::COND_UGT, _And(OpSize::i64Bit, AL, _Constant(0xf)), _Constant(9), _Constant(1), _Constant(0)));
|
|
auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
auto TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
auto EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
|
|
CalculateDeferredFlags();
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
SetAF(0);
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
{
|
|
auto NewAL = _Sub(OpSize::i64Bit, AL, _Constant(0x6));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAL, 1);
|
|
CalculateDeferredFlags();
|
|
auto NewCF = GetRFLAG(FEXCore::X86State::RFLAG_CF_LOC);
|
|
// XXX: I don't think this is correct. Needs Investigation.
|
|
// The `CF` variable is the original CF from the start of the operation
|
|
// The `NewCF` will be _Constant(0) stored aboved.
|
|
// So Or(CF, _Constant(0)) ill mean CF gets updated to the old value in the true case?
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Or(OpSize::i64Bit, CF, NewCF));
|
|
SetAF(1);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
|
|
Cond = _Or(OpSize::i64Bit, CF, _Select(FEXCore::IR::COND_UGT, AL, _Constant(0x99), _Constant(1), _Constant(0)));
|
|
FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(0));
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
{
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
auto NewAL = _Sub(OpSize::i64Bit, AL, _Constant(0x60));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAL, 1);
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(1));
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
// Update Flags
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
SetRFLAG<FEXCore::X86State::RFLAG_SF_LOC>(_Select(FEXCore::IR::COND_UGE, _And(OpSize::i64Bit, AL, _Constant(0x80)), _Constant(0), _Constant(1), _Constant(0)));
|
|
SetRFLAG<FEXCore::X86State::RFLAG_ZF_LOC>(_Select(FEXCore::IR::COND_EQ, _And(OpSize::i64Bit, AL, _Constant(0xFF)), _Constant(0), _Constant(1), _Constant(0)));
|
|
CalculatePF(AL);
|
|
FixupAF();
|
|
}
|
|
|
|
void OpDispatchBuilder::AAAOp(OpcodeArgs) {
|
|
InvalidateDeferredFlags();
|
|
|
|
auto AF = LoadAF();
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
auto AX = LoadGPRRegister(X86State::REG_RAX, 2);
|
|
auto Cond = _Or(OpSize::i64Bit, AF, _Select(FEXCore::IR::COND_UGT, _And(OpSize::i64Bit, AL, _Constant(0xF)), _Constant(9), _Constant(1), _Constant(0)));
|
|
|
|
auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
auto TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
auto EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
auto NewAX = _And(OpSize::i64Bit, AX, _Constant(0xFF0F));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAX, 2);
|
|
ZeroNZCV();
|
|
SetAF(0);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
|
|
{
|
|
auto NewAX = _Add(OpSize::i64Bit, AX, _Constant(0x106));
|
|
auto Result = _And(OpSize::i64Bit, NewAX, _Constant(0xFF0F));
|
|
StoreGPRRegister(X86State::REG_RAX, Result, 2);
|
|
ZeroNZCV();
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(1));
|
|
SetAF(1);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
}
|
|
|
|
void OpDispatchBuilder::AASOp(OpcodeArgs) {
|
|
InvalidateDeferredFlags();
|
|
|
|
auto AF = LoadAF();
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
auto AX = LoadGPRRegister(X86State::REG_RAX, 2);
|
|
auto Cond = _Or(OpSize::i64Bit, AF, _Select(FEXCore::IR::COND_UGT, _And(OpSize::i64Bit, AL, _Constant(0xF)), _Constant(9), _Constant(1), _Constant(0)));
|
|
|
|
auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
auto TrueBlock = CreateNewCodeBlockAfter(FalseBlock);
|
|
auto EndBlock = CreateNewCodeBlockAfter(TrueBlock);
|
|
_CondJump(Cond, TrueBlock, FalseBlock);
|
|
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
{
|
|
auto NewAX = _And(OpSize::i64Bit, AX, _Constant(0xFF0F));
|
|
StoreGPRRegister(X86State::REG_RAX, NewAX, 2);
|
|
ZeroNZCV();
|
|
SetAF(0);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(TrueBlock);
|
|
StartNewBlock();
|
|
{
|
|
auto NewAX = _Sub(OpSize::i64Bit, AX, _Constant(6));
|
|
NewAX = _Sub(OpSize::i64Bit, NewAX, _Constant(0x100));
|
|
auto Result = _And(OpSize::i64Bit, NewAX, _Constant(0xFF0F));
|
|
StoreGPRRegister(X86State::REG_RAX, Result, 2);
|
|
ZeroNZCV();
|
|
SetRFLAG<FEXCore::X86State::RFLAG_CF_LOC>(_Constant(1));
|
|
SetAF(1);
|
|
CalculateDeferredFlags();
|
|
_Jump(EndBlock);
|
|
}
|
|
SetCurrentCodeBlock(EndBlock);
|
|
StartNewBlock();
|
|
}
|
|
|
|
void OpDispatchBuilder::AAMOp(OpcodeArgs) {
|
|
InvalidateDeferredFlags();
|
|
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
auto Imm8 = _Constant(Op->Src[0].Data.Literal.Value & 0xFF);
|
|
auto UDivOp = _UDiv(OpSize::i64Bit, AL, Imm8);
|
|
auto URemOp = _URem(OpSize::i64Bit, AL, Imm8);
|
|
auto AH = _Lshl(OpSize::i64Bit, UDivOp, _Constant(8));
|
|
auto AX = _Add(OpSize::i64Bit, AH, URemOp);
|
|
StoreGPRRegister(X86State::REG_RAX, AX, 2);
|
|
|
|
// Update Flags
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
SetNZ_ZeroCV(1, AL);
|
|
CalculatePF(AL);
|
|
_InvalidateFlags(1u << X86State::RFLAG_AF_LOC);
|
|
}
|
|
|
|
void OpDispatchBuilder::AADOp(OpcodeArgs) {
|
|
InvalidateDeferredFlags();
|
|
|
|
auto AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
auto AH = _Lshr(OpSize::i32Bit, LoadGPRRegister(X86State::REG_RAX, 2), _Constant(8));
|
|
auto Imm8 = _Constant(Op->Src[0].Data.Literal.Value & 0xFF);
|
|
auto NewAL = _Add(OpSize::i64Bit, AL, _Mul(OpSize::i64Bit, AH, Imm8));
|
|
auto Result = _And(OpSize::i64Bit, NewAL, _Constant(0xFF));
|
|
StoreGPRRegister(X86State::REG_RAX, Result, 2);
|
|
|
|
// Update Flags
|
|
AL = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
SetNZ_ZeroCV(1, AL);
|
|
CalculatePF(AL);
|
|
_InvalidateFlags(1u << X86State::RFLAG_AF_LOC);
|
|
}
|
|
|
|
void OpDispatchBuilder::XLATOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadGPRRegister(X86State::REG_RBX);
|
|
OrderedNode *Offset = LoadGPRRegister(X86State::REG_RAX, 1);
|
|
|
|
Src = AppendSegmentOffset(Src, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
Src = _Add(OpSize::i64Bit, Src, Offset);
|
|
|
|
auto Res = _LoadMemAutoTSO(GPRClass, 1, Src, 1);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, Res, 1);
|
|
}
|
|
|
|
template<OpDispatchBuilder::Segment Seg>
|
|
void OpDispatchBuilder::ReadSegmentReg(OpcodeArgs) {
|
|
// 64-bit only
|
|
// Doesn't hit the segment register optimization
|
|
auto Size = GetSrcSize(Op);
|
|
OrderedNode *Src{};
|
|
if constexpr (Seg == Segment::FS) {
|
|
Src = _LoadContext(Size, GPRClass, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
}
|
|
else {
|
|
Src = _LoadContext(Size, GPRClass, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
}
|
|
|
|
template<OpDispatchBuilder::Segment Seg>
|
|
void OpDispatchBuilder::WriteSegmentReg(OpcodeArgs) {
|
|
// Documentation claims that the 32-bit version of this instruction inserts in to the lower 32-bits of the segment
|
|
// This is incorrect and it instead zero extends the 32-bit value to 64-bit
|
|
auto Size = GetDstSize(Op);
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
if constexpr (Seg == Segment::FS) {
|
|
_StoreContext(Size, GPRClass, Src, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
}
|
|
else {
|
|
_StoreContext(Size, GPRClass, Src, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::EnterOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
LOGMAN_THROW_A_FMT(Op->Src[0].IsLiteral(), "Src1 needs to be literal here");
|
|
const uint64_t Value = Op->Src[0].Data.Literal.Value;
|
|
|
|
const uint16_t AllocSpace = Value & 0xFFFF;
|
|
const uint8_t Level = (Value >> 16) & 0x1F;
|
|
|
|
const auto PushValue = [&](uint8_t Size, OrderedNode *Src) -> OrderedNode* {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
auto NewSP = _Push(GPRSize, Size, Src, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
return NewSP;
|
|
};
|
|
|
|
auto OldBP = LoadGPRRegister(X86State::REG_RBP);
|
|
auto NewSP = PushValue(GPRSize, OldBP);
|
|
auto temp_RBP = NewSP;
|
|
|
|
if (Level > 0) {
|
|
for (uint8_t i = 1; i < Level; ++i) {
|
|
auto Offset = _Constant(i * GPRSize);
|
|
auto MemLoc = _Sub(IR::SizeToOpSize(GPRSize), OldBP, Offset);
|
|
auto Mem = _LoadMem(GPRClass, GPRSize, MemLoc, GPRSize);
|
|
NewSP = PushValue(GPRSize, Mem);
|
|
}
|
|
NewSP = PushValue(GPRSize, temp_RBP);
|
|
}
|
|
NewSP = _Sub(IR::SizeToOpSize(GPRSize), NewSP, _Constant(AllocSpace));
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
StoreGPRRegister(X86State::REG_RBP, temp_RBP);
|
|
}
|
|
|
|
void OpDispatchBuilder::SGDTOp(OpcodeArgs) {
|
|
auto DestAddress = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
|
|
// Store an emulated value in the format of:
|
|
// uint16_t Limit;
|
|
// {uint32_t,uint64_t} Base;
|
|
//
|
|
// Limit is always 0
|
|
// Base is always in kernel space at: 0xFFFFFFFFFFFE0000ULL
|
|
//
|
|
// Operand size prefix is ignored on this instruction, size purely depends on operating mode.
|
|
uint64_t GDTAddress = 0xFFFFFFFFFFFE0000ULL;
|
|
size_t GDTStoreSize = 8;
|
|
if (!CTX->Config.Is64BitMode) {
|
|
// Mask off upper bits if 32-bit result.
|
|
GDTAddress &= ~0U;
|
|
GDTStoreSize = 4;
|
|
}
|
|
|
|
_StoreMemAutoTSO(GPRClass, 2, DestAddress, _Constant(0));
|
|
_StoreMemAutoTSO(GPRClass, GDTStoreSize, _Add(OpSize::i64Bit, DestAddress, _Constant(2)), _Constant(GDTAddress));
|
|
}
|
|
|
|
void OpDispatchBuilder::RDTSCOp(OpcodeArgs) {
|
|
auto Counter = _CycleCounter();
|
|
auto CounterLow = _Bfe(OpSize::i64Bit, 32, 0, Counter);
|
|
auto CounterHigh = _Bfe(OpSize::i64Bit, 32, 32, Counter);
|
|
StoreGPRRegister(X86State::REG_RAX, CounterLow);
|
|
StoreGPRRegister(X86State::REG_RDX, CounterHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::INCOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX) {
|
|
LogMan::Msg::EFmt("Can't handle REP on this");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
OrderedNode *Dest;
|
|
OrderedNode *Result;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
auto OneConst = _Constant(Size, 1);
|
|
|
|
const bool IsLocked = DestIsLockedMem(Op);
|
|
|
|
if (IsLocked) {
|
|
HandledLock = true;
|
|
auto DestAddress = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestAddress = AppendSegmentOffset(DestAddress, Op->Flags);
|
|
Dest = _AtomicFetchAdd(OpSizeFromSrc(Op), OneConst, DestAddress);
|
|
|
|
} else {
|
|
Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
}
|
|
|
|
Result = _Add(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, OneConst);
|
|
if (!IsLocked) {
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
GenerateFlags_ADD(Op, Result, Dest, OneConst, false);
|
|
}
|
|
|
|
void OpDispatchBuilder::DECOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX) {
|
|
LogMan::Msg::EFmt("Can't handle REP on this");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
OrderedNode *Dest;
|
|
OrderedNode *Result;
|
|
const auto Size = GetSrcBitSize(Op);
|
|
auto OneConst = _Constant(Size, 1);
|
|
|
|
const bool IsLocked = DestIsLockedMem(Op);
|
|
|
|
if (IsLocked) {
|
|
HandledLock = true;
|
|
auto DestAddress = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestAddress = AppendSegmentOffset(DestAddress, Op->Flags);
|
|
Dest = _AtomicFetchSub(OpSizeFromSrc(Op), OneConst, DestAddress);
|
|
} else {
|
|
Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
}
|
|
|
|
Result = _Sub(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Dest, OneConst);
|
|
if (!IsLocked) {
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
GenerateFlags_SUB(Op, Result, Dest, OneConst, false);
|
|
}
|
|
|
|
void OpDispatchBuilder::STOSOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) {
|
|
LogMan::Msg::EFmt("Can't handle adddress size");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
const auto Size = GetSrcSize(Op);
|
|
const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) != 0;
|
|
|
|
if (!Repeat) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
// Only ES prefix
|
|
Dest = AppendSegmentOffset(Dest, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
|
|
// Store to memory where RDI points
|
|
_StoreMemAutoTSO(GPRClass, Size, Dest, Src, Size);
|
|
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
|
|
// Calculate direction.
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
SizeConst, NegSizeConst);
|
|
|
|
// Offset the pointer
|
|
OrderedNode *TailDest = LoadGPRRegister(X86State::REG_RDI);
|
|
TailDest = _Add(OpSize::i64Bit, TailDest, PtrDir);
|
|
|
|
StoreGPRRegister(X86State::REG_RDI, TailDest);
|
|
}
|
|
else {
|
|
// FEX doesn't support partial faulting REP instructions.
|
|
// Converting this to a `MemSet` IR op optimizes this quite significantly in our codegen.
|
|
// If FEX is to gain support for faulting REP instructions, then this implementation needs to change significantly.
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
OrderedNode *Dest = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
// Only ES prefix
|
|
auto Segment = GetSegment(0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
|
|
OrderedNode *Counter = LoadGPRRegister(X86State::REG_RCX);
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
|
|
auto Result = _MemSet(CTX->IsAtomicTSOEnabled(), Size, Segment ?: InvalidNode, Dest, Src, Counter, DF);
|
|
StoreGPRRegister(X86State::REG_RCX, _Constant(0));
|
|
StoreGPRRegister(X86State::REG_RDI, Result);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVSOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) {
|
|
LogMan::Msg::EFmt("Can't handle adddress size");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
// RA now can handle these to be here, to avoid DF accesses
|
|
const auto Size = GetSrcSize(Op);
|
|
|
|
// Calculate direction.
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
|
|
if (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) {
|
|
auto SrcAddr = LoadGPRRegister(X86State::REG_RSI);
|
|
auto DstAddr = LoadGPRRegister(X86State::REG_RDI);
|
|
auto Counter = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
auto DstSegment = GetSegment(0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
auto SrcSegment = GetSegment(Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Result = _MemCpy(CTX->IsAtomicTSOEnabled(), Size,
|
|
DstSegment ?: InvalidNode,
|
|
SrcSegment ?: InvalidNode,
|
|
DstAddr, SrcAddr, Counter, DF);
|
|
|
|
OrderedNode *Result_Dst = _ExtractElementPair(OpSize::i64Bit, Result, 0);
|
|
OrderedNode *Result_Src = _ExtractElementPair(OpSize::i64Bit, Result, 1);
|
|
|
|
StoreGPRRegister(X86State::REG_RCX, _Constant(0));
|
|
StoreGPRRegister(X86State::REG_RDI, Result_Dst);
|
|
StoreGPRRegister(X86State::REG_RSI, Result_Src);
|
|
}
|
|
else {
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ, DF, _Constant(0), SizeConst, NegSizeConst);
|
|
|
|
OrderedNode *RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
OrderedNode *RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
RDI= AppendSegmentOffset(RDI, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
RSI = AppendSegmentOffset(RSI, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Src = _LoadMemAutoTSO(GPRClass, Size, RSI, Size);
|
|
|
|
// Store to memory where RDI points
|
|
_StoreMemAutoTSO(GPRClass, Size, RDI, Src, Size);
|
|
|
|
RSI = _Add(OpSize::i64Bit, RSI, PtrDir);
|
|
RDI = _Add(OpSize::i64Bit, RDI, PtrDir);
|
|
|
|
StoreGPRRegister(X86State::REG_RSI, RSI);
|
|
StoreGPRRegister(X86State::REG_RDI, RDI);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CMPSOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) {
|
|
LogMan::Msg::EFmt("Can't handle adddress size");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
const auto Size = GetSrcSize(Op);
|
|
|
|
bool Repeat = Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX);
|
|
if (!Repeat) {
|
|
OrderedNode *Dest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
OrderedNode *Dest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
// Only ES prefix
|
|
Dest_RDI = AppendSegmentOffset(Dest_RDI, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
// Default DS prefix
|
|
Dest_RSI = AppendSegmentOffset(Dest_RSI, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Src1 = _LoadMemAutoTSO(GPRClass, Size, Dest_RDI, Size);
|
|
auto Src2 = _LoadMemAutoTSO(GPRClass, Size, Dest_RSI, Size);
|
|
|
|
OrderedNode* Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Src2, Src1);
|
|
GenerateFlags_SUB(Op, Result, Src2, Src1);
|
|
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
_Constant(Size), _Constant(-Size));
|
|
|
|
// Offset the pointer
|
|
Dest_RDI = _Add(OpSize::i64Bit, Dest_RDI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RDI, Dest_RDI);
|
|
|
|
// Offset second pointer
|
|
Dest_RSI = _Add(OpSize::i64Bit, Dest_RSI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RSI, Dest_RSI);
|
|
}
|
|
else {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
bool REPE = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX;
|
|
|
|
// read DF once
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
_Constant(Size), _Constant(-Size));
|
|
|
|
auto JumpStart = _Jump();
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopStart = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetJumpTarget(JumpStart, LoopStart);
|
|
SetCurrentCodeBlock(LoopStart);
|
|
StartNewBlock();
|
|
|
|
OrderedNode *Counter = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
// Can we end the block?
|
|
auto CondJump = _CondJump(Counter, {COND_EQ});
|
|
IRPair<IROp_CondJump> InternalCondJump;
|
|
|
|
auto LoopTail = CreateNewCodeBlockAfter(LoopStart);
|
|
SetFalseJumpTarget(CondJump, LoopTail);
|
|
SetCurrentCodeBlock(LoopTail);
|
|
StartNewBlock();
|
|
|
|
// Working loop
|
|
{
|
|
OrderedNode *Dest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
OrderedNode *Dest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
// Only ES prefix
|
|
Dest_RDI = AppendSegmentOffset(Dest_RDI, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
// Default DS prefix
|
|
Dest_RSI = AppendSegmentOffset(Dest_RSI, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Src1 = _LoadMemAutoTSO(GPRClass, Size, Dest_RDI, Size);
|
|
auto Src2 = _LoadMem(GPRClass, Size, Dest_RSI, Size);
|
|
|
|
OrderedNode* Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Src2, Src1);
|
|
GenerateFlags_SUB(Op, Result, Src2, Src1);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *TailCounter = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
// Decrement counter
|
|
TailCounter = _Sub(OpSize::i64Bit, TailCounter, _Constant(1));
|
|
|
|
// Store the counter since we don't have phis
|
|
StoreGPRRegister(X86State::REG_RCX, TailCounter);
|
|
|
|
// Offset the pointer
|
|
Dest_RDI = _Add(OpSize::i64Bit, Dest_RDI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RDI, Dest_RDI);
|
|
|
|
// Offset second pointer
|
|
Dest_RSI = _Add(OpSize::i64Bit, Dest_RSI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RSI, Dest_RSI);
|
|
|
|
OrderedNode *ZF = GetRFLAG(FEXCore::X86State::RFLAG_ZF_LOC);
|
|
CalculateDeferredFlags();
|
|
InternalCondJump = _CondJump(ZF, {REPE ? COND_NEQ : COND_EQ});
|
|
|
|
// Jump back to the start if we have more work to do
|
|
SetTrueJumpTarget(InternalCondJump, LoopStart);
|
|
}
|
|
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopEnd = CreateNewCodeBlockAfter(LoopTail);
|
|
SetTrueJumpTarget(CondJump, LoopEnd);
|
|
|
|
SetFalseJumpTarget(InternalCondJump, LoopEnd);
|
|
|
|
SetCurrentCodeBlock(LoopEnd);
|
|
StartNewBlock();
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::LODSOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) {
|
|
LogMan::Msg::EFmt("Can't handle adddress size");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
const auto Size = GetSrcSize(Op);
|
|
const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX)) != 0;
|
|
|
|
if (!Repeat) {
|
|
OrderedNode *Dest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
Dest_RSI = AppendSegmentOffset(Dest_RSI, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Src = _LoadMemAutoTSO(GPRClass, Size, Dest_RSI, Size);
|
|
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
SizeConst, NegSizeConst);
|
|
|
|
// Offset the pointer
|
|
OrderedNode *TailDest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
|
|
TailDest_RSI = _Add(OpSize::i64Bit, TailDest_RSI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RSI, TailDest_RSI);
|
|
}
|
|
else {
|
|
// Calculate flags early. because end of block
|
|
CalculateDeferredFlags();
|
|
|
|
// XXX: Theoretically LODS could be optimized to
|
|
// RSI += {-}(RCX * Size)
|
|
// RAX = [RSI - Size]
|
|
// But this might violate the case of an application scanning pages for read permission and catching the fault
|
|
// May or may not matter
|
|
|
|
// Read DF once
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
SizeConst, NegSizeConst);
|
|
|
|
auto JumpStart = _Jump();
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopStart = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetJumpTarget(JumpStart, LoopStart);
|
|
SetCurrentCodeBlock(LoopStart);
|
|
StartNewBlock();
|
|
|
|
OrderedNode *Counter = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
// Can we end the block?
|
|
|
|
// We leave if RCX = 0
|
|
auto CondJump = _CondJump(Counter, {COND_EQ});
|
|
|
|
auto LoopTail = CreateNewCodeBlockAfter(LoopStart);
|
|
SetFalseJumpTarget(CondJump, LoopTail);
|
|
SetCurrentCodeBlock(LoopTail);
|
|
StartNewBlock();
|
|
|
|
// Working loop
|
|
{
|
|
OrderedNode *Dest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
|
|
Dest_RSI = AppendSegmentOffset(Dest_RSI, Op->Flags, FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX);
|
|
|
|
auto Src = _LoadMemAutoTSO(GPRClass, Size, Dest_RSI, Size);
|
|
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
|
|
OrderedNode *TailCounter = LoadGPRRegister(X86State::REG_RCX);
|
|
OrderedNode *TailDest_RSI = LoadGPRRegister(X86State::REG_RSI);
|
|
|
|
// Decrement counter
|
|
TailCounter = _Sub(OpSize::i64Bit, TailCounter, _Constant(1));
|
|
|
|
// Store the counter since we don't have phis
|
|
StoreGPRRegister(X86State::REG_RCX, TailCounter);
|
|
|
|
// Offset the pointer
|
|
TailDest_RSI = _Add(OpSize::i64Bit, TailDest_RSI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RSI, TailDest_RSI);
|
|
|
|
// Jump back to the start, we have more work to do
|
|
_Jump(LoopStart);
|
|
}
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopEnd = CreateNewCodeBlockAfter(LoopTail);
|
|
SetTrueJumpTarget(CondJump, LoopEnd);
|
|
SetCurrentCodeBlock(LoopEnd);
|
|
StartNewBlock();
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::SCASOp(OpcodeArgs) {
|
|
if (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) {
|
|
LogMan::Msg::EFmt("Can't handle adddress size");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
|
|
const auto Size = GetSrcSize(Op);
|
|
const bool Repeat = (Op->Flags & (FEXCore::X86Tables::DecodeFlags::FLAG_REPNE_PREFIX | FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX)) != 0;
|
|
|
|
if (!Repeat) {
|
|
OrderedNode *Dest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
Dest_RDI = AppendSegmentOffset(Dest_RDI, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
|
|
auto Src1 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto Src2 = _LoadMemAutoTSO(GPRClass, Size, Dest_RDI, Size);
|
|
|
|
OrderedNode* Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Src1, Src2);
|
|
GenerateFlags_SUB(Op, Result, Src1, Src2);
|
|
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
SizeConst, NegSizeConst);
|
|
|
|
// Offset the pointer
|
|
OrderedNode *TailDest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
TailDest_RDI = _Add(OpSize::i64Bit, TailDest_RDI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RDI, TailDest_RDI);
|
|
}
|
|
else {
|
|
// Calculate flags early. because end of block
|
|
CalculateDeferredFlags();
|
|
|
|
bool REPE = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REP_PREFIX;
|
|
|
|
// read DF once
|
|
|
|
auto SizeConst = _Constant(Size);
|
|
auto NegSizeConst = _Constant(-Size);
|
|
|
|
auto DF = GetRFLAG(FEXCore::X86State::RFLAG_DF_LOC);
|
|
auto PtrDir = _Select(FEXCore::IR::COND_EQ,
|
|
DF, _Constant(0),
|
|
SizeConst, NegSizeConst);
|
|
|
|
auto JumpStart = _Jump();
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopStart = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetJumpTarget(JumpStart, LoopStart);
|
|
SetCurrentCodeBlock(LoopStart);
|
|
StartNewBlock();
|
|
|
|
OrderedNode *Counter = LoadGPRRegister(X86State::REG_RCX);
|
|
|
|
// Can we end the block?
|
|
// We leave if RCX = 0
|
|
auto CondJump = _CondJump(Counter, {COND_EQ});
|
|
IRPair<IROp_CondJump> InternalCondJump;
|
|
|
|
auto LoopTail = CreateNewCodeBlockAfter(LoopStart);
|
|
SetFalseJumpTarget(CondJump, LoopTail);
|
|
SetCurrentCodeBlock(LoopTail);
|
|
StartNewBlock();
|
|
|
|
// Working loop
|
|
{
|
|
OrderedNode *Dest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
Dest_RDI = AppendSegmentOffset(Dest_RDI, 0, FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX, true);
|
|
|
|
auto Src1 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
auto Src2 = _LoadMemAutoTSO(GPRClass, Size, Dest_RDI, Size);
|
|
|
|
OrderedNode* Result = _Sub(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Src1, Src2);
|
|
GenerateFlags_SUB(Op, Result, Src1, Src2);
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
OrderedNode *TailCounter = LoadGPRRegister(X86State::REG_RCX);
|
|
OrderedNode *TailDest_RDI = LoadGPRRegister(X86State::REG_RDI);
|
|
|
|
// Decrement counter
|
|
TailCounter = _Sub(OpSize::i64Bit, TailCounter, _Constant(1));
|
|
|
|
// Store the counter since we don't have phis
|
|
StoreGPRRegister(X86State::REG_RCX, TailCounter);
|
|
|
|
// Offset the pointer
|
|
TailDest_RDI = _Add(OpSize::i64Bit, TailDest_RDI, PtrDir);
|
|
StoreGPRRegister(X86State::REG_RDI, TailDest_RDI);
|
|
|
|
OrderedNode *ZF = GetRFLAG(FEXCore::X86State::RFLAG_ZF_LOC);
|
|
CalculateDeferredFlags();
|
|
InternalCondJump = _CondJump(ZF, {REPE ? COND_NEQ : COND_EQ});
|
|
|
|
// Jump back to the start if we have more work to do
|
|
SetTrueJumpTarget(InternalCondJump, LoopStart);
|
|
}
|
|
// Make sure to start a new block after ending this one
|
|
auto LoopEnd = CreateNewCodeBlockAfter(LoopTail);
|
|
SetTrueJumpTarget(CondJump, LoopEnd);
|
|
|
|
SetFalseJumpTarget(InternalCondJump, LoopEnd);
|
|
|
|
SetCurrentCodeBlock(LoopEnd);
|
|
StartNewBlock();
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::BSWAPOp(OpcodeArgs) {
|
|
OrderedNode *Dest;
|
|
const auto Size = GetSrcSize(Op);
|
|
if (Size == 2) {
|
|
// BSWAP of 16bit is undef. ZEN+ causes the lower 16bits to get zero'd
|
|
Dest = _Constant(0);
|
|
}
|
|
else {
|
|
Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, CTX->GetGPRSize(), Op->Flags, -1);
|
|
Dest = _Rev(IR::SizeToOpSize(Size), Dest);
|
|
}
|
|
StoreResult(GPRClass, Op, Dest, -1);
|
|
}
|
|
|
|
void OpDispatchBuilder::PUSHFOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src = GetPackedRFLAG();
|
|
if (Size != 8) {
|
|
Src = _Bfe(OpSize::i32Bit, Size * 8, 0, Src);
|
|
}
|
|
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
auto NewSP = _Push(GPRSize, Size, Src, OldSP);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
}
|
|
|
|
void OpDispatchBuilder::POPFOp(OpcodeArgs) {
|
|
const uint8_t Size = GetSrcSize(Op);
|
|
|
|
auto Constant = _Constant(Size);
|
|
auto OldSP = LoadGPRRegister(X86State::REG_RSP);
|
|
OrderedNode *Src = _LoadMem(GPRClass, Size, OldSP, Size);
|
|
auto NewSP = _Add(OpSize::i64Bit, OldSP, Constant);
|
|
|
|
// Store the new stack pointer
|
|
StoreGPRRegister(X86State::REG_RSP, NewSP);
|
|
|
|
// Add back our flag constants
|
|
// Bit 1 is always 1
|
|
// Bit 9 is always 1 because we always have interrupts enabled
|
|
|
|
Src = _Or(OpSize::i64Bit, Src, _Constant(Size * 8, 0x202));
|
|
|
|
SetPackedRFLAG(false, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::NEGOp(OpcodeArgs) {
|
|
HandledLock = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK) != 0;
|
|
|
|
auto Size = GetSrcSize(Op);
|
|
auto ZeroConst = _Constant(0);
|
|
|
|
OrderedNode *Dest{};
|
|
OrderedNode *Result{};
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
|
|
Dest = _AtomicFetchNeg(IR::SizeToOpSize(Size), DestMem);
|
|
Result = _Neg(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Dest);
|
|
}
|
|
else {
|
|
Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
Result = _Neg(Size == 8 ? OpSize::i64Bit : OpSize::i32Bit, Dest);
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
GenerateFlags_SUB(Op, Result, ZeroConst, Dest);
|
|
}
|
|
|
|
void OpDispatchBuilder::DIVOp(OpcodeArgs) {
|
|
// This loads the divisor
|
|
OrderedNode *Divisor = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
const auto Size = GetSrcSize(Op);
|
|
|
|
if (Size == 1) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, 2);
|
|
|
|
auto UDivOp = _UDiv(OpSize::i16Bit, Src1, Divisor);
|
|
auto URemOp = _URem(OpSize::i16Bit, Src1, Divisor);
|
|
|
|
// AX[15:0] = concat<URem[7:0]:UDiv[7:0]>
|
|
auto ResultAX = _Bfi(IR::SizeToOpSize(GPRSize), 8, 8, UDivOp, URemOp);
|
|
StoreGPRRegister(X86State::REG_RAX, ResultAX, 2);
|
|
}
|
|
else if (Size == 2) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX, Size);
|
|
auto UDivOp = _LUDiv(OpSize::i16Bit, Src1, Src2, Divisor);
|
|
auto URemOp = _LURem(OpSize::i16Bit, Src1, Src2, Divisor);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp, Size);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp, Size);
|
|
}
|
|
else if (Size == 4) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX, Size);
|
|
|
|
OrderedNode *UDivOp = _Bfe(OpSize::i32Bit, Size * 8, 0, _LUDiv(OpSize::i32Bit, Src1, Src2, Divisor));
|
|
OrderedNode *URemOp = _Bfe(OpSize::i32Bit, Size * 8, 0, _LURem(OpSize::i32Bit, Src1, Src2, Divisor));
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp);
|
|
}
|
|
else if (Size == 8) {
|
|
if (!CTX->Config.Is64BitMode) {
|
|
LogMan::Msg::EFmt("Doesn't exist in 32bit mode");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX);
|
|
|
|
auto UDivOp = _LUDiv(OpSize::i64Bit, Src1, Src2, Divisor);
|
|
auto URemOp = _LURem(OpSize::i64Bit, Src1, Src2, Divisor);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::IDIVOp(OpcodeArgs) {
|
|
// This loads the divisor
|
|
OrderedNode *Divisor = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1);
|
|
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
const auto Size = GetSrcSize(Op);
|
|
|
|
if (Size == 1) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, 2);
|
|
Src1 = _Sbfe(OpSize::i64Bit, 16, 0, Src1);
|
|
Divisor = _Sbfe(OpSize::i64Bit, 8, 0, Divisor);
|
|
|
|
auto UDivOp = _Div(OpSize::i64Bit, Src1, Divisor);
|
|
auto URemOp = _Rem(OpSize::i64Bit, Src1, Divisor);
|
|
|
|
// AX[15:0] = concat<URem[7:0]:UDiv[7:0]>
|
|
auto ResultAX = _Bfi(IR::SizeToOpSize(GPRSize), 8, 8, UDivOp, URemOp);
|
|
StoreGPRRegister(X86State::REG_RAX, ResultAX, 2);
|
|
}
|
|
else if (Size == 2) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX, Size);
|
|
auto UDivOp = _LDiv(OpSize::i16Bit, Src1, Src2, Divisor);
|
|
auto URemOp = _LRem(OpSize::i16Bit, Src1, Src2, Divisor);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp, Size);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp, Size);
|
|
}
|
|
else if (Size == 4) {
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX, Size);
|
|
|
|
OrderedNode *UDivOp = _Bfe(OpSize::i32Bit, Size * 8, 0, _LDiv(OpSize::i32Bit, Src1, Src2, Divisor));
|
|
OrderedNode *URemOp = _Bfe(OpSize::i32Bit, Size * 8, 0, _LRem(OpSize::i32Bit, Src1, Src2, Divisor));
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp);
|
|
}
|
|
else if (Size == 8) {
|
|
if (!CTX->Config.Is64BitMode) {
|
|
LogMan::Msg::EFmt("Doesn't exist in 32bit mode");
|
|
DecodeFailure = true;
|
|
return;
|
|
}
|
|
OrderedNode *Src1 = LoadGPRRegister(X86State::REG_RAX);
|
|
OrderedNode *Src2 = LoadGPRRegister(X86State::REG_RDX);
|
|
|
|
auto UDivOp = _LDiv(OpSize::i64Bit, Src1, Src2, Divisor);
|
|
auto URemOp = _LRem(OpSize::i64Bit, Src1, Src2, Divisor);
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, UDivOp);
|
|
StoreGPRRegister(X86State::REG_RDX, URemOp);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::BSFOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
const uint8_t DstSize = GetDstSize(Op) == 2 ? 2 : GPRSize;
|
|
OrderedNode *Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, DstSize, Op->Flags, -1);
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
// Find the LSB of this source
|
|
auto Result = _FindLSB(OpSizeFromSrc(Op), Src);
|
|
|
|
auto ZeroConst = _Constant(0);
|
|
|
|
// If Src was zero then the destination doesn't get modified
|
|
auto SelectOp = _Select(FEXCore::IR::COND_EQ,
|
|
Src, ZeroConst,
|
|
Dest, Result);
|
|
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, SelectOp, DstSize, -1);
|
|
|
|
GenerateFlags_BITSELECT(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::BSROp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
const uint8_t DstSize = GetDstSize(Op) == 2 ? 2 : GPRSize;
|
|
OrderedNode *Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, DstSize, Op->Flags, -1);
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
// Find the MSB of this source
|
|
auto Result = _FindMSB(OpSizeFromSrc(Op), Src);
|
|
|
|
auto ZeroConst = _Constant(0);
|
|
|
|
// If Src was zero then the destination doesn't get modified
|
|
auto SelectOp = _Select(FEXCore::IR::COND_EQ,
|
|
Src, ZeroConst,
|
|
Dest, Result);
|
|
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, SelectOp, DstSize, -1);
|
|
|
|
GenerateFlags_BITSELECT(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::CMPXCHGOp(OpcodeArgs) {
|
|
// CMPXCHG ModRM, reg, {RAX}
|
|
// MemData = *ModRM.dest
|
|
// if (RAX == MemData)
|
|
// modRM.dest = reg;
|
|
// ZF = 1
|
|
// else
|
|
// ZF = 0
|
|
// RAX = MemData
|
|
//
|
|
// CASL Xs, Xt, Xn
|
|
// MemData = *Xn
|
|
// if (MemData == Xs)
|
|
// *Xn = Xt
|
|
// Xs = MemData
|
|
|
|
const auto GPRSize = CTX->GetGPRSize();
|
|
auto Size = GetSrcSize(Op);
|
|
|
|
// This is our source register
|
|
OrderedNode *Src2 = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
// 0x80014000
|
|
// 0x80064000
|
|
// 0x80064000
|
|
|
|
if (Op->Dest.IsGPR()) {
|
|
OrderedNode *Src1{};
|
|
OrderedNode *Src1Lower{};
|
|
|
|
OrderedNode *Src3{};
|
|
OrderedNode *Src3Lower{};
|
|
if (GPRSize == 8 && Size == 4) {
|
|
Src1 = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, GPRSize, Op->Flags, -1);
|
|
Src3 = LoadGPRRegister(X86State::REG_RAX);
|
|
}
|
|
else {
|
|
Src1 = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, Size, Op->Flags, -1);
|
|
Src3 = LoadGPRRegister(X86State::REG_RAX);
|
|
}
|
|
|
|
if (Size != GPRSize) {
|
|
Src1Lower = _Bfe(IR::SizeToOpSize(GPRSize), Size * 8, 0, Src1);
|
|
Src3Lower = _Bfe(IR::SizeToOpSize(GPRSize), Size * 8, 0, Src3);
|
|
}
|
|
else {
|
|
Src1Lower = Src1;
|
|
Src3Lower = Src3;
|
|
}
|
|
|
|
// If our destination is a GPR then this behaves differently
|
|
// RAX = RAX == Op1 ? RAX : Op1
|
|
// AKA if they match then don't touch RAX value
|
|
// Otherwise set it to the rm operand
|
|
OrderedNode *CASResult = _Select(FEXCore::IR::COND_EQ,
|
|
Src1Lower, Src3Lower,
|
|
Src3Lower, Src1Lower);
|
|
|
|
// Op1 = RAX == Op1 ? Op2 : Op1
|
|
// If they match then set the rm operand to the input
|
|
// else don't set the rm operand
|
|
OrderedNode *DestResult = _Select(FEXCore::IR::COND_EQ,
|
|
Src1Lower, Src3Lower,
|
|
Src2, Src1);
|
|
|
|
// Store in to GPR Dest
|
|
// Have to make sure this is after the result store in RAX for when Dest == RAX
|
|
if (GPRSize == 8 && Size == 4) {
|
|
// This allows us to only hit the ZEXT case on failure
|
|
OrderedNode *RAXResult = _Select(FEXCore::IR::COND_EQ,
|
|
CASResult, Src3Lower,
|
|
Src3, Src1Lower);
|
|
|
|
// When the size is 4 we need to make sure not zext the GPR when the comparison fails
|
|
StoreGPRRegister(X86State::REG_RAX, RAXResult);
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, DestResult, GPRSize, -1);
|
|
}
|
|
else {
|
|
StoreGPRRegister(X86State::REG_RAX, CASResult, Size);
|
|
StoreResult(GPRClass, Op, DestResult, -1);
|
|
}
|
|
|
|
OrderedNode *Result = _Sub(IR::SizeToOpSize(GPRSize), Src3Lower, CASResult);
|
|
GenerateFlags_SUB(Op, Result, Src3Lower, CASResult);
|
|
}
|
|
else {
|
|
HandledLock = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK;
|
|
|
|
OrderedNode *Src3{};
|
|
OrderedNode *Src3Lower{};
|
|
if (GPRSize == 8 && Size == 4) {
|
|
Src3 = LoadGPRRegister(X86State::REG_RAX);
|
|
Src3Lower = _Bfe(OpSize::i32Bit, 32, 0, Src3);
|
|
}
|
|
else {
|
|
Src3 = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
Src3Lower = Src3;
|
|
}
|
|
// If this is a memory location then we want the pointer to it
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
|
|
Src1 = AppendSegmentOffset(Src1, Op->Flags);
|
|
|
|
// DataSrc = *Src1
|
|
// if (DataSrc == Src3) { *Src1 == Src2; } Src2 = DataSrc
|
|
// This will write to memory! Careful!
|
|
// Third operand must be a calculated guest memory address
|
|
OrderedNode *CASResult = _CAS(IR::SizeToOpSize(Size), Src3Lower, Src2, Src1);
|
|
OrderedNode *RAXResult = CASResult;
|
|
|
|
if (GPRSize == 8 && Size == 4) {
|
|
// This allows us to only hit the ZEXT case on failure
|
|
RAXResult = _Select(FEXCore::IR::COND_EQ,
|
|
CASResult, Src3Lower,
|
|
Src3, CASResult);
|
|
Size = 8;
|
|
}
|
|
|
|
// RAX gets the result of the CAS op
|
|
StoreGPRRegister(X86State::REG_RAX, RAXResult, Size);
|
|
|
|
|
|
const auto Size = GetDstBitSize(Op);
|
|
|
|
OrderedNode *Result = _Sub(Size == 64 ? OpSize::i64Bit : OpSize::i32Bit, Src3Lower, CASResult);
|
|
GenerateFlags_SUB(Op, Result, Src3Lower, CASResult);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CMPXCHGPairOp(OpcodeArgs) {
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
// REX.W used to determine if it is 16byte or 8byte
|
|
// Unlike CMPXCHG, the destination can only be a memory location
|
|
uint8_t Size = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? 8 : 4;
|
|
|
|
HandledLock = (Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK) != 0;
|
|
// If this is a memory location then we want the pointer to it
|
|
OrderedNode *Src1 = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
|
|
Src1 = AppendSegmentOffset(Src1, Op->Flags);
|
|
|
|
OrderedNode *Expected_Lower = LoadGPRRegister(X86State::REG_RAX, Size);
|
|
OrderedNode *Expected_Upper = LoadGPRRegister(X86State::REG_RDX, Size);
|
|
OrderedNode *Expected = _CreateElementPair(IR::SizeToOpSize(Size * 2), Expected_Lower, Expected_Upper);
|
|
|
|
OrderedNode *Desired_Lower = LoadGPRRegister(X86State::REG_RBX, Size);
|
|
OrderedNode *Desired_Upper = LoadGPRRegister(X86State::REG_RCX, Size);
|
|
OrderedNode *Desired = _CreateElementPair(IR::SizeToOpSize(Size * 2), Desired_Lower, Desired_Upper);
|
|
|
|
// ssa0 = Expected
|
|
// ssa1 = Desired
|
|
// ssa2 = MemoryLocation
|
|
|
|
// DataSrc = *MemSrc
|
|
// if (DataSrc == Expected) { *MemSrc == Desired; } Expected = DataSrc
|
|
// This will write to memory! Careful!
|
|
// Third operand must be a calculated guest memory address
|
|
|
|
OrderedNode *CASResult = _CASPair(IR::SizeToOpSize(Size * 2), Expected, Desired, Src1);
|
|
|
|
OrderedNode *Result_Lower = _ExtractElementPair(IR::SizeToOpSize(Size), CASResult, 0);
|
|
OrderedNode *Result_Upper = _ExtractElementPair(IR::SizeToOpSize(Size), CASResult, 1);
|
|
|
|
// Set ZF if memory result was expected
|
|
auto OneConst = _Constant(1);
|
|
auto ZeroConst = _Constant(0);
|
|
|
|
OrderedNode *ZFResult = _Select(FEXCore::IR::COND_EQ,
|
|
CASResult, Expected,
|
|
OneConst, ZeroConst);
|
|
|
|
// Set ZF
|
|
SetRFLAG<FEXCore::X86State::RFLAG_ZF_LOC>(ZFResult);
|
|
CalculateDeferredFlags();
|
|
|
|
auto CondJump = _CondJump(ZFResult);
|
|
|
|
// Make sure to start a new block after ending this one
|
|
auto JumpTarget = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetFalseJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
|
|
StoreGPRRegister(X86State::REG_RAX, Result_Lower);
|
|
StoreGPRRegister(X86State::REG_RDX, Result_Upper);
|
|
|
|
auto Jump = _Jump();
|
|
auto NextJumpTarget = CreateNewCodeBlockAfter(JumpTarget);
|
|
SetJumpTarget(Jump, NextJumpTarget);
|
|
SetTrueJumpTarget(CondJump, NextJumpTarget);
|
|
SetCurrentCodeBlock(NextJumpTarget);
|
|
StartNewBlock();
|
|
}
|
|
|
|
void OpDispatchBuilder::CreateJumpBlocks(fextl::vector<FEXCore::Frontend::Decoder::DecodedBlocks> const *Blocks) {
|
|
OrderedNode *PrevCodeBlock{};
|
|
for (auto &Target : *Blocks) {
|
|
auto CodeNode = CreateCodeNode();
|
|
|
|
JumpTargets.try_emplace(Target.Entry, JumpTargetInfo{CodeNode, false});
|
|
|
|
if (PrevCodeBlock) {
|
|
LinkCodeBlocks(PrevCodeBlock, CodeNode);
|
|
}
|
|
|
|
PrevCodeBlock = CodeNode;
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::BeginFunction(uint64_t RIP, fextl::vector<FEXCore::Frontend::Decoder::DecodedBlocks> const *Blocks, uint32_t NumInstructions) {
|
|
Entry = RIP;
|
|
auto IRHeader = _IRHeader(InvalidNode, RIP, 0, NumInstructions);
|
|
CreateJumpBlocks(Blocks);
|
|
|
|
auto Block = GetNewJumpBlock(RIP);
|
|
SetCurrentCodeBlock(Block);
|
|
IRHeader.first->Blocks = Block->Wrapped(DualListData.ListBegin());
|
|
|
|
LOGMAN_THROW_A_FMT(IsDeferredFlagsStored(), "Something failed to calculate flags and now we began with invalid state");
|
|
}
|
|
|
|
void OpDispatchBuilder::Finalize() {
|
|
// Calculate flags early.
|
|
// This usually doesn't emit any IR but in the case of hitting the block instruction limit it will
|
|
CalculateDeferredFlags();
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// Node 0 is invalid node
|
|
OrderedNode *RealNode = reinterpret_cast<OrderedNode*>(GetNode(1));
|
|
|
|
[[maybe_unused]] const FEXCore::IR::IROp_Header *IROp =
|
|
RealNode->Op(DualListData.DataBegin());
|
|
LOGMAN_THROW_AA_FMT(IROp->Op == OP_IRHEADER, "First op in function must be our header");
|
|
|
|
// Let's walk the jump blocks and see if we have handled every block target
|
|
for (auto &Handler : JumpTargets) {
|
|
if (Handler.second.HaveEmitted) continue;
|
|
|
|
// We haven't emitted. Dump out to the dispatcher
|
|
SetCurrentCodeBlock(Handler.second.BlockEntry);
|
|
CalculateDeferredFlags();
|
|
_ExitFunction(_EntrypointOffset(IR::SizeToOpSize(GPRSize), Handler.first - Entry));
|
|
}
|
|
}
|
|
|
|
uint8_t OpDispatchBuilder::GetDstSize(X86Tables::DecodedOp Op) const {
|
|
const uint32_t DstSizeFlag = X86Tables::DecodeFlags::GetSizeDstFlags(Op->Flags);
|
|
LOGMAN_THROW_AA_FMT(DstSizeFlag != 0 && DstSizeFlag != X86Tables::DecodeFlags::SIZE_MASK, "Invalid destination size for op");
|
|
return 1u << (DstSizeFlag - 1);
|
|
}
|
|
|
|
uint8_t OpDispatchBuilder::GetSrcSize(X86Tables::DecodedOp Op) const {
|
|
const uint32_t SrcSizeFlag = X86Tables::DecodeFlags::GetSizeSrcFlags(Op->Flags);
|
|
LOGMAN_THROW_AA_FMT(SrcSizeFlag != 0 && SrcSizeFlag != X86Tables::DecodeFlags::SIZE_MASK, "Invalid destination size for op");
|
|
return 1u << (SrcSizeFlag - 1);
|
|
}
|
|
|
|
uint32_t OpDispatchBuilder::GetSrcBitSize(X86Tables::DecodedOp Op) const {
|
|
return GetSrcSize(Op) * 8;
|
|
}
|
|
|
|
uint32_t OpDispatchBuilder::GetDstBitSize(X86Tables::DecodedOp Op) const {
|
|
return GetDstSize(Op) * 8;
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::GetSegment(uint32_t Flags, uint32_t DefaultPrefix, bool Override) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
if (CTX->Config.Is64BitMode) {
|
|
if (Flags & FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX) {
|
|
return _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
}
|
|
else if (Flags & FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) {
|
|
return _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
}
|
|
// If there was any other segment in 64bit then it is ignored
|
|
}
|
|
else {
|
|
uint32_t Prefix = Flags & FEXCore::X86Tables::DecodeFlags::FLAG_SEGMENTS;
|
|
if (!Prefix || Override) {
|
|
// If there was no prefix then use the default one if available
|
|
// Or the argument only uses a specific prefix (with override set)
|
|
Prefix = DefaultPrefix;
|
|
}
|
|
// With the segment register optimization we store the GDT bases directly in the segment register to remove indexed loads
|
|
OrderedNode *SegmentResult{};
|
|
switch (Prefix) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, es_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, cs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, ss_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, ds_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX:
|
|
SegmentResult = _LoadContext(GPRSize, GPRClass, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
break;
|
|
default:
|
|
break; // Do nothing
|
|
}
|
|
|
|
CheckLegacySegmentRead(SegmentResult, Prefix);
|
|
return SegmentResult;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::AppendSegmentOffset(OrderedNode *Value, uint32_t Flags, uint32_t DefaultPrefix, bool Override) {
|
|
auto Segment = GetSegment(Flags, DefaultPrefix, Override);
|
|
if (Segment) {
|
|
Value = _Add(IR::SizeToOpSize(std::max<uint8_t>(4, std::max(GetOpSize(Value), GetOpSize(Segment)))), Value, Segment);
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
|
|
void OpDispatchBuilder::CheckLegacySegmentRead(OrderedNode *NewNode, uint32_t SegmentReg) {
|
|
#ifndef FEX_DISABLE_TELEMETRY
|
|
if (SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX ||
|
|
SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) {
|
|
// FS and GS segments aren't considered legacy.
|
|
return;
|
|
}
|
|
|
|
if (!(SegmentsNeedReadCheck & SegmentReg)) {
|
|
// If the block has done multiple reads of a segment register then skip redundant read checks.
|
|
// Segment write will cause another read check.
|
|
return;
|
|
}
|
|
|
|
if (CTX->Config.DisableTelemetry()) {
|
|
// Telemetry disabled at runtime.
|
|
return;
|
|
}
|
|
|
|
FEXCore::Telemetry::TelemetryType TelemIndex{};
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_ES;
|
|
SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_CS;
|
|
SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_SS;
|
|
SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_DS;
|
|
SegmentsNeedReadCheck &= ~FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX;
|
|
break;
|
|
default: FEX_UNREACHABLE;
|
|
}
|
|
|
|
// Will set the telemetry value if NewNode is != 0
|
|
_TelemetrySetValue(NewNode, TelemIndex);
|
|
#endif
|
|
}
|
|
|
|
void OpDispatchBuilder::CheckLegacySegmentWrite(OrderedNode *NewNode, uint32_t SegmentReg) {
|
|
#ifndef FEX_DISABLE_TELEMETRY
|
|
if (SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX ||
|
|
SegmentReg == FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX) {
|
|
// FS and GS segments aren't considered legacy.
|
|
return;
|
|
}
|
|
|
|
if (CTX->Config.DisableTelemetry()) {
|
|
// Telemetry disabled at runtime.
|
|
return;
|
|
}
|
|
|
|
FEXCore::Telemetry::TelemetryType TelemIndex{};
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_ES;
|
|
SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_CS;
|
|
SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_SS;
|
|
SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX;
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
TelemIndex = FEXCore::Telemetry::TelemetryType::TYPE_WRITES_32BIT_SEGMENT_DS;
|
|
SegmentsNeedReadCheck |= FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX;
|
|
break;
|
|
default: FEX_UNREACHABLE;
|
|
}
|
|
|
|
// Will set the telemetry value if NewNode is != 0
|
|
_TelemetrySetValue(NewNode, TelemIndex);
|
|
#endif
|
|
}
|
|
|
|
void OpDispatchBuilder::UpdatePrefixFromSegment(OrderedNode *Segment, uint32_t SegmentReg) {
|
|
// Use BFE to extract the selector index in bits [15,3] of the segment register.
|
|
// In some cases the upper 16-bits of the 32-bit GPR contain garbage to ignore.
|
|
Segment = _Bfe(OpSize::i32Bit, 16 - 3, 3, Segment);
|
|
auto NewSegment = _LoadContextIndexed(Segment, 4, offsetof(FEXCore::Core::CPUState, gdt[0]), 4, GPRClass);
|
|
CheckLegacySegmentWrite(NewSegment, SegmentReg);
|
|
switch (SegmentReg) {
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, es_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, cs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, ss_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, ds_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, fs_cached));
|
|
break;
|
|
case FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX:
|
|
_StoreContext(4, GPRClass, NewSegment, offsetof(FEXCore::Core::CPUState, gs_cached));
|
|
break;
|
|
default: break; // Do nothing
|
|
}
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::LoadSource_WithOpSize(FEXCore::IR::RegisterClassType Class, FEXCore::X86Tables::DecodedOp const& Op, FEXCore::X86Tables::DecodedOperand const& Operand, uint8_t OpSize, uint32_t Flags, int8_t Align, bool LoadData, bool ForceLoad, MemoryAccessType AccessType, bool AllowUpperGarbage) {
|
|
LOGMAN_THROW_A_FMT(Operand.IsGPR() ||
|
|
Operand.IsLiteral() ||
|
|
Operand.IsGPRDirect() ||
|
|
Operand.IsGPRIndirect() ||
|
|
Operand.IsRIPRelative() ||
|
|
Operand.IsSIB(),
|
|
"Unsupported Src type");
|
|
|
|
OrderedNode *Src {nullptr};
|
|
bool LoadableType = false;
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
const uint32_t AddrSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) != 0 ? (GPRSize >> 1) : GPRSize;
|
|
|
|
if (Operand.IsLiteral()) {
|
|
uint64_t constant = Operand.Data.Literal.Value;
|
|
uint64_t width = Operand.Data.Literal.Size * 8;
|
|
|
|
if (Operand.Data.Literal.Size != 8) {
|
|
// zero extend
|
|
constant = constant & ((1ULL << width) - 1);
|
|
}
|
|
Src = _Constant(width, constant);
|
|
}
|
|
else if (Operand.IsGPR()) {
|
|
const auto gpr = Operand.Data.GPR.GPR;
|
|
const auto highIndex = Operand.Data.GPR.HighBits ? 1 : 0;
|
|
|
|
if (gpr >= FEXCore::X86State::REG_MM_0) {
|
|
Src = _LoadContext(OpSize, FPRClass, offsetof(FEXCore::Core::CPUState, mm[gpr - FEXCore::X86State::REG_MM_0]));
|
|
}
|
|
else if (gpr >= FEXCore::X86State::REG_XMM_0) {
|
|
const auto gprIndex = gpr - X86State::REG_XMM_0;
|
|
|
|
// Load the full register size if it is a XMM register source.
|
|
Src = LoadXMMRegister(gprIndex);
|
|
|
|
// Now extract the subregister if it was a partial load /smaller/ than SSE size
|
|
// TODO: Instead of doing the VMov implicitly on load, hunt down all use cases that require partial loads and do it after load.
|
|
// We don't have information here to know if the operation needs zero upper bits or can contain data.
|
|
if (OpSize < Core::CPUState::XMM_SSE_REG_SIZE) {
|
|
Src = _VMov(OpSize, Src);
|
|
}
|
|
}
|
|
else {
|
|
Src = LoadGPRRegister(gpr, OpSize, highIndex ? 8 : 0, AllowUpperGarbage);
|
|
}
|
|
}
|
|
else if (Operand.IsGPRDirect()) {
|
|
Src = LoadGPRRegister(Operand.Data.GPR.GPR, GPRSize);
|
|
|
|
LoadableType = true;
|
|
if (Operand.Data.GPR.GPR == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
else if (Operand.IsGPRIndirect()) {
|
|
auto GPR = LoadGPRRegister(Operand.Data.GPRIndirect.GPR, GPRSize);
|
|
|
|
auto Constant = _Constant(GPRSize * 8, Operand.Data.GPRIndirect.Displacement);
|
|
|
|
Src = _Add(IR::SizeToOpSize(GPRSize), GPR, Constant);
|
|
|
|
LoadableType = true;
|
|
if (Operand.Data.GPRIndirect.GPR == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
else if (Operand.IsRIPRelative()) {
|
|
if (CTX->Config.Is64BitMode) {
|
|
Src = GetRelocatedPC(Op, Operand.Data.RIPLiteral.Value.s);
|
|
}
|
|
else {
|
|
// 32bit this isn't RIP relative but instead absolute
|
|
Src = _Constant(GPRSize * 8, Operand.Data.RIPLiteral.Value.u);
|
|
}
|
|
|
|
LoadableType = true;
|
|
}
|
|
else if (Operand.IsSIB()) {
|
|
const bool IsVSIB = (Op->Flags & X86Tables::DecodeFlags::FLAG_VSIB_BYTE) != 0;
|
|
OrderedNode *Tmp{};
|
|
|
|
// NOTE: VSIB cannot have the index * scale portion calculated ahead of time,
|
|
// since the index in this case is a vector. So, we can't just apply the scale
|
|
// to it, since this needs to be applied to each element in the index register
|
|
// after said element has been sign extended. So, we pass this through for the
|
|
// instruction implementation to handle.
|
|
//
|
|
// What we do handle though, is the applying the displacement value to
|
|
// the base register (if a base register is provided), since this is a
|
|
// part of the address calculation that can be done ahead of time.
|
|
if (Operand.Data.SIB.Index != FEXCore::X86State::REG_INVALID && !IsVSIB) {
|
|
Tmp = LoadGPRRegister(Operand.Data.SIB.Index, GPRSize);
|
|
|
|
if (Operand.Data.SIB.Scale != 1) {
|
|
auto Constant = _Constant(GPRSize * 8, Operand.Data.SIB.Scale);
|
|
Tmp = _Mul(IR::SizeToOpSize(GPRSize), Tmp, Constant);
|
|
}
|
|
if (Operand.Data.SIB.Index == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
|
|
if (Operand.Data.SIB.Base != FEXCore::X86State::REG_INVALID) {
|
|
auto GPR = LoadGPRRegister(Operand.Data.SIB.Base, GPRSize);
|
|
|
|
if (Tmp != nullptr) {
|
|
Tmp = _Add(IR::SizeToOpSize(GPRSize), Tmp, GPR);
|
|
}
|
|
else {
|
|
Tmp = GPR;
|
|
}
|
|
|
|
if (Operand.Data.SIB.Base == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
|
|
if (Operand.Data.SIB.Offset) {
|
|
if (Tmp != nullptr) {
|
|
Src = _Add(IR::SizeToOpSize(GPRSize), Tmp, _Constant(GPRSize * 8, Operand.Data.SIB.Offset));
|
|
}
|
|
else {
|
|
Src = _Constant(GPRSize * 8, Operand.Data.SIB.Offset);
|
|
}
|
|
}
|
|
else {
|
|
if (Tmp != nullptr) {
|
|
Src = Tmp;
|
|
}
|
|
else {
|
|
Src = _Constant(GPRSize * 8, 0);
|
|
}
|
|
}
|
|
|
|
LoadableType = true;
|
|
}
|
|
else {
|
|
LOGMAN_MSG_A_FMT("Unknown Src Type: {}\n", Operand.Type);
|
|
}
|
|
|
|
if (LoadableType && AddrSize < GPRSize) {
|
|
// For 64-bit AddrSize can be 32-bit or 64-bit
|
|
// For 32-bit AddrSize can be 32-bit or 16-bit
|
|
//
|
|
// If the AddrSize is not the GPRSize then we need to clear the upper bits.
|
|
Src = _Bfe(IR::SizeToOpSize(GPRSize), AddrSize * 8, 0, Src);
|
|
}
|
|
|
|
if ((LoadableType && LoadData) || ForceLoad) {
|
|
Src = AppendSegmentOffset(Src, Flags);
|
|
|
|
if (AccessType == MemoryAccessType::ACCESS_NONTSO || AccessType == MemoryAccessType::ACCESS_STREAM) {
|
|
Src = _LoadMem(Class, OpSize, Src, Align == -1 ? OpSize : Align);
|
|
}
|
|
else {
|
|
Src = _LoadMemAutoTSO(Class, OpSize, Src, Align == -1 ? OpSize : Align);
|
|
}
|
|
}
|
|
return Src;
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::GetRelocatedPC(FEXCore::X86Tables::DecodedOp const& Op, int64_t Offset) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
return _EntrypointOffset(IR::SizeToOpSize(GPRSize), Op->PC + Op->InstSize + Offset - Entry);
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::LoadGPRRegister(uint32_t GPR, int8_t Size, uint8_t Offset, bool AllowUpperGarbage) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
if (Size == -1) {
|
|
Size = GPRSize;
|
|
}
|
|
OrderedNode *Reg = _LoadRegister(false, offsetof(FEXCore::Core::CPUState, gregs[GPR]), GPRClass, GPRFixedClass, GPRSize);
|
|
|
|
if ((!AllowUpperGarbage && (Size != GPRSize)) || Offset != 0) {
|
|
// Extract the subregister if requested.
|
|
const auto OpSize = IR::SizeToOpSize(std::max<uint8_t>(4u, Size));
|
|
if (AllowUpperGarbage)
|
|
Reg = _Lshr(OpSize, Reg, _Constant(Offset));
|
|
else
|
|
Reg = _Bfe(OpSize, Size * 8, Offset, Reg);
|
|
}
|
|
return Reg;
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::LoadXMMRegister(uint32_t XMM) {
|
|
const auto VectorSize = CTX->HostFeatures.SupportsAVX ? 32 : 16;
|
|
const auto VectorOffset = CTX->HostFeatures.SupportsAVX ?
|
|
offsetof(Core::CPUState, xmm.avx.data[XMM][0]) :
|
|
offsetof(Core::CPUState, xmm.sse.data[XMM][0]);
|
|
|
|
OrderedNode *Reg = _LoadRegister(false, VectorOffset, FPRClass, FPRFixedClass, VectorSize);
|
|
return Reg;
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreGPRRegister(uint32_t GPR, OrderedNode *const Src, int8_t Size, uint8_t Offset) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
if (Size == -1) {
|
|
Size = GPRSize;
|
|
}
|
|
|
|
OrderedNode *Reg = Src;
|
|
if (Size != GPRSize || Offset != 0) {
|
|
// Need to do an insert if not automatic size or zero offset.
|
|
Reg = LoadGPRRegister(GPR);
|
|
Reg = _Bfi(IR::SizeToOpSize(GPRSize), Size * 8, Offset, Reg, Src);
|
|
}
|
|
|
|
_StoreRegister(Reg, false, offsetof(FEXCore::Core::CPUState, gregs[GPR]), GPRClass, GPRFixedClass, GPRSize);
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreXMMRegister(uint32_t XMM, OrderedNode *const Src) {
|
|
const auto VectorSize = CTX->HostFeatures.SupportsAVX ? 32 : 16;
|
|
const auto VectorOffset = CTX->HostFeatures.SupportsAVX ?
|
|
offsetof(Core::CPUState, xmm.avx.data[XMM][0]) :
|
|
offsetof(Core::CPUState, xmm.sse.data[XMM][0]);
|
|
|
|
_StoreRegister(Src, false, VectorOffset, FPRClass, FPRFixedClass, VectorSize);
|
|
}
|
|
|
|
OrderedNode *OpDispatchBuilder::LoadSource(FEXCore::IR::RegisterClassType Class, FEXCore::X86Tables::DecodedOp const& Op, FEXCore::X86Tables::DecodedOperand const& Operand, uint32_t Flags, int8_t Align, bool LoadData, bool ForceLoad, MemoryAccessType AccessType, bool AllowUpperGarbage) {
|
|
const uint8_t OpSize = GetSrcSize(Op);
|
|
return LoadSource_WithOpSize(Class, Op, Operand, OpSize, Flags, Align, LoadData, ForceLoad, AccessType, AllowUpperGarbage);
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreResult_WithOpSize(FEXCore::IR::RegisterClassType Class, FEXCore::X86Tables::DecodedOp Op, FEXCore::X86Tables::DecodedOperand const& Operand, OrderedNode *const Src, uint8_t OpSize, int8_t Align, MemoryAccessType AccessType) {
|
|
LOGMAN_THROW_A_FMT(Operand.IsGPR() ||
|
|
Operand.IsLiteral() ||
|
|
Operand.IsGPRDirect() ||
|
|
Operand.IsGPRIndirect() ||
|
|
Operand.IsRIPRelative() ||
|
|
Operand.IsSIB(),
|
|
"Unsupported Dest type");
|
|
|
|
// 8Bit and 16bit destination types store their result without effecting the upper bits
|
|
// 32bit ops ZEXT the result to 64bit
|
|
OrderedNode *MemStoreDst {nullptr};
|
|
bool MemStore = false;
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
const uint32_t AddrSize = (Op->Flags & X86Tables::DecodeFlags::FLAG_ADDRESS_SIZE) != 0 ? (GPRSize >> 1) : GPRSize;
|
|
|
|
if (Operand.IsLiteral()) {
|
|
MemStoreDst = _Constant(Operand.Data.Literal.Size * 8, Operand.Data.Literal.Value);
|
|
MemStore = true; // Literals are ONLY hardcoded memory destinations
|
|
}
|
|
else if (Operand.IsGPR()) {
|
|
const auto gpr = Operand.Data.GPR.GPR;
|
|
if (gpr >= FEXCore::X86State::REG_MM_0) {
|
|
_StoreContext(OpSize, Class, Src, offsetof(FEXCore::Core::CPUState, mm[gpr - FEXCore::X86State::REG_MM_0]));
|
|
}
|
|
else if (gpr >= FEXCore::X86State::REG_XMM_0) {
|
|
const auto gprIndex = gpr - X86State::REG_XMM_0;
|
|
const auto VectorSize = CTX->HostFeatures.SupportsAVX ? 32 : 16;
|
|
|
|
auto Result = Src;
|
|
if (OpSize != VectorSize) {
|
|
// Partial writes can come from FPRs.
|
|
// TODO: Fix the instructions doing partial writes rather than dealing with it here.
|
|
|
|
LOGMAN_THROW_A_FMT(Class != IR::GPRClass, "Partial writes from GPR not allowed. Instruction: {}",
|
|
Op->TableInfo->Name);
|
|
|
|
// OpSize of 16 is special in that it is expected to zero the upper bits of the 256-bit operation.
|
|
// TODO: Longer term we should enforce the difference between zero and insert.
|
|
if (VectorSize == Core::CPUState::XMM_AVX_REG_SIZE && OpSize == Core::CPUState::XMM_SSE_REG_SIZE) {
|
|
Result = _VMov(OpSize, Src);
|
|
} else {
|
|
auto SrcVector = LoadXMMRegister(gprIndex);
|
|
Result = _VInsElement(VectorSize, OpSize, 0, 0, SrcVector, Src);
|
|
}
|
|
}
|
|
|
|
StoreXMMRegister(gprIndex, Result);
|
|
}
|
|
else {
|
|
if (GPRSize == 8 && OpSize == 4) {
|
|
// If the Source IR op is 64 bits, we need to zext the upper bits
|
|
// For all other sizes, the upper bits are guaranteed to already be zero
|
|
OrderedNode *Value = GetOpSize(Src) == 8 ? _Bfe(OpSize::i32Bit, 32, 0, Src) : Src;
|
|
StoreGPRRegister(gpr, Value, GPRSize);
|
|
|
|
LOGMAN_THROW_AA_FMT(!Operand.Data.GPR.HighBits, "Can't handle 32bit store to high 8bit register");
|
|
}
|
|
else {
|
|
LOGMAN_THROW_AA_FMT(!(GPRSize == 4 && OpSize > 4), "Oops had a {} GPR load", OpSize);
|
|
|
|
if (GPRSize != OpSize) {
|
|
// if the GPR isn't the full size then we need to insert.
|
|
// eg:
|
|
// mov al, 2 ; Move in to lower 8-bits.
|
|
// mov ah, 2 ; Move in to upper 8-bits of 16-bit reg.
|
|
// mov ax, 2 ; Move in to lower 16-bits of reg.
|
|
StoreGPRRegister(gpr, Src, OpSize, Operand.Data.GPR.HighBits * 8);
|
|
}
|
|
else {
|
|
StoreGPRRegister(gpr, Src, std::min(GPRSize, OpSize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (Operand.IsGPRDirect()) {
|
|
MemStoreDst = LoadGPRRegister(Operand.Data.GPR.GPR, GPRSize);
|
|
|
|
MemStore = true;
|
|
if (Operand.Data.GPR.GPR == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
else if (Operand.IsGPRIndirect()) {
|
|
auto GPR = LoadGPRRegister(Operand.Data.GPRIndirect.GPR, GPRSize);
|
|
auto Constant = _Constant(GPRSize * 8, Operand.Data.GPRIndirect.Displacement);
|
|
|
|
MemStoreDst = _Add(IR::SizeToOpSize(GPRSize), GPR, Constant);
|
|
MemStore = true;
|
|
if (Operand.Data.GPRIndirect.GPR == FEXCore::X86State::REG_RSP && AccessType == MemoryAccessType::ACCESS_DEFAULT) {
|
|
AccessType = MemoryAccessType::ACCESS_NONTSO;
|
|
}
|
|
}
|
|
else if (Operand.IsRIPRelative()) {
|
|
if (CTX->Config.Is64BitMode) {
|
|
MemStoreDst = GetRelocatedPC(Op, Operand.Data.RIPLiteral.Value.s);
|
|
}
|
|
else {
|
|
// 32bit this isn't RIP relative but instead absolute
|
|
MemStoreDst = _Constant(GPRSize * 8, Operand.Data.RIPLiteral.Value.u);
|
|
}
|
|
MemStore = true;
|
|
}
|
|
else if (Operand.IsSIB()) {
|
|
OrderedNode *Tmp {};
|
|
if (Operand.Data.SIB.Index != FEXCore::X86State::REG_INVALID) {
|
|
Tmp = LoadGPRRegister(Operand.Data.SIB.Index, GPRSize);
|
|
|
|
if (Operand.Data.SIB.Scale != 1) {
|
|
auto Constant = _Constant(GPRSize * 8, Operand.Data.SIB.Scale);
|
|
Tmp = _Mul(IR::SizeToOpSize(GPRSize), Tmp, Constant);
|
|
}
|
|
}
|
|
|
|
if (Operand.Data.SIB.Base != FEXCore::X86State::REG_INVALID) {
|
|
auto GPR = LoadGPRRegister(Operand.Data.SIB.Base, GPRSize);
|
|
|
|
if (Tmp != nullptr) {
|
|
Tmp = _Add(IR::SizeToOpSize(GPRSize), Tmp, GPR);
|
|
}
|
|
else {
|
|
Tmp = GPR;
|
|
}
|
|
}
|
|
|
|
if (Operand.Data.SIB.Offset) {
|
|
if (Tmp != nullptr) {
|
|
MemStoreDst = _Add(IR::SizeToOpSize(GPRSize), Tmp, _Constant(GPRSize * 8, Operand.Data.SIB.Offset));
|
|
}
|
|
else {
|
|
MemStoreDst = _Constant(GPRSize * 8, Operand.Data.SIB.Offset);
|
|
}
|
|
}
|
|
else {
|
|
if (Tmp != nullptr) {
|
|
MemStoreDst = Tmp;
|
|
}
|
|
else {
|
|
MemStoreDst = _Constant(GPRSize * 8, 0);
|
|
}
|
|
}
|
|
|
|
if (AddrSize < GPRSize) {
|
|
// If AddrSize == 16 then we need to clear the upper bits
|
|
// GPRSize will be 32 in this case
|
|
MemStoreDst = _Bfe(IR::SizeToOpSize(std::max<uint8_t>(4u, AddrSize)), AddrSize * 8, 0, MemStoreDst);
|
|
}
|
|
|
|
MemStore = true;
|
|
}
|
|
|
|
if (MemStore) {
|
|
MemStoreDst = AppendSegmentOffset(MemStoreDst, Op->Flags);
|
|
|
|
if (OpSize == 10) {
|
|
// For X87 extended doubles, split before storing
|
|
_StoreMem(FPRClass, 8, MemStoreDst, Src, Align);
|
|
auto Upper = _VExtractToGPR(16, 8, Src, 1);
|
|
auto DestAddr = _Add(OpSize::i64Bit, MemStoreDst, _Constant(8));
|
|
_StoreMem(GPRClass, 2, DestAddr, Upper, std::min<uint8_t>(Align, 8));
|
|
} else {
|
|
if (AccessType == MemoryAccessType::ACCESS_NONTSO || AccessType == MemoryAccessType::ACCESS_STREAM) {
|
|
_StoreMem(Class, OpSize, MemStoreDst, Src, Align == -1 ? OpSize : Align);
|
|
}
|
|
else {
|
|
_StoreMemAutoTSO(Class, OpSize, MemStoreDst, Src, Align == -1 ? OpSize : Align);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreResult(FEXCore::IR::RegisterClassType Class, FEXCore::X86Tables::DecodedOp Op, FEXCore::X86Tables::DecodedOperand const& Operand, OrderedNode *const Src, int8_t Align, MemoryAccessType AccessType) {
|
|
StoreResult_WithOpSize(Class, Op, Operand, Src, GetDstSize(Op), Align, AccessType);
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreResult(FEXCore::IR::RegisterClassType Class, FEXCore::X86Tables::DecodedOp Op, OrderedNode *const Src, int8_t Align, MemoryAccessType AccessType) {
|
|
StoreResult(Class, Op, Op->Dest, Src, Align, AccessType);
|
|
}
|
|
|
|
OpDispatchBuilder::OpDispatchBuilder(FEXCore::Context::ContextImpl *ctx)
|
|
: IREmitter {ctx->OpDispatcherAllocator}
|
|
, CTX {ctx} {
|
|
ResetWorkingList();
|
|
InstallHostSpecificOpcodeHandlers();
|
|
}
|
|
OpDispatchBuilder::OpDispatchBuilder(FEXCore::Utils::IntrusivePooledAllocator &Allocator)
|
|
: IREmitter {Allocator}
|
|
, CTX {nullptr} {
|
|
}
|
|
|
|
void OpDispatchBuilder::ResetWorkingList() {
|
|
IREmitter::ResetWorkingList();
|
|
JumpTargets.clear();
|
|
BlockSetRIP = false;
|
|
DecodeFailure = false;
|
|
ShouldDump = false;
|
|
CurrentCodeBlock = nullptr;
|
|
}
|
|
|
|
void OpDispatchBuilder::UnhandledOp(OpcodeArgs) {
|
|
DecodeFailure = true;
|
|
}
|
|
|
|
template<uint32_t SrcIndex>
|
|
void OpDispatchBuilder::MOVGPROp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[SrcIndex], Op->Flags, 1);
|
|
StoreResult(GPRClass, Op, Src, 1);
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVGPRNTOp(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, 1);
|
|
StoreResult(GPRClass, Op, Src, 1, MemoryAccessType::ACCESS_STREAM);
|
|
}
|
|
|
|
void OpDispatchBuilder::ALUOpImpl(OpcodeArgs, FEXCore::IR::IROps ALUIROp, FEXCore::IR::IROps AtomicFetchOp) {
|
|
auto Size = GetDstSize(Op);
|
|
const auto OpSize = Size == 8 ? OpSize::i64Bit : OpSize::i32Bit;
|
|
|
|
// Logical ops can tolerate garbage in the upper bits, so don't mask.
|
|
bool AllowUpperGarbage = ALUIROp == FEXCore::IR::IROps::OP_AND ||
|
|
ALUIROp == FEXCore::IR::IROps::OP_XOR ||
|
|
ALUIROp == FEXCore::IR::IROps::OP_OR;
|
|
|
|
// X86 basic ALU ops just do the operation between the destination and a single source
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1, true, false, MemoryAccessType::ACCESS_DEFAULT, AllowUpperGarbage);
|
|
|
|
OrderedNode *Result{};
|
|
OrderedNode *Dest{};
|
|
|
|
if (DestIsLockedMem(Op)) {
|
|
HandledLock = true;
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
|
|
auto FetchOp = _AtomicFetchAdd(IR::SizeToOpSize(Size), Src, DestMem);
|
|
// Overwrite our atomic op type
|
|
FetchOp.first->Header.Op = AtomicFetchOp;
|
|
Dest = FetchOp;
|
|
|
|
auto ALUOp = _Add(OpSize, Dest, Src);
|
|
// Overwrite our IR's op type
|
|
ALUOp.first->Header.Op = ALUIROp;
|
|
|
|
Result = ALUOp;
|
|
}
|
|
else {
|
|
Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, true, false, MemoryAccessType::ACCESS_DEFAULT, AllowUpperGarbage);
|
|
|
|
/* On x86, the canonical way to zero a register is XOR with itself...
|
|
* because modern x86 detects this pattern in hardware. arm64 does not
|
|
* detect this pattern, we should do it like the x86 hardware would. On
|
|
* arm64, "mov x0, #0" is faster than "eor x0, x0, x0". Additionally this
|
|
* lets more constant folding kick in for flags.
|
|
*/
|
|
if (ALUIROp == FEXCore::IR::IROps::OP_XOR &&
|
|
Op->Dest.IsGPR() && Op->Src[0].IsGPR() &&
|
|
Op->Dest.Data.GPR == Op->Src[0].Data.GPR) {
|
|
|
|
Result = _Constant(0);
|
|
} else {
|
|
auto ALUOp = _Add(OpSize, Dest, Src);
|
|
// Overwrite our IR's op type
|
|
ALUOp.first->Header.Op = ALUIROp;
|
|
|
|
Result = ALUOp;
|
|
}
|
|
|
|
StoreResult(GPRClass, Op, Result, -1);
|
|
}
|
|
|
|
// Flags set
|
|
{
|
|
switch (ALUIROp) {
|
|
case FEXCore::IR::IROps::OP_ADD:
|
|
GenerateFlags_ADD(Op, Result, Dest, Src);
|
|
break;
|
|
case FEXCore::IR::IROps::OP_SUB:
|
|
GenerateFlags_SUB(Op, Result, Dest, Src);
|
|
break;
|
|
case FEXCore::IR::IROps::OP_AND:
|
|
case FEXCore::IR::IROps::OP_XOR:
|
|
case FEXCore::IR::IROps::OP_OR: {
|
|
GenerateFlags_Logical(Op, Result, Dest, Src);
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<FEXCore::IR::IROps ALUIROp, FEXCore::IR::IROps AtomicFetchOp>
|
|
void OpDispatchBuilder::ALUOp(OpcodeArgs) {
|
|
ALUOpImpl(Op, ALUIROp, AtomicFetchOp);
|
|
}
|
|
|
|
void OpDispatchBuilder::INTOp(OpcodeArgs) {
|
|
IR::BreakDefinition Reason;
|
|
bool SetRIPToNext = false;
|
|
|
|
switch (Op->OP) {
|
|
case 0xCD: { // INT imm8
|
|
uint8_t Literal = Op->Src[0].Data.Literal.Value;
|
|
|
|
#ifndef _WIN32
|
|
constexpr uint8_t SYSCALL_LITERAL = 0x80;
|
|
#else
|
|
constexpr uint8_t SYSCALL_LITERAL = 0x2E;
|
|
#endif
|
|
if (Literal == SYSCALL_LITERAL) {
|
|
// Syscall on linux
|
|
SyscallOp(Op);
|
|
return;
|
|
}
|
|
|
|
Reason.ErrorRegister = Literal << 3 | (0b010);
|
|
Reason.Signal = Core::FAULT_SIGSEGV;
|
|
// GP is raised when task-gate isn't setup to be valid
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_GP;
|
|
Reason.si_code = 0x80;
|
|
break;
|
|
}
|
|
case 0xCE: // INTO
|
|
Reason.ErrorRegister = 0;
|
|
Reason.Signal = Core::FAULT_SIGSEGV;
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_OF;
|
|
Reason.si_code = 0x80;
|
|
break;
|
|
case 0xF1: // INT1
|
|
Reason.ErrorRegister = 0;
|
|
Reason.Signal = Core::FAULT_SIGTRAP;
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_DB;
|
|
Reason.si_code = 1;
|
|
SetRIPToNext = true;
|
|
break;
|
|
case 0xF4: { // HLT
|
|
Reason.ErrorRegister = 0;
|
|
Reason.Signal = Core::FAULT_SIGSEGV;
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_GP;
|
|
Reason.si_code = 0x80;
|
|
break;
|
|
}
|
|
case 0x0B: // UD2
|
|
Reason.ErrorRegister = 0;
|
|
Reason.Signal = Core::FAULT_SIGILL;
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_UD;
|
|
Reason.si_code = 2;
|
|
break;
|
|
case 0xCC: // INT3
|
|
Reason.ErrorRegister = 0;
|
|
Reason.Signal = Core::FAULT_SIGTRAP;
|
|
Reason.TrapNumber = X86State::X86_TRAPNO_BP;
|
|
Reason.si_code = 0x80;
|
|
SetRIPToNext = true;
|
|
break;
|
|
}
|
|
|
|
// Calculate flags early.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
if (SetRIPToNext) {
|
|
BlockSetRIP = SetRIPToNext;
|
|
|
|
// We want to set RIP to the next instruction after INT3/INT1
|
|
auto NewRIP = GetRelocatedPC(Op);
|
|
_StoreContext(GPRSize, GPRClass, NewRIP, offsetof(FEXCore::Core::CPUState, rip));
|
|
}
|
|
else if (Op->OP != 0xCE) {
|
|
auto NewRIP = GetRelocatedPC(Op, -Op->InstSize);
|
|
_StoreContext(GPRSize, GPRClass, NewRIP, offsetof(FEXCore::Core::CPUState, rip));
|
|
}
|
|
|
|
if (Op->OP == 0xCE) { // Conditional to only break if Overflow == 1
|
|
auto Flag = GetRFLAG(FEXCore::X86State::RFLAG_OF_LOC);
|
|
CalculateDeferredFlags();
|
|
|
|
// If condition doesn't hold then keep going
|
|
auto CondJump = _CondJump(Flag, {COND_EQ});
|
|
auto FalseBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetFalseJumpTarget(CondJump, FalseBlock);
|
|
SetCurrentCodeBlock(FalseBlock);
|
|
StartNewBlock();
|
|
|
|
auto NewRIP = GetRelocatedPC(Op);
|
|
_StoreContext(GPRSize, GPRClass, NewRIP, offsetof(FEXCore::Core::CPUState, rip));
|
|
_Break(Reason);
|
|
|
|
// Make sure to start a new block after ending this one
|
|
auto JumpTarget = CreateNewCodeBlockAfter(FalseBlock);
|
|
SetTrueJumpTarget(CondJump, JumpTarget);
|
|
SetCurrentCodeBlock(JumpTarget);
|
|
StartNewBlock();
|
|
}
|
|
else {
|
|
BlockSetRIP = true;
|
|
_Break(Reason);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::TZCNT(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
Src = _FindTrailingZeroes(OpSizeFromSrc(Op), Src);
|
|
StoreResult(GPRClass, Op, Src, -1);
|
|
|
|
GenerateFlags_TZCNT(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::LZCNT(OpcodeArgs) {
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1);
|
|
|
|
auto Res = _CountLeadingZeroes(OpSizeFromSrc(Op), Src);
|
|
StoreResult(GPRClass, Op, Res, -1);
|
|
GenerateFlags_LZCNT(Op, Src);
|
|
}
|
|
|
|
void OpDispatchBuilder::MOVBEOp(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
const auto SrcSize = GetSrcSize(Op);
|
|
|
|
OrderedNode *Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, 1);
|
|
Src = _Rev(IR::SizeToOpSize(std::max<uint8_t>(4u, SrcSize)), Src);
|
|
|
|
if (SrcSize == 2) {
|
|
// 16-bit does an insert.
|
|
// Rev of 16-bit value as 32-bit replaces the result in the upper 16-bits of the result.
|
|
// bfxil the 16-bit result in to the GPR.
|
|
OrderedNode *Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, GPRSize, Op->Flags, -1);
|
|
auto Result = _Bfxil(IR::SizeToOpSize(GPRSize), 16, 16, Dest, Src);
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Result, GPRSize, -1);
|
|
}
|
|
else {
|
|
// 32-bit does regular zext
|
|
StoreResult(GPRClass, Op, Op->Dest, Src, -1);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CLWB(OpcodeArgs) {
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
_CacheLineClean(DestMem);
|
|
}
|
|
|
|
void OpDispatchBuilder::CLFLUSHOPT(OpcodeArgs) {
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
_CacheLineClear(DestMem, false);
|
|
}
|
|
|
|
void OpDispatchBuilder::LoadFenceOrXRSTOR(OpcodeArgs) {
|
|
// 0xE8 signifies LFENCE
|
|
if (Op->ModRM == 0xE8) {
|
|
_Fence(IR::Fence_Load);
|
|
} else {
|
|
XRstorOpImpl(Op);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::MemFenceOrXSAVEOPT(OpcodeArgs) {
|
|
if (Op->ModRM == 0xF0) {
|
|
// 0xF0 is MFENCE
|
|
_Fence(FEXCore::IR::Fence_LoadStore);
|
|
}
|
|
else {
|
|
LogMan::Msg::EFmt("Application tried using XSAVEOPT");
|
|
UnimplementedOp(Op);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::StoreFenceOrCLFlush(OpcodeArgs) {
|
|
if (Op->ModRM == 0xF8) {
|
|
// 0xF8 is SFENCE
|
|
_Fence({FEXCore::IR::Fence_Store});
|
|
}
|
|
else {
|
|
// This is a CLFlush
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1, false);
|
|
DestMem = AppendSegmentOffset(DestMem, Op->Flags);
|
|
_CacheLineClear(DestMem, true);
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::CLZeroOp(OpcodeArgs) {
|
|
OrderedNode *DestMem = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1, false);
|
|
_CacheLineZero(DestMem);
|
|
}
|
|
|
|
void OpDispatchBuilder::RDTSCPOp(OpcodeArgs) {
|
|
// RDTSCP is slightly different than RDTSC
|
|
// IA32_TSC_AUX is returned in RCX
|
|
// All previous loads are globally visible
|
|
// - Explicitly does not wait for stores to be globally visible
|
|
// - Explicitly use an MFENCE before this instruction if you want this behaviour
|
|
// This instruction is not an execution fence, so subsequent instructions can execute after this
|
|
// - Explicitly use an LFENCE after RDTSCP if you want to block this behaviour
|
|
|
|
_Fence({FEXCore::IR::Fence_Load});
|
|
auto Counter = _CycleCounter();
|
|
auto CounterLow = _Bfe(OpSize::i64Bit, 32, 0, Counter);
|
|
auto CounterHigh = _Bfe(OpSize::i64Bit, 32, 32, Counter);
|
|
auto ID = _ProcessorID();
|
|
StoreGPRRegister(X86State::REG_RAX, CounterLow);
|
|
StoreGPRRegister(X86State::REG_RCX, ID);
|
|
StoreGPRRegister(X86State::REG_RDX, CounterHigh);
|
|
}
|
|
|
|
void OpDispatchBuilder::CRC32(OpcodeArgs) {
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// Destination GPR size is always 4 or 8 bytes depending on widening
|
|
uint8_t DstSize = Op->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_REX_WIDENING ? 8 : 4;
|
|
OrderedNode *Dest = LoadSource_WithOpSize(GPRClass, Op, Op->Dest, GPRSize, Op->Flags, -1);
|
|
|
|
// Incoming memory is 8, 16, 32, or 64
|
|
OrderedNode *Src{};
|
|
if (Op->Src[0].IsGPR()) {
|
|
Src = LoadSource_WithOpSize(GPRClass, Op, Op->Src[0], GPRSize, Op->Flags, -1);
|
|
}
|
|
else {
|
|
Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, 1);
|
|
}
|
|
auto Result = _CRC32(Dest, Src, GetSrcSize(Op));
|
|
StoreResult_WithOpSize(GPRClass, Op, Op->Dest, Result, DstSize, -1);
|
|
}
|
|
|
|
template<bool Reseed>
|
|
void OpDispatchBuilder::RDRANDOp(OpcodeArgs) {
|
|
auto Res = _RDRAND(Reseed);
|
|
|
|
OrderedNode *Result_Lower = _ExtractElementPair(OpSize::i64Bit, Res, 0);
|
|
OrderedNode *Result_Upper = _ExtractElementPair(OpSize::i64Bit, Res, 1);
|
|
|
|
StoreResult(GPRClass, Op, Result_Lower, -1);
|
|
GenerateFlags_RDRAND(Op, Result_Upper);
|
|
}
|
|
|
|
void OpDispatchBuilder::UnimplementedOp(OpcodeArgs) {
|
|
// Ensure flags are calculated on invalid op.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// We don't actually support this instruction
|
|
// Multiblock may hit it though
|
|
_StoreContext(GPRSize, GPRClass, GetRelocatedPC(Op, -Op->InstSize), offsetof(FEXCore::Core::CPUState, rip));
|
|
_Break(FEXCore::IR::BreakDefinition {
|
|
.ErrorRegister = 0,
|
|
.Signal = SIGILL,
|
|
.TrapNumber = 0,
|
|
.si_code = 0,
|
|
});
|
|
|
|
BlockSetRIP = true;
|
|
|
|
if (Multiblock) {
|
|
auto NextBlock = CreateNewCodeBlockAfter(GetCurrentBlock());
|
|
SetCurrentCodeBlock(NextBlock);
|
|
StartNewBlock();
|
|
}
|
|
}
|
|
|
|
void OpDispatchBuilder::InvalidOp(OpcodeArgs) {
|
|
// Ensure flags are calculated on invalid op.
|
|
CalculateDeferredFlags();
|
|
|
|
const uint8_t GPRSize = CTX->GetGPRSize();
|
|
|
|
// We don't actually support this instruction
|
|
// Multiblock may hit it though
|
|
_StoreContext(GPRSize, GPRClass, GetRelocatedPC(Op, -Op->InstSize), offsetof(FEXCore::Core::CPUState, rip));
|
|
_Break(FEXCore::IR::BreakDefinition {
|
|
.ErrorRegister = 0,
|
|
.Signal = SIGILL,
|
|
.TrapNumber = 0,
|
|
.si_code = 0,
|
|
});
|
|
BlockSetRIP = true;
|
|
}
|
|
|
|
#undef OpcodeArgs
|
|
|
|
|
|
void OpDispatchBuilder::InstallHostSpecificOpcodeHandlers() {
|
|
static bool Initialized = false;
|
|
if (!CTX || Initialized) {
|
|
// IRCompaction doesn't set a CTX and doesn't need this anyway
|
|
return;
|
|
}
|
|
#define OPD(prefix, opcode) (((prefix) << 8) | opcode)
|
|
constexpr uint16_t PF_38_NONE = 0;
|
|
constexpr uint16_t PF_38_66 = (1U << 0);
|
|
constexpr uint16_t PF_38_F2 = (1U << 1);
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F38_SHA[] = {
|
|
{OPD(PF_38_NONE, 0xC8), 1, &OpDispatchBuilder::SHA1NEXTEOp},
|
|
{OPD(PF_38_NONE, 0xC9), 1, &OpDispatchBuilder::SHA1MSG1Op},
|
|
{OPD(PF_38_NONE, 0xCA), 1, &OpDispatchBuilder::SHA1MSG2Op},
|
|
{OPD(PF_38_NONE, 0xCB), 1, &OpDispatchBuilder::SHA256RNDS2Op},
|
|
{OPD(PF_38_NONE, 0xCC), 1, &OpDispatchBuilder::SHA256MSG1Op},
|
|
{OPD(PF_38_NONE, 0xCD), 1, &OpDispatchBuilder::SHA256MSG2Op},
|
|
};
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F38_AES[] = {
|
|
{OPD(PF_38_66, 0xDB), 1, &OpDispatchBuilder::AESImcOp},
|
|
{OPD(PF_38_66, 0xDC), 1, &OpDispatchBuilder::AESEncOp},
|
|
{OPD(PF_38_66, 0xDD), 1, &OpDispatchBuilder::AESEncLastOp},
|
|
{OPD(PF_38_66, 0xDE), 1, &OpDispatchBuilder::AESDecOp},
|
|
{OPD(PF_38_66, 0xDF), 1, &OpDispatchBuilder::AESDecLastOp},
|
|
};
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F38_CRC[] = {
|
|
{OPD(PF_38_F2, 0xF0), 1, &OpDispatchBuilder::CRC32},
|
|
{OPD(PF_38_F2, 0xF1), 1, &OpDispatchBuilder::CRC32},
|
|
|
|
{OPD(PF_38_66 | PF_38_F2, 0xF0), 1, &OpDispatchBuilder::CRC32},
|
|
{OPD(PF_38_66 | PF_38_F2, 0xF1), 1, &OpDispatchBuilder::CRC32},
|
|
};
|
|
#undef OPD
|
|
|
|
#define OPD(REX, prefix, opcode) ((REX << 9) | (prefix << 8) | opcode)
|
|
#define PF_3A_NONE 0
|
|
#define PF_3A_66 1
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F3A_AES[] = {
|
|
{OPD(0, PF_3A_66, 0xDF), 1, &OpDispatchBuilder::AESKeyGenAssist},
|
|
};
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F3A_PCLMUL[] = {
|
|
{OPD(0, PF_3A_66, 0x44), 1, &OpDispatchBuilder::PCLMULQDQOp},
|
|
};
|
|
|
|
#undef PF_3A_NONE
|
|
#undef PF_3A_66
|
|
#undef OPD
|
|
|
|
#define OPD(map_select, pp, opcode) (((map_select - 1) << 10) | (pp << 8) | (opcode))
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> VEX_PCLMUL[] = {
|
|
{OPD(3, 0b01, 0x44), 1, &OpDispatchBuilder::VPCLMULQDQOp},
|
|
};
|
|
#undef OPD
|
|
|
|
#define OPD(group, prefix, Reg) (((group - FEXCore::X86Tables::TYPE_GROUP_6) << 5) | (prefix) << 3 | (Reg))
|
|
constexpr uint16_t PF_NONE = 0;
|
|
constexpr uint16_t PF_66 = 2;
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> SecondaryExtensionOp_RDRAND[] = {
|
|
// GROUP 9
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_NONE, 6), 1, &OpDispatchBuilder::RDRANDOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_NONE, 7), 1, &OpDispatchBuilder::RDRANDOp<true>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_66, 6), 1, &OpDispatchBuilder::RDRANDOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_66, 7), 1, &OpDispatchBuilder::RDRANDOp<true>},
|
|
};
|
|
#undef OPD
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> SecondaryModRMExtensionOp_CLZero[] = {
|
|
{((3 << 3) | 4), 1, &OpDispatchBuilder::CLZeroOp},
|
|
};
|
|
|
|
#define OPD(map_select, pp, opcode) (((map_select - 1) << 10) | (pp << 8) | (opcode))
|
|
static constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> AVXTable[] = {
|
|
{OPD(1, 0b00, 0x10), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{OPD(1, 0b01, 0x10), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{OPD(1, 0b10, 0x10), 1, &OpDispatchBuilder::VMOVSSOp},
|
|
{OPD(1, 0b11, 0x10), 1, &OpDispatchBuilder::VMOVSDOp},
|
|
{OPD(1, 0b00, 0x11), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{OPD(1, 0b01, 0x11), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{OPD(1, 0b10, 0x11), 1, &OpDispatchBuilder::VMOVSSOp},
|
|
{OPD(1, 0b11, 0x11), 1, &OpDispatchBuilder::VMOVSDOp},
|
|
|
|
{OPD(1, 0b00, 0x12), 1, &OpDispatchBuilder::VMOVLPOp},
|
|
{OPD(1, 0b01, 0x12), 1, &OpDispatchBuilder::VMOVLPOp},
|
|
{OPD(1, 0b10, 0x12), 1, &OpDispatchBuilder::VMOVSLDUPOp},
|
|
{OPD(1, 0b11, 0x12), 1, &OpDispatchBuilder::VMOVDDUPOp},
|
|
{OPD(1, 0b00, 0x13), 1, &OpDispatchBuilder::VMOVLPOp},
|
|
{OPD(1, 0b01, 0x13), 1, &OpDispatchBuilder::VMOVLPOp},
|
|
|
|
{OPD(1, 0b00, 0x14), 1, &OpDispatchBuilder::VPUNPCKLOp<4>},
|
|
{OPD(1, 0b01, 0x14), 1, &OpDispatchBuilder::VPUNPCKLOp<8>},
|
|
|
|
{OPD(1, 0b00, 0x15), 1, &OpDispatchBuilder::VPUNPCKHOp<4>},
|
|
{OPD(1, 0b01, 0x15), 1, &OpDispatchBuilder::VPUNPCKHOp<8>},
|
|
|
|
{OPD(1, 0b00, 0x16), 1, &OpDispatchBuilder::VMOVHPOp},
|
|
{OPD(1, 0b01, 0x16), 1, &OpDispatchBuilder::VMOVHPOp},
|
|
{OPD(1, 0b10, 0x16), 1, &OpDispatchBuilder::VMOVSHDUPOp},
|
|
{OPD(1, 0b00, 0x17), 1, &OpDispatchBuilder::VMOVHPOp},
|
|
{OPD(1, 0b01, 0x17), 1, &OpDispatchBuilder::VMOVHPOp},
|
|
|
|
{OPD(1, 0b00, 0x28), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{OPD(1, 0b01, 0x28), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{OPD(1, 0b00, 0x29), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{OPD(1, 0b01, 0x29), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
|
|
{OPD(1, 0b10, 0x2A), 1, &OpDispatchBuilder::AVXCVTGPR_To_FPR<4>},
|
|
{OPD(1, 0b11, 0x2A), 1, &OpDispatchBuilder::AVXCVTGPR_To_FPR<8>},
|
|
|
|
{OPD(1, 0b00, 0x2B), 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{OPD(1, 0b01, 0x2B), 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
|
|
{OPD(1, 0b10, 0x2C), 1, &OpDispatchBuilder::CVTFPR_To_GPR<4, false>},
|
|
{OPD(1, 0b11, 0x2C), 1, &OpDispatchBuilder::CVTFPR_To_GPR<8, false>},
|
|
|
|
{OPD(1, 0b10, 0x2D), 1, &OpDispatchBuilder::CVTFPR_To_GPR<4, true>},
|
|
{OPD(1, 0b11, 0x2D), 1, &OpDispatchBuilder::CVTFPR_To_GPR<8, true>},
|
|
|
|
{OPD(1, 0b00, 0x2E), 1, &OpDispatchBuilder::UCOMISxOp<4>},
|
|
{OPD(1, 0b01, 0x2E), 1, &OpDispatchBuilder::UCOMISxOp<8>},
|
|
{OPD(1, 0b00, 0x2F), 1, &OpDispatchBuilder::UCOMISxOp<4>},
|
|
{OPD(1, 0b01, 0x2F), 1, &OpDispatchBuilder::UCOMISxOp<8>},
|
|
|
|
{OPD(1, 0b00, 0x50), 1, &OpDispatchBuilder::MOVMSKOp<4>},
|
|
{OPD(1, 0b01, 0x50), 1, &OpDispatchBuilder::MOVMSKOp<8>},
|
|
|
|
{OPD(1, 0b00, 0x51), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFSQRT, 4, false>},
|
|
{OPD(1, 0b01, 0x51), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFSQRT, 8, false>},
|
|
{OPD(1, 0b10, 0x51), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFSQRT, 4, true>},
|
|
{OPD(1, 0b11, 0x51), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFSQRT, 8, true>},
|
|
|
|
{OPD(1, 0b00, 0x52), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFRSQRT, 4, false>},
|
|
{OPD(1, 0b10, 0x52), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFRSQRT, 4, true>},
|
|
|
|
{OPD(1, 0b00, 0x53), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFRECP, 4, false>},
|
|
{OPD(1, 0b10, 0x53), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VFRECP, 4, true>},
|
|
|
|
{OPD(1, 0b00, 0x54), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VAND, 16>},
|
|
{OPD(1, 0b01, 0x54), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VAND, 16>},
|
|
|
|
{OPD(1, 0b00, 0x55), 1, &OpDispatchBuilder::VANDNOp},
|
|
{OPD(1, 0b01, 0x55), 1, &OpDispatchBuilder::VANDNOp},
|
|
|
|
{OPD(1, 0b00, 0x56), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VOR, 16>},
|
|
{OPD(1, 0b01, 0x56), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VOR, 16>},
|
|
|
|
{OPD(1, 0b00, 0x57), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VXOR, 16>},
|
|
{OPD(1, 0b01, 0x57), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VXOR, 16>},
|
|
|
|
{OPD(1, 0b00, 0x58), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFADD, 4>},
|
|
{OPD(1, 0b01, 0x58), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFADD, 8>},
|
|
{OPD(1, 0b10, 0x58), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFADD, 4>},
|
|
{OPD(1, 0b11, 0x58), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFADD, 8>},
|
|
|
|
{OPD(1, 0b00, 0x59), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMUL, 4>},
|
|
{OPD(1, 0b01, 0x59), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMUL, 8>},
|
|
{OPD(1, 0b10, 0x59), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMUL, 4>},
|
|
{OPD(1, 0b11, 0x59), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMUL, 8>},
|
|
|
|
{OPD(1, 0b00, 0x5A), 1, &OpDispatchBuilder::Vector_CVT_Float_To_Float<8, 4>},
|
|
{OPD(1, 0b01, 0x5A), 1, &OpDispatchBuilder::Vector_CVT_Float_To_Float<4, 8>},
|
|
{OPD(1, 0b10, 0x5A), 1, &OpDispatchBuilder::AVXScalar_CVT_Float_To_Float<8, 4>},
|
|
{OPD(1, 0b11, 0x5A), 1, &OpDispatchBuilder::AVXScalar_CVT_Float_To_Float<4, 8>},
|
|
|
|
{OPD(1, 0b00, 0x5B), 1, &OpDispatchBuilder::AVXVector_CVT_Int_To_Float<4, false>},
|
|
{OPD(1, 0b01, 0x5B), 1, &OpDispatchBuilder::AVXVector_CVT_Float_To_Int<4, false, true>},
|
|
{OPD(1, 0b10, 0x5B), 1, &OpDispatchBuilder::AVXVector_CVT_Float_To_Int<4, false, false>},
|
|
|
|
{OPD(1, 0b00, 0x5C), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFSUB, 4>},
|
|
{OPD(1, 0b01, 0x5C), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFSUB, 8>},
|
|
{OPD(1, 0b10, 0x5C), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFSUB, 4>},
|
|
{OPD(1, 0b11, 0x5C), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFSUB, 8>},
|
|
|
|
{OPD(1, 0b00, 0x5D), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMIN, 4>},
|
|
{OPD(1, 0b01, 0x5D), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMIN, 8>},
|
|
{OPD(1, 0b10, 0x5D), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMIN, 4>},
|
|
{OPD(1, 0b11, 0x5D), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMIN, 8>},
|
|
|
|
{OPD(1, 0b00, 0x5E), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFDIV, 4>},
|
|
{OPD(1, 0b01, 0x5E), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFDIV, 8>},
|
|
{OPD(1, 0b10, 0x5E), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFDIV, 4>},
|
|
{OPD(1, 0b11, 0x5E), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFDIV, 8>},
|
|
|
|
{OPD(1, 0b00, 0x5F), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMAX, 4>},
|
|
{OPD(1, 0b01, 0x5F), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VFMAX, 8>},
|
|
{OPD(1, 0b10, 0x5F), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMAX, 4>},
|
|
{OPD(1, 0b11, 0x5F), 1, &OpDispatchBuilder::AVXVectorScalarALUOp<IR::OP_VFMAX, 8>},
|
|
|
|
{OPD(1, 0b01, 0x60), 1, &OpDispatchBuilder::VPUNPCKLOp<1>},
|
|
{OPD(1, 0b01, 0x61), 1, &OpDispatchBuilder::VPUNPCKLOp<2>},
|
|
{OPD(1, 0b01, 0x62), 1, &OpDispatchBuilder::VPUNPCKLOp<4>},
|
|
{OPD(1, 0b01, 0x63), 1, &OpDispatchBuilder::VPACKSSOp<2>},
|
|
{OPD(1, 0b01, 0x64), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPGT, 1>},
|
|
{OPD(1, 0b01, 0x65), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPGT, 2>},
|
|
{OPD(1, 0b01, 0x66), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPGT, 4>},
|
|
{OPD(1, 0b01, 0x67), 1, &OpDispatchBuilder::VPACKUSOp<2>},
|
|
{OPD(1, 0b01, 0x68), 1, &OpDispatchBuilder::VPUNPCKHOp<1>},
|
|
{OPD(1, 0b01, 0x69), 1, &OpDispatchBuilder::VPUNPCKHOp<2>},
|
|
{OPD(1, 0b01, 0x6A), 1, &OpDispatchBuilder::VPUNPCKHOp<4>},
|
|
{OPD(1, 0b01, 0x6B), 1, &OpDispatchBuilder::VPACKSSOp<4>},
|
|
{OPD(1, 0b01, 0x6C), 1, &OpDispatchBuilder::VPUNPCKLOp<8>},
|
|
{OPD(1, 0b01, 0x6D), 1, &OpDispatchBuilder::VPUNPCKHOp<8>},
|
|
{OPD(1, 0b01, 0x6E), 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
|
|
{OPD(1, 0b01, 0x6F), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{OPD(1, 0b10, 0x6F), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
|
|
{OPD(1, 0b01, 0x70), 1, &OpDispatchBuilder::VPSHUFWOp<4, true>},
|
|
{OPD(1, 0b10, 0x70), 1, &OpDispatchBuilder::VPSHUFWOp<2, false>},
|
|
{OPD(1, 0b11, 0x70), 1, &OpDispatchBuilder::VPSHUFWOp<2, true>},
|
|
|
|
{OPD(1, 0b01, 0x74), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPEQ, 1>},
|
|
{OPD(1, 0b01, 0x75), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPEQ, 2>},
|
|
{OPD(1, 0b01, 0x76), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPEQ, 4>},
|
|
|
|
{OPD(1, 0b00, 0x77), 1, &OpDispatchBuilder::VZEROOp},
|
|
|
|
{OPD(1, 0b01, 0x7C), 1, &OpDispatchBuilder::VHADDPOp<IR::OP_VFADDP, 8>},
|
|
{OPD(1, 0b11, 0x7C), 1, &OpDispatchBuilder::VHADDPOp<IR::OP_VFADDP, 4>},
|
|
{OPD(1, 0b01, 0x7D), 1, &OpDispatchBuilder::VHSUBPOp<8>},
|
|
{OPD(1, 0b11, 0x7D), 1, &OpDispatchBuilder::VHSUBPOp<4>},
|
|
|
|
{OPD(1, 0b01, 0x7E), 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
{OPD(1, 0b10, 0x7E), 1, &OpDispatchBuilder::MOVQOp},
|
|
|
|
{OPD(1, 0b01, 0x7F), 1, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{OPD(1, 0b10, 0x7F), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
|
|
{OPD(1, 0b00, 0xC2), 1, &OpDispatchBuilder::AVXVFCMPOp<4, false>},
|
|
{OPD(1, 0b01, 0xC2), 1, &OpDispatchBuilder::AVXVFCMPOp<8, false>},
|
|
{OPD(1, 0b10, 0xC2), 1, &OpDispatchBuilder::AVXVFCMPOp<4, true>},
|
|
{OPD(1, 0b11, 0xC2), 1, &OpDispatchBuilder::AVXVFCMPOp<8, true>},
|
|
|
|
{OPD(1, 0b01, 0xC4), 1, &OpDispatchBuilder::VPINSRWOp},
|
|
{OPD(1, 0b01, 0xC5), 1, &OpDispatchBuilder::PExtrOp<2>},
|
|
|
|
{OPD(1, 0b00, 0xC6), 1, &OpDispatchBuilder::VSHUFOp<4>},
|
|
{OPD(1, 0b01, 0xC6), 1, &OpDispatchBuilder::VSHUFOp<8>},
|
|
|
|
{OPD(1, 0b01, 0xD0), 1, &OpDispatchBuilder::VADDSUBPOp<8>},
|
|
{OPD(1, 0b11, 0xD0), 1, &OpDispatchBuilder::VADDSUBPOp<4>},
|
|
|
|
{OPD(1, 0b01, 0xD1), 1, &OpDispatchBuilder::VPSRLDOp<2>},
|
|
{OPD(1, 0b01, 0xD2), 1, &OpDispatchBuilder::VPSRLDOp<4>},
|
|
{OPD(1, 0b01, 0xD3), 1, &OpDispatchBuilder::VPSRLDOp<8>},
|
|
{OPD(1, 0b01, 0xD4), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VADD, 8>},
|
|
{OPD(1, 0b01, 0xD5), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VMUL, 2>},
|
|
{OPD(1, 0b01, 0xD6), 1, &OpDispatchBuilder::MOVQOp},
|
|
{OPD(1, 0b01, 0xD7), 1, &OpDispatchBuilder::MOVMSKOpOne},
|
|
|
|
{OPD(1, 0b01, 0xD8), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUQSUB, 1>},
|
|
{OPD(1, 0b01, 0xD9), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUQSUB, 2>},
|
|
{OPD(1, 0b01, 0xDA), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMIN, 1>},
|
|
{OPD(1, 0b01, 0xDB), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VAND, 16>},
|
|
{OPD(1, 0b01, 0xDC), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUQADD, 1>},
|
|
{OPD(1, 0b01, 0xDD), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUQADD, 2>},
|
|
{OPD(1, 0b01, 0xDE), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMAX, 1>},
|
|
{OPD(1, 0b01, 0xDF), 1, &OpDispatchBuilder::VANDNOp},
|
|
|
|
{OPD(1, 0b01, 0xE0), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VURAVG, 1>},
|
|
{OPD(1, 0b01, 0xE1), 1, &OpDispatchBuilder::VPSRAOp<2>},
|
|
{OPD(1, 0b01, 0xE2), 1, &OpDispatchBuilder::VPSRAOp<4>},
|
|
{OPD(1, 0b01, 0xE3), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VURAVG, 2>},
|
|
{OPD(1, 0b01, 0xE4), 1, &OpDispatchBuilder::VPMULHWOp<false>},
|
|
{OPD(1, 0b01, 0xE5), 1, &OpDispatchBuilder::VPMULHWOp<true>},
|
|
|
|
{OPD(1, 0b01, 0xE6), 1, &OpDispatchBuilder::AVXVector_CVT_Float_To_Int<8, true, false>},
|
|
{OPD(1, 0b10, 0xE6), 1, &OpDispatchBuilder::AVXVector_CVT_Int_To_Float<4, true>},
|
|
{OPD(1, 0b11, 0xE6), 1, &OpDispatchBuilder::AVXVector_CVT_Float_To_Int<8, true, true>},
|
|
|
|
{OPD(1, 0b01, 0xE7), 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
|
|
{OPD(1, 0b01, 0xE8), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSQSUB, 1>},
|
|
{OPD(1, 0b01, 0xE9), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSQSUB, 2>},
|
|
{OPD(1, 0b01, 0xEA), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMIN, 2>},
|
|
{OPD(1, 0b01, 0xEB), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VOR, 16>},
|
|
{OPD(1, 0b01, 0xEC), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSQADD, 1>},
|
|
{OPD(1, 0b01, 0xED), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSQADD, 2>},
|
|
{OPD(1, 0b01, 0xEE), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMAX, 2>},
|
|
{OPD(1, 0b01, 0xEF), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VXOR, 16>},
|
|
|
|
{OPD(1, 0b11, 0xF0), 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{OPD(1, 0b01, 0xF1), 1, &OpDispatchBuilder::VPSLLOp<2>},
|
|
{OPD(1, 0b01, 0xF2), 1, &OpDispatchBuilder::VPSLLOp<4>},
|
|
{OPD(1, 0b01, 0xF3), 1, &OpDispatchBuilder::VPSLLOp<8>},
|
|
{OPD(1, 0b01, 0xF4), 1, &OpDispatchBuilder::VPMULLOp<4, false>},
|
|
{OPD(1, 0b01, 0xF5), 1, &OpDispatchBuilder::VPMADDWDOp},
|
|
{OPD(1, 0b01, 0xF6), 1, &OpDispatchBuilder::VPSADBWOp},
|
|
{OPD(1, 0b01, 0xF7), 1, &OpDispatchBuilder::MASKMOVOp},
|
|
|
|
{OPD(1, 0b01, 0xF8), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSUB, 1>},
|
|
{OPD(1, 0b01, 0xF9), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSUB, 2>},
|
|
{OPD(1, 0b01, 0xFA), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSUB, 4>},
|
|
{OPD(1, 0b01, 0xFB), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSUB, 8>},
|
|
{OPD(1, 0b01, 0xFC), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VADD, 1>},
|
|
{OPD(1, 0b01, 0xFD), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VADD, 2>},
|
|
{OPD(1, 0b01, 0xFE), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VADD, 4>},
|
|
|
|
{OPD(2, 0b01, 0x00), 1, &OpDispatchBuilder::VPSHUFBOp},
|
|
{OPD(2, 0b01, 0x01), 1, &OpDispatchBuilder::VHADDPOp<IR::OP_VADDP, 2>},
|
|
{OPD(2, 0b01, 0x02), 1, &OpDispatchBuilder::VHADDPOp<IR::OP_VADDP, 4>},
|
|
{OPD(2, 0b01, 0x03), 1, &OpDispatchBuilder::VPHADDSWOp},
|
|
{OPD(2, 0b01, 0x04), 1, &OpDispatchBuilder::VPMADDUBSWOp},
|
|
|
|
{OPD(2, 0b01, 0x05), 1, &OpDispatchBuilder::VPHSUBOp<2>},
|
|
{OPD(2, 0b01, 0x06), 1, &OpDispatchBuilder::VPHSUBOp<4>},
|
|
{OPD(2, 0b01, 0x07), 1, &OpDispatchBuilder::VPHSUBSWOp},
|
|
|
|
{OPD(2, 0b01, 0x08), 1, &OpDispatchBuilder::VPSIGN<1>},
|
|
{OPD(2, 0b01, 0x09), 1, &OpDispatchBuilder::VPSIGN<2>},
|
|
{OPD(2, 0b01, 0x0A), 1, &OpDispatchBuilder::VPSIGN<4>},
|
|
{OPD(2, 0b01, 0x0B), 1, &OpDispatchBuilder::VPMULHRSWOp},
|
|
{OPD(2, 0b01, 0x0C), 1, &OpDispatchBuilder::VPERMILRegOp<4>},
|
|
{OPD(2, 0b01, 0x0D), 1, &OpDispatchBuilder::VPERMILRegOp<8>},
|
|
{OPD(2, 0b01, 0x0E), 1, &OpDispatchBuilder::VTESTPOp<4>},
|
|
{OPD(2, 0b01, 0x0F), 1, &OpDispatchBuilder::VTESTPOp<8>},
|
|
|
|
{OPD(2, 0b01, 0x16), 1, &OpDispatchBuilder::VPERMDOp},
|
|
{OPD(2, 0b01, 0x17), 1, &OpDispatchBuilder::PTestOp},
|
|
{OPD(2, 0b01, 0x18), 1, &OpDispatchBuilder::VBROADCASTOp<4>},
|
|
{OPD(2, 0b01, 0x19), 1, &OpDispatchBuilder::VBROADCASTOp<8>},
|
|
{OPD(2, 0b01, 0x1A), 1, &OpDispatchBuilder::VBROADCASTOp<16>},
|
|
{OPD(2, 0b01, 0x1C), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VABS, 1, false>},
|
|
{OPD(2, 0b01, 0x1D), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VABS, 2, false>},
|
|
{OPD(2, 0b01, 0x1E), 1, &OpDispatchBuilder::AVXVectorUnaryOp<IR::OP_VABS, 4, false>},
|
|
|
|
{OPD(2, 0b01, 0x20), 1, &OpDispatchBuilder::ExtendVectorElements<1, 2, true>},
|
|
{OPD(2, 0b01, 0x21), 1, &OpDispatchBuilder::ExtendVectorElements<1, 4, true>},
|
|
{OPD(2, 0b01, 0x22), 1, &OpDispatchBuilder::ExtendVectorElements<1, 8, true>},
|
|
{OPD(2, 0b01, 0x23), 1, &OpDispatchBuilder::ExtendVectorElements<2, 4, true>},
|
|
{OPD(2, 0b01, 0x24), 1, &OpDispatchBuilder::ExtendVectorElements<2, 8, true>},
|
|
{OPD(2, 0b01, 0x25), 1, &OpDispatchBuilder::ExtendVectorElements<4, 8, true>},
|
|
|
|
{OPD(2, 0b01, 0x28), 1, &OpDispatchBuilder::VPMULLOp<4, true>},
|
|
{OPD(2, 0b01, 0x29), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPEQ, 8>},
|
|
{OPD(2, 0b01, 0x2A), 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{OPD(2, 0b01, 0x2B), 1, &OpDispatchBuilder::VPACKUSOp<4>},
|
|
{OPD(2, 0b01, 0x2C), 1, &OpDispatchBuilder::VMASKMOVOp<4, false>},
|
|
{OPD(2, 0b01, 0x2D), 1, &OpDispatchBuilder::VMASKMOVOp<8, false>},
|
|
{OPD(2, 0b01, 0x2E), 1, &OpDispatchBuilder::VMASKMOVOp<4, true>},
|
|
{OPD(2, 0b01, 0x2F), 1, &OpDispatchBuilder::VMASKMOVOp<8, true>},
|
|
|
|
{OPD(2, 0b01, 0x30), 1, &OpDispatchBuilder::ExtendVectorElements<1, 2, false>},
|
|
{OPD(2, 0b01, 0x31), 1, &OpDispatchBuilder::ExtendVectorElements<1, 4, false>},
|
|
{OPD(2, 0b01, 0x32), 1, &OpDispatchBuilder::ExtendVectorElements<1, 8, false>},
|
|
{OPD(2, 0b01, 0x33), 1, &OpDispatchBuilder::ExtendVectorElements<2, 4, false>},
|
|
{OPD(2, 0b01, 0x34), 1, &OpDispatchBuilder::ExtendVectorElements<2, 8, false>},
|
|
{OPD(2, 0b01, 0x35), 1, &OpDispatchBuilder::ExtendVectorElements<4, 8, false>},
|
|
{OPD(2, 0b01, 0x36), 1, &OpDispatchBuilder::VPERMDOp},
|
|
|
|
{OPD(2, 0b01, 0x37), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VCMPGT, 8>},
|
|
{OPD(2, 0b01, 0x38), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMIN, 1>},
|
|
{OPD(2, 0b01, 0x39), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMIN, 4>},
|
|
{OPD(2, 0b01, 0x3A), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMIN, 2>},
|
|
{OPD(2, 0b01, 0x3B), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMIN, 4>},
|
|
{OPD(2, 0b01, 0x3C), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMAX, 1>},
|
|
{OPD(2, 0b01, 0x3D), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VSMAX, 4>},
|
|
{OPD(2, 0b01, 0x3E), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMAX, 2>},
|
|
{OPD(2, 0b01, 0x3F), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VUMAX, 4>},
|
|
|
|
{OPD(2, 0b01, 0x40), 1, &OpDispatchBuilder::AVXVectorALUOp<IR::OP_VMUL, 4>},
|
|
{OPD(2, 0b01, 0x41), 1, &OpDispatchBuilder::PHMINPOSUWOp},
|
|
{OPD(2, 0b01, 0x45), 1, &OpDispatchBuilder::VPSRLVOp},
|
|
{OPD(2, 0b01, 0x46), 1, &OpDispatchBuilder::VPSRAVDOp},
|
|
{OPD(2, 0b01, 0x47), 1, &OpDispatchBuilder::VPSLLVOp},
|
|
|
|
{OPD(2, 0b01, 0x58), 1, &OpDispatchBuilder::VBROADCASTOp<4>},
|
|
{OPD(2, 0b01, 0x59), 1, &OpDispatchBuilder::VBROADCASTOp<8>},
|
|
{OPD(2, 0b01, 0x5A), 1, &OpDispatchBuilder::VBROADCASTOp<16>},
|
|
|
|
{OPD(2, 0b01, 0x78), 1, &OpDispatchBuilder::VBROADCASTOp<1>},
|
|
{OPD(2, 0b01, 0x79), 1, &OpDispatchBuilder::VBROADCASTOp<2>},
|
|
|
|
{OPD(2, 0b01, 0x8C), 1, &OpDispatchBuilder::VPMASKMOVOp<false>},
|
|
{OPD(2, 0b01, 0x8E), 1, &OpDispatchBuilder::VPMASKMOVOp<true>},
|
|
|
|
{OPD(2, 0b01, 0xDB), 1, &OpDispatchBuilder::AESImcOp},
|
|
{OPD(2, 0b01, 0xDC), 1, &OpDispatchBuilder::VAESEncOp},
|
|
{OPD(2, 0b01, 0xDD), 1, &OpDispatchBuilder::VAESEncLastOp},
|
|
{OPD(2, 0b01, 0xDE), 1, &OpDispatchBuilder::VAESDecOp},
|
|
{OPD(2, 0b01, 0xDF), 1, &OpDispatchBuilder::VAESDecLastOp},
|
|
|
|
{OPD(3, 0b01, 0x00), 1, &OpDispatchBuilder::VPERMQOp},
|
|
{OPD(3, 0b01, 0x01), 1, &OpDispatchBuilder::VPERMQOp},
|
|
{OPD(3, 0b01, 0x02), 1, &OpDispatchBuilder::VPBLENDDOp},
|
|
{OPD(3, 0b01, 0x04), 1, &OpDispatchBuilder::VPERMILImmOp<4>},
|
|
{OPD(3, 0b01, 0x05), 1, &OpDispatchBuilder::VPERMILImmOp<8>},
|
|
{OPD(3, 0b01, 0x06), 1, &OpDispatchBuilder::VPERM2Op},
|
|
{OPD(3, 0b01, 0x08), 1, &OpDispatchBuilder::AVXVectorRound<4, false>},
|
|
{OPD(3, 0b01, 0x09), 1, &OpDispatchBuilder::AVXVectorRound<8, false>},
|
|
{OPD(3, 0b01, 0x0A), 1, &OpDispatchBuilder::AVXVectorRound<4, true>},
|
|
{OPD(3, 0b01, 0x0B), 1, &OpDispatchBuilder::AVXVectorRound<8, true>},
|
|
{OPD(3, 0b01, 0x0C), 1, &OpDispatchBuilder::VPBLENDDOp},
|
|
{OPD(3, 0b01, 0x0D), 1, &OpDispatchBuilder::VBLENDPDOp},
|
|
{OPD(3, 0b01, 0x0E), 1, &OpDispatchBuilder::VPBLENDWOp},
|
|
{OPD(3, 0b01, 0x0F), 1, &OpDispatchBuilder::VPALIGNROp},
|
|
|
|
{OPD(3, 0b01, 0x14), 1, &OpDispatchBuilder::PExtrOp<1>},
|
|
{OPD(3, 0b01, 0x15), 1, &OpDispatchBuilder::PExtrOp<2>},
|
|
{OPD(3, 0b01, 0x16), 1, &OpDispatchBuilder::PExtrOp<4>},
|
|
{OPD(3, 0b01, 0x17), 1, &OpDispatchBuilder::PExtrOp<4>},
|
|
|
|
{OPD(3, 0b01, 0x18), 1, &OpDispatchBuilder::VINSERTOp},
|
|
{OPD(3, 0b01, 0x19), 1, &OpDispatchBuilder::VEXTRACT128Op},
|
|
{OPD(3, 0b01, 0x20), 1, &OpDispatchBuilder::VPINSRBOp},
|
|
{OPD(3, 0b01, 0x21), 1, &OpDispatchBuilder::VINSERTPSOp},
|
|
{OPD(3, 0b01, 0x22), 1, &OpDispatchBuilder::VPINSRDQOp},
|
|
|
|
{OPD(3, 0b01, 0x38), 1, &OpDispatchBuilder::VINSERTOp},
|
|
{OPD(3, 0b01, 0x39), 1, &OpDispatchBuilder::VEXTRACT128Op},
|
|
|
|
{OPD(3, 0b01, 0x40), 1, &OpDispatchBuilder::VDPPOp<4>},
|
|
{OPD(3, 0b01, 0x41), 1, &OpDispatchBuilder::VDPPOp<8>},
|
|
{OPD(3, 0b01, 0x42), 1, &OpDispatchBuilder::VMPSADBWOp},
|
|
|
|
{OPD(3, 0b01, 0x46), 1, &OpDispatchBuilder::VPERM2Op},
|
|
|
|
{OPD(3, 0b01, 0x4A), 1, &OpDispatchBuilder::AVXVectorVariableBlend<4>},
|
|
{OPD(3, 0b01, 0x4B), 1, &OpDispatchBuilder::AVXVectorVariableBlend<8>},
|
|
{OPD(3, 0b01, 0x4C), 1, &OpDispatchBuilder::AVXVectorVariableBlend<1>},
|
|
|
|
{OPD(3, 0b01, 0x60), 1, &OpDispatchBuilder::VPCMPESTRMOp},
|
|
{OPD(3, 0b01, 0x61), 1, &OpDispatchBuilder::VPCMPESTRIOp},
|
|
{OPD(3, 0b01, 0x62), 1, &OpDispatchBuilder::VPCMPISTRMOp},
|
|
{OPD(3, 0b01, 0x63), 1, &OpDispatchBuilder::VPCMPISTRIOp},
|
|
|
|
{OPD(3, 0b01, 0xDF), 1, &OpDispatchBuilder::AESKeyGenAssist},
|
|
};
|
|
#undef OPD
|
|
|
|
#define OPD(group, pp, opcode) (((group - X86Tables::TYPE_VEX_GROUP_12) << 4) | (pp << 3) | (opcode))
|
|
static constexpr std::tuple<uint8_t, uint8_t, X86Tables::OpDispatchPtr> VEXTableGroupOps[] {
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_12, 1, 0b010), 1, &OpDispatchBuilder::VPSRLIOp<2>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_12, 1, 0b110), 1, &OpDispatchBuilder::VPSLLIOp<2>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_12, 1, 0b100), 1, &OpDispatchBuilder::VPSRAIOp<2>},
|
|
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_13, 1, 0b010), 1, &OpDispatchBuilder::VPSRLIOp<4>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_13, 1, 0b110), 1, &OpDispatchBuilder::VPSLLIOp<4>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_13, 1, 0b100), 1, &OpDispatchBuilder::VPSRAIOp<4>},
|
|
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_14, 1, 0b010), 1, &OpDispatchBuilder::VPSRLIOp<8>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_14, 1, 0b011), 1, &OpDispatchBuilder::VPSRLDQOp},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_14, 1, 0b110), 1, &OpDispatchBuilder::VPSLLIOp<8>},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_14, 1, 0b111), 1, &OpDispatchBuilder::VPSLLDQOp},
|
|
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_15, 0, 0b010), 1, &OpDispatchBuilder::LDMXCSR},
|
|
{OPD(X86Tables::TYPE_VEX_GROUP_15, 0, 0b011), 1, &OpDispatchBuilder::STMXCSR},
|
|
};
|
|
#undef OPD
|
|
|
|
auto InstallToTable = [](auto& FinalTable, auto& LocalTable) {
|
|
for (auto Op : LocalTable) {
|
|
auto OpNum = std::get<0>(Op);
|
|
auto Dispatcher = std::get<2>(Op);
|
|
for (uint8_t i = 0; i < std::get<1>(Op); ++i) {
|
|
LOGMAN_THROW_A_FMT(FinalTable[OpNum + i].OpcodeDispatcher == nullptr, "Duplicate Entry");
|
|
FinalTable[OpNum + i].OpcodeDispatcher = Dispatcher;
|
|
}
|
|
}
|
|
};
|
|
|
|
if (CTX->HostFeatures.SupportsCRC) {
|
|
InstallToTable(FEXCore::X86Tables::H0F38TableOps, H0F38_CRC);
|
|
}
|
|
|
|
InstallToTable(FEXCore::X86Tables::H0F38TableOps, H0F38_SHA);
|
|
|
|
if (CTX->HostFeatures.SupportsAES) {
|
|
InstallToTable(FEXCore::X86Tables::H0F38TableOps, H0F38_AES);
|
|
InstallToTable(FEXCore::X86Tables::H0F3ATableOps, H0F3A_AES);
|
|
}
|
|
|
|
if (CTX->HostFeatures.SupportsCLZERO) {
|
|
InstallToTable(FEXCore::X86Tables::SecondModRMTableOps, SecondaryModRMExtensionOp_CLZero);
|
|
}
|
|
|
|
if (CTX->HostFeatures.SupportsRAND) {
|
|
InstallToTable(FEXCore::X86Tables::SecondInstGroupOps, SecondaryExtensionOp_RDRAND);
|
|
}
|
|
|
|
if (CTX->HostFeatures.SupportsAVX) {
|
|
InstallToTable(FEXCore::X86Tables::VEXTableOps, AVXTable);
|
|
InstallToTable(FEXCore::X86Tables::VEXTableGroupOps, VEXTableGroupOps);
|
|
}
|
|
|
|
if (CTX->HostFeatures.SupportsPMULL_128Bit) {
|
|
InstallToTable(FEXCore::X86Tables::H0F3ATableOps, H0F3A_PCLMUL);
|
|
InstallToTable(FEXCore::X86Tables::VEXTableOps, VEX_PCLMUL);
|
|
}
|
|
Initialized = true;
|
|
}
|
|
|
|
void InstallOpcodeHandlers(Context::OperatingMode Mode) {
|
|
constexpr std::tuple<uint8_t, uint8_t, X86Tables::OpDispatchPtr> BaseOpTable[] = {
|
|
// Instructions
|
|
{0x00, 6, &OpDispatchBuilder::ALUOp<FEXCore::IR::IROps::OP_ADD, FEXCore::IR::IROps::OP_ATOMICFETCHADD>},
|
|
|
|
{0x08, 6, &OpDispatchBuilder::ALUOp<FEXCore::IR::IROps::OP_OR, FEXCore::IR::IROps::OP_ATOMICFETCHOR>},
|
|
|
|
{0x10, 6, &OpDispatchBuilder::ADCOp<0>},
|
|
|
|
{0x18, 6, &OpDispatchBuilder::SBBOp<0, true>},
|
|
|
|
{0x20, 6, &OpDispatchBuilder::ALUOp<FEXCore::IR::IROps::OP_AND, FEXCore::IR::IROps::OP_ATOMICFETCHAND>},
|
|
|
|
{0x28, 6, &OpDispatchBuilder::ALUOp<FEXCore::IR::IROps::OP_SUB, FEXCore::IR::IROps::OP_ATOMICFETCHSUB>},
|
|
|
|
{0x30, 6, &OpDispatchBuilder::ALUOp<FEXCore::IR::IROps::OP_XOR, FEXCore::IR::IROps::OP_ATOMICFETCHXOR>},
|
|
|
|
{0x38, 6, &OpDispatchBuilder::CMPOp<0>},
|
|
{0x50, 8, &OpDispatchBuilder::PUSHREGOp},
|
|
{0x58, 8, &OpDispatchBuilder::POPOp},
|
|
{0x68, 1, &OpDispatchBuilder::PUSHOp},
|
|
{0x69, 1, &OpDispatchBuilder::IMUL2SrcOp},
|
|
{0x6A, 1, &OpDispatchBuilder::PUSHOp},
|
|
{0x6B, 1, &OpDispatchBuilder::IMUL2SrcOp},
|
|
|
|
{0x70, 16, &OpDispatchBuilder::CondJUMPOp},
|
|
{0x84, 2, &OpDispatchBuilder::TESTOp<0>},
|
|
{0x86, 2, &OpDispatchBuilder::XCHGOp},
|
|
{0x88, 4, &OpDispatchBuilder::MOVGPROp<0>},
|
|
|
|
{0x8C, 1, &OpDispatchBuilder::MOVSegOp<false>},
|
|
{0x8D, 1, &OpDispatchBuilder::LEAOp},
|
|
{0x8E, 1, &OpDispatchBuilder::MOVSegOp<true>},
|
|
{0x8F, 1, &OpDispatchBuilder::POPOp},
|
|
{0x90, 8, &OpDispatchBuilder::XCHGOp},
|
|
|
|
{0x98, 1, &OpDispatchBuilder::CDQOp},
|
|
{0x99, 1, &OpDispatchBuilder::CQOOp},
|
|
{0x9B, 1, &OpDispatchBuilder::NOPOp},
|
|
{0x9C, 1, &OpDispatchBuilder::PUSHFOp},
|
|
{0x9D, 1, &OpDispatchBuilder::POPFOp},
|
|
{0x9E, 1, &OpDispatchBuilder::SAHFOp},
|
|
{0x9F, 1, &OpDispatchBuilder::LAHFOp},
|
|
{0xA0, 4, &OpDispatchBuilder::MOVOffsetOp},
|
|
{0xA4, 2, &OpDispatchBuilder::MOVSOp},
|
|
|
|
{0xA6, 2, &OpDispatchBuilder::CMPSOp},
|
|
{0xA8, 2, &OpDispatchBuilder::TESTOp<0>},
|
|
{0xAA, 2, &OpDispatchBuilder::STOSOp},
|
|
{0xAC, 2, &OpDispatchBuilder::LODSOp},
|
|
{0xAE, 2, &OpDispatchBuilder::SCASOp},
|
|
{0xB0, 16, &OpDispatchBuilder::MOVGPROp<0>},
|
|
{0xC2, 2, &OpDispatchBuilder::RETOp},
|
|
{0xC8, 1, &OpDispatchBuilder::EnterOp},
|
|
{0xC9, 1, &OpDispatchBuilder::LEAVEOp},
|
|
{0xCC, 2, &OpDispatchBuilder::INTOp},
|
|
{0xCF, 1, &OpDispatchBuilder::IRETOp},
|
|
{0xD7, 2, &OpDispatchBuilder::XLATOp},
|
|
{0xE0, 3, &OpDispatchBuilder::LoopOp},
|
|
{0xE3, 1, &OpDispatchBuilder::CondJUMPRCXOp},
|
|
{0xE8, 1, &OpDispatchBuilder::CALLOp},
|
|
{0xE9, 1, &OpDispatchBuilder::JUMPOp},
|
|
{0xEB, 1, &OpDispatchBuilder::JUMPOp},
|
|
{0xF1, 1, &OpDispatchBuilder::INTOp},
|
|
{0xF4, 1, &OpDispatchBuilder::INTOp},
|
|
|
|
{0xF5, 1, &OpDispatchBuilder::FLAGControlOp},
|
|
{0xF8, 2, &OpDispatchBuilder::FLAGControlOp},
|
|
{0xFC, 2, &OpDispatchBuilder::FLAGControlOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, X86Tables::OpDispatchPtr> BaseOpTable_32[] = {
|
|
{0x06, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX>},
|
|
{0x07, 1, &OpDispatchBuilder::POPSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_ES_PREFIX>},
|
|
{0x0E, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_CS_PREFIX>},
|
|
{0x16, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX>},
|
|
{0x17, 1, &OpDispatchBuilder::POPSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_SS_PREFIX>},
|
|
{0x1E, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX>},
|
|
{0x1F, 1, &OpDispatchBuilder::POPSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_DS_PREFIX>},
|
|
{0x27, 1, &OpDispatchBuilder::DAAOp},
|
|
{0x2F, 1, &OpDispatchBuilder::DASOp},
|
|
{0x37, 1, &OpDispatchBuilder::AAAOp},
|
|
{0x3F, 1, &OpDispatchBuilder::AASOp},
|
|
{0x40, 8, &OpDispatchBuilder::INCOp},
|
|
{0x48, 8, &OpDispatchBuilder::DECOp},
|
|
|
|
{0x60, 1, &OpDispatchBuilder::PUSHAOp},
|
|
{0x61, 1, &OpDispatchBuilder::POPAOp},
|
|
{0xCE, 1, &OpDispatchBuilder::INTOp},
|
|
{0xD4, 1, &OpDispatchBuilder::AAMOp},
|
|
{0xD5, 1, &OpDispatchBuilder::AADOp},
|
|
{0xD6, 1, &OpDispatchBuilder::SBBOp<0, false>},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, X86Tables::OpDispatchPtr> BaseOpTable_64[] = {
|
|
{0x63, 1, &OpDispatchBuilder::MOVSXDOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> TwoByteOpTable[] = {
|
|
// Instructions
|
|
{0x0B, 1, &OpDispatchBuilder::INTOp},
|
|
{0x0E, 1, &OpDispatchBuilder::X87EMMS},
|
|
|
|
{0x19, 7, &OpDispatchBuilder::NOPOp}, // NOP with ModRM
|
|
|
|
{0x31, 1, &OpDispatchBuilder::RDTSCOp},
|
|
|
|
{0x3F, 1, &OpDispatchBuilder::ThunkOp},
|
|
{0x40, 16, &OpDispatchBuilder::CMOVOp},
|
|
{0x6E, 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
{0x6F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0x7E, 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
{0x7F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0x80, 16, &OpDispatchBuilder::CondJUMPOp},
|
|
{0x90, 16, &OpDispatchBuilder::SETccOp},
|
|
{0xA0, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX>},
|
|
{0xA1, 1, &OpDispatchBuilder::POPSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_FS_PREFIX>},
|
|
{0xA2, 1, &OpDispatchBuilder::CPUIDOp},
|
|
{0xA3, 1, &OpDispatchBuilder::BTOp<0>}, // BT
|
|
{0xA4, 1, &OpDispatchBuilder::SHLDImmediateOp},
|
|
{0xA5, 1, &OpDispatchBuilder::SHLDOp},
|
|
{0xA8, 1, &OpDispatchBuilder::PUSHSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX>},
|
|
{0xA9, 1, &OpDispatchBuilder::POPSegmentOp<FEXCore::X86Tables::DecodeFlags::FLAG_GS_PREFIX>},
|
|
{0xAB, 1, &OpDispatchBuilder::BTSOp<0>},
|
|
{0xAC, 1, &OpDispatchBuilder::SHRDImmediateOp},
|
|
{0xAD, 1, &OpDispatchBuilder::SHRDOp},
|
|
{0xAF, 1, &OpDispatchBuilder::IMUL1SrcOp},
|
|
{0xB0, 2, &OpDispatchBuilder::CMPXCHGOp}, // CMPXCHG
|
|
{0xB3, 1, &OpDispatchBuilder::BTROp<0>},
|
|
{0xB6, 2, &OpDispatchBuilder::MOVZXOp},
|
|
{0xBB, 1, &OpDispatchBuilder::BTCOp<0>},
|
|
{0xBC, 1, &OpDispatchBuilder::BSFOp}, // BSF
|
|
{0xBD, 1, &OpDispatchBuilder::BSROp}, // BSF
|
|
{0xBE, 2, &OpDispatchBuilder::MOVSXOp},
|
|
{0xC0, 2, &OpDispatchBuilder::XADDOp},
|
|
{0xC3, 1, &OpDispatchBuilder::MOVGPRNTOp},
|
|
{0xC4, 1, &OpDispatchBuilder::PINSROp<2>},
|
|
{0xC5, 1, &OpDispatchBuilder::PExtrOp<2>},
|
|
{0xC8, 8, &OpDispatchBuilder::BSWAPOp},
|
|
|
|
// SSE
|
|
{0x10, 2, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0x12, 2, &OpDispatchBuilder::MOVLPOp},
|
|
{0x14, 1, &OpDispatchBuilder::PUNPCKLOp<4>},
|
|
{0x15, 1, &OpDispatchBuilder::PUNPCKHOp<4>},
|
|
{0x16, 2, &OpDispatchBuilder::MOVHPDOp},
|
|
{0x28, 2, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{0x2A, 1, &OpDispatchBuilder::MMX_To_XMM_Vector_CVT_Int_To_Float<4, false>},
|
|
{0x2B, 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{0x2C, 1, &OpDispatchBuilder::XMM_To_MMX_Vector_CVT_Float_To_Int<4, false, false>},
|
|
{0x2D, 1, &OpDispatchBuilder::XMM_To_MMX_Vector_CVT_Float_To_Int<4, false, true>},
|
|
{0x2E, 2, &OpDispatchBuilder::UCOMISxOp<4>},
|
|
{0x50, 1, &OpDispatchBuilder::MOVMSKOp<4>},
|
|
{0x51, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFSQRT, 4, false>},
|
|
{0x52, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRSQRT, 4, false>},
|
|
{0x53, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRECP, 4, false>},
|
|
{0x54, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VAND, 16>},
|
|
{0x55, 1, &OpDispatchBuilder::VectorALUROp<IR::OP_VBIC, 8>},
|
|
{0x56, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VOR, 16>},
|
|
{0x57, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VXOR, 16>},
|
|
{0x58, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADD, 4>},
|
|
{0x59, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMUL, 4>},
|
|
{0x5A, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Float<8, 4>},
|
|
{0x5B, 1, &OpDispatchBuilder::Vector_CVT_Int_To_Float<4, false>},
|
|
{0x5C, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFSUB, 4>},
|
|
{0x5D, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMIN, 4>},
|
|
{0x5E, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFDIV, 4>},
|
|
{0x5F, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMAX, 4>},
|
|
{0x60, 1, &OpDispatchBuilder::PUNPCKLOp<1>},
|
|
{0x61, 1, &OpDispatchBuilder::PUNPCKLOp<2>},
|
|
{0x62, 1, &OpDispatchBuilder::PUNPCKLOp<4>},
|
|
{0x63, 1, &OpDispatchBuilder::PACKSSOp<2>},
|
|
{0x64, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 1>},
|
|
{0x65, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 2>},
|
|
{0x66, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 4>},
|
|
{0x67, 1, &OpDispatchBuilder::PACKUSOp<2>},
|
|
{0x68, 1, &OpDispatchBuilder::PUNPCKHOp<1>},
|
|
{0x69, 1, &OpDispatchBuilder::PUNPCKHOp<2>},
|
|
{0x6A, 1, &OpDispatchBuilder::PUNPCKHOp<4>},
|
|
{0x6B, 1, &OpDispatchBuilder::PACKSSOp<4>},
|
|
{0x70, 1, &OpDispatchBuilder::PSHUFW8ByteOp},
|
|
|
|
{0x74, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 1>},
|
|
{0x75, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 2>},
|
|
{0x76, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 4>},
|
|
{0x77, 1, &OpDispatchBuilder::X87EMMS},
|
|
|
|
{0xC2, 1, &OpDispatchBuilder::VFCMPOp<4, false>},
|
|
{0xC6, 1, &OpDispatchBuilder::SHUFOp<4>},
|
|
|
|
{0xD1, 1, &OpDispatchBuilder::PSRLDOp<2>},
|
|
{0xD2, 1, &OpDispatchBuilder::PSRLDOp<4>},
|
|
{0xD3, 1, &OpDispatchBuilder::PSRLDOp<8>},
|
|
{0xD4, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 8>},
|
|
{0xD5, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VMUL, 2>},
|
|
{0xD7, 1, &OpDispatchBuilder::MOVMSKOpOne}, // PMOVMSKB
|
|
{0xD8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQSUB, 1>},
|
|
{0xD9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQSUB, 2>},
|
|
{0xDA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMIN, 1>},
|
|
{0xDB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VAND, 8>},
|
|
{0xDC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQADD, 1>},
|
|
{0xDD, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQADD, 2>},
|
|
{0xDE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMAX, 1>},
|
|
{0xDF, 1, &OpDispatchBuilder::VectorALUROp<IR::OP_VBIC, 8>},
|
|
{0xE0, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VURAVG, 1>},
|
|
{0xE1, 1, &OpDispatchBuilder::PSRAOp<2>},
|
|
{0xE2, 1, &OpDispatchBuilder::PSRAOp<4>},
|
|
{0xE3, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VURAVG, 2>},
|
|
{0xE4, 1, &OpDispatchBuilder::PMULHW<false>},
|
|
{0xE5, 1, &OpDispatchBuilder::PMULHW<true>},
|
|
{0xE7, 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{0xE8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQSUB, 1>},
|
|
{0xE9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQSUB, 2>},
|
|
{0xEA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMIN, 2>},
|
|
{0xEB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VOR, 8>},
|
|
{0xEC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQADD, 1>},
|
|
{0xED, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQADD, 2>},
|
|
{0xEE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMAX, 2>},
|
|
{0xEF, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VXOR, 8>},
|
|
|
|
{0xF1, 1, &OpDispatchBuilder::PSLL<2>},
|
|
{0xF2, 1, &OpDispatchBuilder::PSLL<4>},
|
|
{0xF3, 1, &OpDispatchBuilder::PSLL<8>},
|
|
{0xF4, 1, &OpDispatchBuilder::PMULLOp<4, false>},
|
|
{0xF5, 1, &OpDispatchBuilder::PMADDWD},
|
|
{0xF6, 1, &OpDispatchBuilder::PSADBW},
|
|
{0xF7, 1, &OpDispatchBuilder::MASKMOVOp},
|
|
{0xF8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 1>},
|
|
{0xF9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 2>},
|
|
{0xFA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 4>},
|
|
{0xFB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 8>},
|
|
{0xFC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 1>},
|
|
{0xFD, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 2>},
|
|
{0xFE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 4>},
|
|
|
|
// FEX reserved instructions
|
|
{0x37, 1, &OpDispatchBuilder::CallbackReturnOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> TwoByteOpTable_32[] = {
|
|
{0x05, 1, &OpDispatchBuilder::NOPOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> TwoByteOpTable_64[] = {
|
|
{0x05, 1, &OpDispatchBuilder::SyscallOp},
|
|
};
|
|
|
|
#define OPD(group, prefix, Reg) (((group - FEXCore::X86Tables::TYPE_GROUP_1) << 6) | (prefix) << 3 | (Reg))
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> PrimaryGroupOpTable[] = {
|
|
// GROUP 1
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 0), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 1), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 2), 1, &OpDispatchBuilder::ADCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 3), 1, &OpDispatchBuilder::SBBOp<1, true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 4), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 5), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 6), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x80), 7), 1, &OpDispatchBuilder::CMPOp<1>}, // CMP
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 0), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 1), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 2), 1, &OpDispatchBuilder::ADCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 3), 1, &OpDispatchBuilder::SBBOp<1, true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 4), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 5), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 6), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x81), 7), 1, &OpDispatchBuilder::CMPOp<1>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 0), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 1), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 2), 1, &OpDispatchBuilder::ADCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 3), 1, &OpDispatchBuilder::SBBOp<1, true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 4), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 5), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 6), 1, &OpDispatchBuilder::SecondaryALUOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_1, OpToIndex(0x83), 7), 1, &OpDispatchBuilder::CMPOp<1>},
|
|
|
|
// GROUP 2
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 0), 1, &OpDispatchBuilder::ROLImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 1), 1, &OpDispatchBuilder::RORImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 2), 1, &OpDispatchBuilder::RCLOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 3), 1, &OpDispatchBuilder::RCROp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 4), 1, &OpDispatchBuilder::SHLImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 5), 1, &OpDispatchBuilder::SHRImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 6), 1, &OpDispatchBuilder::SHLImmediateOp}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC0), 7), 1, &OpDispatchBuilder::ASHRImmediateOp}, // SAR
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 0), 1, &OpDispatchBuilder::ROLImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 1), 1, &OpDispatchBuilder::RORImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 2), 1, &OpDispatchBuilder::RCLOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 3), 1, &OpDispatchBuilder::RCROp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 4), 1, &OpDispatchBuilder::SHLImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 5), 1, &OpDispatchBuilder::SHRImmediateOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 6), 1, &OpDispatchBuilder::SHLImmediateOp}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xC1), 7), 1, &OpDispatchBuilder::ASHRImmediateOp}, // SAR
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 0), 1, &OpDispatchBuilder::ROLOp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 1), 1, &OpDispatchBuilder::ROROp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 2), 1, &OpDispatchBuilder::RCLOp1Bit},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 3), 1, &OpDispatchBuilder::RCROp8x1Bit},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 4), 1, &OpDispatchBuilder::SHLOp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 5), 1, &OpDispatchBuilder::SHROp<true>}, // 1Bit SHR
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 6), 1, &OpDispatchBuilder::SHLOp<true>}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD0), 7), 1, &OpDispatchBuilder::ASHROp<true>}, // SAR
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 0), 1, &OpDispatchBuilder::ROLOp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 1), 1, &OpDispatchBuilder::ROROp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 2), 1, &OpDispatchBuilder::RCLOp1Bit},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 3), 1, &OpDispatchBuilder::RCROp1Bit},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 4), 1, &OpDispatchBuilder::SHLOp<true>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 5), 1, &OpDispatchBuilder::SHROp<true>}, // 1Bit SHR
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 6), 1, &OpDispatchBuilder::SHLOp<true>}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD1), 7), 1, &OpDispatchBuilder::ASHROp<true>}, // SAR
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 0), 1, &OpDispatchBuilder::ROLOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 1), 1, &OpDispatchBuilder::ROROp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 2), 1, &OpDispatchBuilder::RCLSmallerOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 3), 1, &OpDispatchBuilder::RCRSmallerOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 4), 1, &OpDispatchBuilder::SHLOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 5), 1, &OpDispatchBuilder::SHROp<false>}, // SHR by CL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 6), 1, &OpDispatchBuilder::SHLOp<false>}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD2), 7), 1, &OpDispatchBuilder::ASHROp<false>}, // SAR
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 0), 1, &OpDispatchBuilder::ROLOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 1), 1, &OpDispatchBuilder::ROROp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 2), 1, &OpDispatchBuilder::RCLOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 3), 1, &OpDispatchBuilder::RCROp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 4), 1, &OpDispatchBuilder::SHLOp<false>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 5), 1, &OpDispatchBuilder::SHROp<false>}, // SHR by CL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 6), 1, &OpDispatchBuilder::SHLOp<false>}, // SAL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_2, OpToIndex(0xD3), 7), 1, &OpDispatchBuilder::ASHROp<false>}, // SAR
|
|
|
|
// GROUP 3
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 0), 1, &OpDispatchBuilder::TESTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 1), 1, &OpDispatchBuilder::TESTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 2), 1, &OpDispatchBuilder::NOTOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 3), 1, &OpDispatchBuilder::NEGOp}, // NEG
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 4), 1, &OpDispatchBuilder::MULOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 5), 1, &OpDispatchBuilder::IMULOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 6), 1, &OpDispatchBuilder::DIVOp}, // DIV
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF6), 7), 1, &OpDispatchBuilder::IDIVOp}, // IDIV
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 0), 1, &OpDispatchBuilder::TESTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 1), 1, &OpDispatchBuilder::TESTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 2), 1, &OpDispatchBuilder::NOTOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 3), 1, &OpDispatchBuilder::NEGOp}, // NEG
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 4), 1, &OpDispatchBuilder::MULOp}, // MUL
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 5), 1, &OpDispatchBuilder::IMULOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 6), 1, &OpDispatchBuilder::DIVOp}, // DIV
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_3, OpToIndex(0xF7), 7), 1, &OpDispatchBuilder::IDIVOp}, // IDIV
|
|
|
|
// GROUP 4
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_4, OpToIndex(0xFE), 0), 1, &OpDispatchBuilder::INCOp}, // INC
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_4, OpToIndex(0xFE), 1), 1, &OpDispatchBuilder::DECOp}, // DEC
|
|
|
|
// GROUP 5
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 0), 1, &OpDispatchBuilder::INCOp}, // INC
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 1), 1, &OpDispatchBuilder::DECOp}, // DEC
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 2), 1, &OpDispatchBuilder::CALLAbsoluteOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 4), 1, &OpDispatchBuilder::JUMPAbsoluteOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_5, OpToIndex(0xFF), 6), 1, &OpDispatchBuilder::PUSHOp},
|
|
|
|
// GROUP 11
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_11, OpToIndex(0xC6), 0), 1, &OpDispatchBuilder::MOVGPROp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_11, OpToIndex(0xC7), 0), 1, &OpDispatchBuilder::MOVGPROp<1>},
|
|
};
|
|
#undef OPD
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> RepModOpTable[] = {
|
|
{0x10, 2, &OpDispatchBuilder::MOVSSOp},
|
|
{0x12, 1, &OpDispatchBuilder::VMOVSLDUPOp},
|
|
{0x16, 1, &OpDispatchBuilder::VMOVSHDUPOp},
|
|
{0x19, 7, &OpDispatchBuilder::NOPOp},
|
|
{0x2A, 1, &OpDispatchBuilder::CVTGPR_To_FPR<4>},
|
|
{0x2B, 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{0x2C, 1, &OpDispatchBuilder::CVTFPR_To_GPR<4, false>},
|
|
{0x2D, 1, &OpDispatchBuilder::CVTFPR_To_GPR<4, true>},
|
|
{0x51, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFSQRT, 4, true>},
|
|
{0x52, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRSQRT, 4, true>},
|
|
{0x53, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRECP, 4, true>},
|
|
{0x58, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFADD, 4>},
|
|
{0x59, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMUL, 4>},
|
|
{0x5A, 1, &OpDispatchBuilder::Scalar_CVT_Float_To_Float<8, 4>},
|
|
{0x5B, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Int<4, false, false>},
|
|
{0x5C, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFSUB, 4>},
|
|
{0x5D, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMIN, 4>},
|
|
{0x5E, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFDIV, 4>},
|
|
{0x5F, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMAX, 4>},
|
|
{0x6F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0x70, 1, &OpDispatchBuilder::PSHUFWOp<false>},
|
|
{0x7E, 1, &OpDispatchBuilder::MOVQOp},
|
|
{0x7F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0xB8, 1, &OpDispatchBuilder::PopcountOp},
|
|
{0xBC, 1, &OpDispatchBuilder::TZCNT},
|
|
{0xBD, 1, &OpDispatchBuilder::LZCNT},
|
|
{0xC2, 1, &OpDispatchBuilder::VFCMPOp<4, true>},
|
|
{0xD6, 1, &OpDispatchBuilder::MOVQ2DQ<true>},
|
|
{0xE6, 1, &OpDispatchBuilder::Vector_CVT_Int_To_Float<4, true>},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> RepNEModOpTable[] = {
|
|
{0x10, 2, &OpDispatchBuilder::MOVSDOp},
|
|
{0x12, 1, &OpDispatchBuilder::MOVDDUPOp},
|
|
{0x19, 7, &OpDispatchBuilder::NOPOp},
|
|
{0x2A, 1, &OpDispatchBuilder::CVTGPR_To_FPR<8>},
|
|
{0x2B, 1, &OpDispatchBuilder::MOVVectorOp},
|
|
{0x2C, 1, &OpDispatchBuilder::CVTFPR_To_GPR<8, false>},
|
|
{0x2D, 1, &OpDispatchBuilder::CVTFPR_To_GPR<8, true>},
|
|
{0x51, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFSQRT, 8, true>},
|
|
//x52 = Invalid
|
|
{0x58, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFADD, 8>},
|
|
{0x59, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMUL, 8>},
|
|
{0x5A, 1, &OpDispatchBuilder::Scalar_CVT_Float_To_Float<4, 8>},
|
|
{0x5C, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFSUB, 8>},
|
|
{0x5D, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMIN, 8>},
|
|
{0x5E, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFDIV, 8>},
|
|
{0x5F, 1, &OpDispatchBuilder::VectorScalarALUOp<IR::OP_VFMAX, 8>},
|
|
{0x70, 1, &OpDispatchBuilder::PSHUFWOp<true>},
|
|
{0x7C, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADDP, 4>},
|
|
{0x7D, 1, &OpDispatchBuilder::HSUBP<4>},
|
|
{0xD0, 1, &OpDispatchBuilder::ADDSUBPOp<4>},
|
|
{0xD6, 1, &OpDispatchBuilder::MOVQ2DQ<false>},
|
|
{0xC2, 1, &OpDispatchBuilder::VFCMPOp<8, true>},
|
|
{0xE6, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Int<8, true, true>},
|
|
{0xF0, 1, &OpDispatchBuilder::MOVVectorOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> OpSizeModOpTable[] = {
|
|
{0x10, 2, &OpDispatchBuilder::MOVVectorOp},
|
|
{0x12, 2, &OpDispatchBuilder::MOVLPOp},
|
|
{0x14, 1, &OpDispatchBuilder::PUNPCKLOp<8>},
|
|
{0x15, 1, &OpDispatchBuilder::PUNPCKHOp<8>},
|
|
{0x16, 2, &OpDispatchBuilder::MOVHPDOp},
|
|
{0x19, 7, &OpDispatchBuilder::NOPOp},
|
|
{0x28, 2, &OpDispatchBuilder::MOVAPS_MOVAPDOp},
|
|
{0x2A, 1, &OpDispatchBuilder::MMX_To_XMM_Vector_CVT_Int_To_Float<4, true>},
|
|
{0x2B, 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{0x2C, 1, &OpDispatchBuilder::XMM_To_MMX_Vector_CVT_Float_To_Int<8, true, false>},
|
|
{0x2D, 1, &OpDispatchBuilder::XMM_To_MMX_Vector_CVT_Float_To_Int<8, true, true>},
|
|
{0x2E, 2, &OpDispatchBuilder::UCOMISxOp<8>},
|
|
|
|
{0x40, 16, &OpDispatchBuilder::CMOVOp},
|
|
{0x50, 1, &OpDispatchBuilder::MOVMSKOp<8>},
|
|
{0x51, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFSQRT, 8, false>},
|
|
{0x54, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VAND, 16>},
|
|
{0x55, 1, &OpDispatchBuilder::VectorALUROp<IR::OP_VBIC, 8>},
|
|
{0x56, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VOR, 16>},
|
|
{0x57, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VXOR, 16>},
|
|
{0x58, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADD, 8>},
|
|
{0x59, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMUL, 8>},
|
|
{0x5A, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Float<4, 8>},
|
|
{0x5B, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Int<4, false, true>},
|
|
{0x5C, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFSUB, 8>},
|
|
{0x5D, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMIN, 8>},
|
|
{0x5E, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFDIV, 8>},
|
|
{0x5F, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMAX, 8>},
|
|
{0x60, 1, &OpDispatchBuilder::PUNPCKLOp<1>},
|
|
{0x61, 1, &OpDispatchBuilder::PUNPCKLOp<2>},
|
|
{0x62, 1, &OpDispatchBuilder::PUNPCKLOp<4>},
|
|
{0x63, 1, &OpDispatchBuilder::PACKSSOp<2>},
|
|
{0x64, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 1>},
|
|
{0x65, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 2>},
|
|
{0x66, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 4>},
|
|
{0x67, 1, &OpDispatchBuilder::PACKUSOp<2>},
|
|
{0x68, 1, &OpDispatchBuilder::PUNPCKHOp<1>},
|
|
{0x69, 1, &OpDispatchBuilder::PUNPCKHOp<2>},
|
|
{0x6A, 1, &OpDispatchBuilder::PUNPCKHOp<4>},
|
|
{0x6B, 1, &OpDispatchBuilder::PACKSSOp<4>},
|
|
{0x6C, 1, &OpDispatchBuilder::PUNPCKLOp<8>},
|
|
{0x6D, 1, &OpDispatchBuilder::PUNPCKHOp<8>},
|
|
{0x6E, 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
{0x6F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0x70, 1, &OpDispatchBuilder::PSHUFDOp},
|
|
|
|
{0x74, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 1>},
|
|
{0x75, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 2>},
|
|
{0x76, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 4>},
|
|
{0x78, 1, nullptr}, // GROUP 17
|
|
{0x7C, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADDP, 8>},
|
|
{0x7D, 1, &OpDispatchBuilder::HSUBP<8>},
|
|
{0x7E, 1, &OpDispatchBuilder::MOVBetweenGPR_FPR},
|
|
{0x7F, 1, &OpDispatchBuilder::MOVUPS_MOVUPDOp},
|
|
{0xC2, 1, &OpDispatchBuilder::VFCMPOp<8, false>},
|
|
{0xC4, 1, &OpDispatchBuilder::PINSROp<2>},
|
|
{0xC5, 1, &OpDispatchBuilder::PExtrOp<2>},
|
|
{0xC6, 1, &OpDispatchBuilder::SHUFOp<8>},
|
|
|
|
{0xD0, 1, &OpDispatchBuilder::ADDSUBPOp<8>},
|
|
{0xD1, 1, &OpDispatchBuilder::PSRLDOp<2>},
|
|
{0xD2, 1, &OpDispatchBuilder::PSRLDOp<4>},
|
|
{0xD3, 1, &OpDispatchBuilder::PSRLDOp<8>},
|
|
{0xD4, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 8>},
|
|
{0xD5, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VMUL, 2>},
|
|
{0xD6, 1, &OpDispatchBuilder::MOVQOp},
|
|
{0xD7, 1, &OpDispatchBuilder::MOVMSKOpOne}, // PMOVMSKB
|
|
{0xD8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQSUB, 1>},
|
|
{0xD9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQSUB, 2>},
|
|
{0xDA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMIN, 1>},
|
|
{0xDB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VAND, 16>},
|
|
{0xDC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQADD, 1>},
|
|
{0xDD, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUQADD, 2>},
|
|
{0xDE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMAX, 1>},
|
|
{0xDF, 1, &OpDispatchBuilder::VectorALUROp<IR::OP_VBIC, 8>},
|
|
{0xE0, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VURAVG, 1>},
|
|
{0xE1, 1, &OpDispatchBuilder::PSRAOp<2>},
|
|
{0xE2, 1, &OpDispatchBuilder::PSRAOp<4>},
|
|
{0xE3, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VURAVG, 2>},
|
|
{0xE4, 1, &OpDispatchBuilder::PMULHW<false>},
|
|
{0xE5, 1, &OpDispatchBuilder::PMULHW<true>},
|
|
{0xE6, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Int<8, true, false>},
|
|
{0xE7, 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{0xE8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQSUB, 1>},
|
|
{0xE9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQSUB, 2>},
|
|
{0xEA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMIN, 2>},
|
|
{0xEB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VOR, 16>},
|
|
{0xEC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQADD, 1>},
|
|
{0xED, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSQADD, 2>},
|
|
{0xEE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMAX, 2>},
|
|
{0xEF, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VXOR, 16>},
|
|
|
|
{0xF1, 1, &OpDispatchBuilder::PSLL<2>},
|
|
{0xF2, 1, &OpDispatchBuilder::PSLL<4>},
|
|
{0xF3, 1, &OpDispatchBuilder::PSLL<8>},
|
|
{0xF4, 1, &OpDispatchBuilder::PMULLOp<4, false>},
|
|
{0xF5, 1, &OpDispatchBuilder::PMADDWD},
|
|
{0xF6, 1, &OpDispatchBuilder::PSADBW},
|
|
{0xF7, 1, &OpDispatchBuilder::MASKMOVOp},
|
|
{0xF8, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 1>},
|
|
{0xF9, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 2>},
|
|
{0xFA, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 4>},
|
|
{0xFB, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSUB, 8>},
|
|
{0xFC, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 1>},
|
|
{0xFD, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 2>},
|
|
{0xFE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADD, 4>},
|
|
};
|
|
|
|
constexpr uint16_t PF_NONE = 0;
|
|
constexpr uint16_t PF_F3 = 1;
|
|
constexpr uint16_t PF_66 = 2;
|
|
constexpr uint16_t PF_F2 = 3;
|
|
#define OPD(group, prefix, Reg) (((group - FEXCore::X86Tables::TYPE_GROUP_6) << 5) | (prefix) << 3 | (Reg))
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> SecondaryExtensionOpTable[] = {
|
|
// GROUP 7
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_7, PF_NONE, 0), 1, &OpDispatchBuilder::SGDTOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_7, PF_F3, 0), 1, &OpDispatchBuilder::SGDTOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_7, PF_66, 0), 1, &OpDispatchBuilder::SGDTOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_7, PF_F2, 0), 1, &OpDispatchBuilder::SGDTOp},
|
|
|
|
// GROUP 8
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_NONE, 4), 1, &OpDispatchBuilder::BTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F3, 4), 1, &OpDispatchBuilder::BTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_66, 4), 1, &OpDispatchBuilder::BTOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F2, 4), 1, &OpDispatchBuilder::BTOp<1>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_NONE, 5), 1, &OpDispatchBuilder::BTSOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F3, 5), 1, &OpDispatchBuilder::BTSOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_66, 5), 1, &OpDispatchBuilder::BTSOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F2, 5), 1, &OpDispatchBuilder::BTSOp<1>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_NONE, 6), 1, &OpDispatchBuilder::BTROp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F3, 6), 1, &OpDispatchBuilder::BTROp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_66, 6), 1, &OpDispatchBuilder::BTROp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F2, 6), 1, &OpDispatchBuilder::BTROp<1>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_NONE, 7), 1, &OpDispatchBuilder::BTCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F3, 7), 1, &OpDispatchBuilder::BTCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_66, 7), 1, &OpDispatchBuilder::BTCOp<1>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_8, PF_F2, 7), 1, &OpDispatchBuilder::BTCOp<1>},
|
|
|
|
// GROUP 9
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_NONE, 1), 1, &OpDispatchBuilder::CMPXCHGPairOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_F3, 1), 1, &OpDispatchBuilder::CMPXCHGPairOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_66, 1), 1, &OpDispatchBuilder::CMPXCHGPairOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_9, PF_F2, 1), 1, &OpDispatchBuilder::CMPXCHGPairOp},
|
|
|
|
// GROUP 12
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_NONE, 2), 1, &OpDispatchBuilder::PSRLI<2>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_NONE, 4), 1, &OpDispatchBuilder::PSRAIOp<2>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_NONE, 6), 1, &OpDispatchBuilder::PSLLI<2>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_66, 2), 1, &OpDispatchBuilder::PSRLI<2>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_66, 4), 1, &OpDispatchBuilder::PSRAIOp<2>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_12, PF_66, 6), 1, &OpDispatchBuilder::PSLLI<2>},
|
|
|
|
// GROUP 13
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_NONE, 2), 1, &OpDispatchBuilder::PSRLI<4>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_NONE, 4), 1, &OpDispatchBuilder::PSRAIOp<4>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_NONE, 6), 1, &OpDispatchBuilder::PSLLI<4>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_66, 2), 1, &OpDispatchBuilder::PSRLI<4>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_66, 4), 1, &OpDispatchBuilder::PSRAIOp<4>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_13, PF_66, 6), 1, &OpDispatchBuilder::PSLLI<4>},
|
|
|
|
// GROUP 14
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_NONE, 2), 1, &OpDispatchBuilder::PSRLI<8>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_NONE, 6), 1, &OpDispatchBuilder::PSLLI<8>},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_66, 2), 1, &OpDispatchBuilder::PSRLI<8>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_66, 3), 1, &OpDispatchBuilder::PSRLDQ},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_66, 6), 1, &OpDispatchBuilder::PSLLI<8>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_14, PF_66, 7), 1, &OpDispatchBuilder::PSLLDQ},
|
|
|
|
// GROUP 15
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 0), 1, &OpDispatchBuilder::FXSaveOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 1), 1, &OpDispatchBuilder::FXRStoreOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 2), 1, &OpDispatchBuilder::LDMXCSR},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 3), 1, &OpDispatchBuilder::STMXCSR},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 4), 1, &OpDispatchBuilder::XSaveOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 5), 1, &OpDispatchBuilder::LoadFenceOrXRSTOR}, // LFENCE (or XRSTOR)
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 6), 1, &OpDispatchBuilder::MemFenceOrXSAVEOPT}, // MFENCE (or XSAVEOPT)
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_NONE, 7), 1, &OpDispatchBuilder::StoreFenceOrCLFlush}, // SFENCE (or CLFLUSH)
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 5), 1, &OpDispatchBuilder::UnimplementedOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 6), 1, &OpDispatchBuilder::UnimplementedOp},
|
|
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_66, 6), 1, &OpDispatchBuilder::CLWB},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_66, 7), 1, &OpDispatchBuilder::CLFLUSHOPT},
|
|
|
|
// GROUP 16
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_16, PF_NONE, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_16, PF_F3, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_16, PF_66, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_16, PF_F2, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
|
|
// GROUP P
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_P, PF_NONE, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_P, PF_F3, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_P, PF_66, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_P, PF_F2, 0), 8, &OpDispatchBuilder::NOPOp},
|
|
};
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> SecondaryExtensionOpTable_64[] = {
|
|
// GROUP 15
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 0), 1, &OpDispatchBuilder::ReadSegmentReg<OpDispatchBuilder::Segment::FS>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 1), 1, &OpDispatchBuilder::ReadSegmentReg<OpDispatchBuilder::Segment::GS>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 2), 1, &OpDispatchBuilder::WriteSegmentReg<OpDispatchBuilder::Segment::FS>},
|
|
{OPD(FEXCore::X86Tables::TYPE_GROUP_15, PF_F3, 3), 1, &OpDispatchBuilder::WriteSegmentReg<OpDispatchBuilder::Segment::GS>},
|
|
};
|
|
|
|
#undef OPD
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> SecondaryModRMExtensionOpTable[] = {
|
|
// REG /2
|
|
{((1 << 3) | 0), 1, &OpDispatchBuilder::XGetBVOp},
|
|
|
|
// REG /7
|
|
{((3 << 3) | 1), 1, &OpDispatchBuilder::RDTSCPOp},
|
|
|
|
};
|
|
// Top bit indicating if it needs to be repeated with {0x40, 0x80} or'd in
|
|
// All OPDReg versions need it
|
|
#define OPDReg(op, reg) ((1 << 15) | ((op - 0xD8) << 8) | (reg << 3))
|
|
#define OPD(op, modrmop) (((op - 0xD8) << 8) | modrmop)
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> X87F64OpTable[] = {
|
|
{OPDReg(0xD8, 0) | 0x00, 8, &OpDispatchBuilder::FADDF64<32, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 1) | 0x00, 8, &OpDispatchBuilder::FMULF64<32, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 2) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<32, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xD8, 3) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<32, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xD8, 4) | 0x00, 8, &OpDispatchBuilder::FSUBF64<32, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 5) | 0x00, 8, &OpDispatchBuilder::FSUBF64<32, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 6) | 0x00, 8, &OpDispatchBuilder::FDIVF64<32, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 7) | 0x00, 8, &OpDispatchBuilder::FDIVF64<32, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xD8, 0xC0), 8, &OpDispatchBuilder::FADDF64<80, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xC8), 8, &OpDispatchBuilder::FMULF64<80, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xD0), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xD8, 0xD8), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xD8, 0xE0), 8, &OpDispatchBuilder::FSUBF64<80, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xE8), 8, &OpDispatchBuilder::FSUBF64<80, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xF0), 8, &OpDispatchBuilder::FDIVF64<80, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xF8), 8, &OpDispatchBuilder::FDIVF64<80, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD9, 0) | 0x00, 8, &OpDispatchBuilder::FLDF64<32>},
|
|
|
|
// 1 = Invalid
|
|
|
|
{OPDReg(0xD9, 2) | 0x00, 8, &OpDispatchBuilder::FSTF64<32>},
|
|
|
|
{OPDReg(0xD9, 3) | 0x00, 8, &OpDispatchBuilder::FSTF64<32>},
|
|
|
|
{OPDReg(0xD9, 4) | 0x00, 8, &OpDispatchBuilder::X87LDENVF64},
|
|
|
|
{OPDReg(0xD9, 5) | 0x00, 8, &OpDispatchBuilder::X87FLDCWF64},
|
|
|
|
{OPDReg(0xD9, 6) | 0x00, 8, &OpDispatchBuilder::X87FNSTENV},
|
|
|
|
{OPDReg(0xD9, 7) | 0x00, 8, &OpDispatchBuilder::X87FSTCW},
|
|
|
|
{OPD(0xD9, 0xC0), 8, &OpDispatchBuilder::FLDF64<80>},
|
|
{OPD(0xD9, 0xC8), 8, &OpDispatchBuilder::FXCH},
|
|
{OPD(0xD9, 0xD0), 1, &OpDispatchBuilder::NOPOp}, // FNOP
|
|
// D1 = Invalid
|
|
// D8 = Invalid
|
|
{OPD(0xD9, 0xE0), 1, &OpDispatchBuilder::FCHSF64},
|
|
{OPD(0xD9, 0xE1), 1, &OpDispatchBuilder::FABSF64},
|
|
// E2 = Invalid
|
|
{OPD(0xD9, 0xE4), 1, &OpDispatchBuilder::FTSTF64},
|
|
{OPD(0xD9, 0xE5), 1, &OpDispatchBuilder::X87FXAMF64},
|
|
// E6 = Invalid
|
|
{OPD(0xD9, 0xE8), 1, &OpDispatchBuilder::FLDF64_Const<0x3FF0000000000000>}, // 1.0
|
|
{OPD(0xD9, 0xE9), 1, &OpDispatchBuilder::FLDF64_Const<0x400A934F0979A372>}, // log2l(10)
|
|
{OPD(0xD9, 0xEA), 1, &OpDispatchBuilder::FLDF64_Const<0x3FF71547652B82FE>}, // log2l(e)
|
|
{OPD(0xD9, 0xEB), 1, &OpDispatchBuilder::FLDF64_Const<0x400921FB54442D18>}, // pi
|
|
{OPD(0xD9, 0xEC), 1, &OpDispatchBuilder::FLDF64_Const<0x3FD34413509F79FF>}, // log10l(2)
|
|
{OPD(0xD9, 0xED), 1, &OpDispatchBuilder::FLDF64_Const<0x3FE62E42FEFA39EF>}, // log(2)
|
|
{OPD(0xD9, 0xEE), 1, &OpDispatchBuilder::FLDF64_Const<0>}, // 0.0
|
|
|
|
// EF = Invalid
|
|
{OPD(0xD9, 0xF0), 1, &OpDispatchBuilder::X87UnaryOpF64<IR::OP_F64F2XM1>},
|
|
{OPD(0xD9, 0xF1), 1, &OpDispatchBuilder::X87FYL2XF64},
|
|
{OPD(0xD9, 0xF2), 1, &OpDispatchBuilder::X87TANF64},
|
|
{OPD(0xD9, 0xF3), 1, &OpDispatchBuilder::X87ATANF64},
|
|
{OPD(0xD9, 0xF4), 1, &OpDispatchBuilder::FXTRACTF64},
|
|
{OPD(0xD9, 0xF5), 1, &OpDispatchBuilder::X87BinaryOpF64<IR::OP_F64FPREM1>},
|
|
{OPD(0xD9, 0xF6), 1, &OpDispatchBuilder::X87ModifySTP<false>},
|
|
{OPD(0xD9, 0xF7), 1, &OpDispatchBuilder::X87ModifySTP<true>},
|
|
{OPD(0xD9, 0xF8), 1, &OpDispatchBuilder::X87BinaryOpF64<IR::OP_F64FPREM>},
|
|
{OPD(0xD9, 0xF9), 1, &OpDispatchBuilder::X87FYL2XF64},
|
|
{OPD(0xD9, 0xFA), 1, &OpDispatchBuilder::FSQRTF64},
|
|
{OPD(0xD9, 0xFB), 1, &OpDispatchBuilder::X87SinCosF64},
|
|
{OPD(0xD9, 0xFC), 1, &OpDispatchBuilder::FRNDINTF64},
|
|
{OPD(0xD9, 0xFD), 1, &OpDispatchBuilder::X87BinaryOpF64<IR::OP_F64SCALE>},
|
|
{OPD(0xD9, 0xFE), 1, &OpDispatchBuilder::X87UnaryOpF64<IR::OP_F64SIN>},
|
|
{OPD(0xD9, 0xFF), 1, &OpDispatchBuilder::X87UnaryOpF64<IR::OP_F64COS>},
|
|
|
|
{OPDReg(0xDA, 0) | 0x00, 8, &OpDispatchBuilder::FADDF64<32, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 1) | 0x00, 8, &OpDispatchBuilder::FMULF64<32, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 2) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<32, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDA, 3) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<32, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDA, 4) | 0x00, 8, &OpDispatchBuilder::FSUBF64<32, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 5) | 0x00, 8, &OpDispatchBuilder::FSUBF64<32, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 6) | 0x00, 8, &OpDispatchBuilder::FDIVF64<32, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 7) | 0x00, 8, &OpDispatchBuilder::FDIVF64<32, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDA, 0xC0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xC8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xD0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xD8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
// E0 = Invalid
|
|
// E8 = Invalid
|
|
{OPD(0xDA, 0xE9), 1, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, true>},
|
|
// EA = Invalid
|
|
// F0 = Invalid
|
|
// F8 = Invalid
|
|
|
|
{OPDReg(0xDB, 0) | 0x00, 8, &OpDispatchBuilder::FILDF64},
|
|
|
|
{OPDReg(0xDB, 1) | 0x00, 8, &OpDispatchBuilder::FISTF64<true>},
|
|
|
|
{OPDReg(0xDB, 2) | 0x00, 8, &OpDispatchBuilder::FISTF64<false>},
|
|
|
|
{OPDReg(0xDB, 3) | 0x00, 8, &OpDispatchBuilder::FISTF64<false>},
|
|
|
|
// 4 = Invalid
|
|
|
|
{OPDReg(0xDB, 5) | 0x00, 8, &OpDispatchBuilder::FLDF64<80>},
|
|
|
|
// 6 = Invalid
|
|
|
|
{OPDReg(0xDB, 7) | 0x00, 8, &OpDispatchBuilder::FSTF64<80>},
|
|
|
|
|
|
{OPD(0xDB, 0xC0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xC8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xD0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xD8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
// E0 = Invalid
|
|
{OPD(0xDB, 0xE2), 1, &OpDispatchBuilder::NOPOp}, // FNCLEX
|
|
{OPD(0xDB, 0xE3), 1, &OpDispatchBuilder::FNINITF64},
|
|
// E4 = Invalid
|
|
{OPD(0xDB, 0xE8), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
{OPD(0xDB, 0xF0), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
|
|
// F8 = Invalid
|
|
|
|
{OPDReg(0xDC, 0) | 0x00, 8, &OpDispatchBuilder::FADDF64<64, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 1) | 0x00, 8, &OpDispatchBuilder::FMULF64<64, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 2) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<64, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDC, 3) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<64, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDC, 4) | 0x00, 8, &OpDispatchBuilder::FSUBF64<64, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 5) | 0x00, 8, &OpDispatchBuilder::FSUBF64<64, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 6) | 0x00, 8, &OpDispatchBuilder::FDIVF64<64, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 7) | 0x00, 8, &OpDispatchBuilder::FDIVF64<64, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDC, 0xC0), 8, &OpDispatchBuilder::FADDF64<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xC8), 8, &OpDispatchBuilder::FMULF64<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xE0), 8, &OpDispatchBuilder::FSUBF64<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xE8), 8, &OpDispatchBuilder::FSUBF64<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xF0), 8, &OpDispatchBuilder::FDIVF64<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xF8), 8, &OpDispatchBuilder::FDIVF64<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
|
|
{OPDReg(0xDD, 0) | 0x00, 8, &OpDispatchBuilder::FLDF64<64>},
|
|
|
|
{OPDReg(0xDD, 1) | 0x00, 8, &OpDispatchBuilder::FISTF64<true>},
|
|
|
|
{OPDReg(0xDD, 2) | 0x00, 8, &OpDispatchBuilder::FSTF64<64>},
|
|
|
|
{OPDReg(0xDD, 3) | 0x00, 8, &OpDispatchBuilder::FSTF64<64>},
|
|
|
|
{OPDReg(0xDD, 4) | 0x00, 8, &OpDispatchBuilder::X87FRSTORF64},
|
|
|
|
// 5 = Invalid
|
|
{OPDReg(0xDD, 6) | 0x00, 8, &OpDispatchBuilder::X87FNSAVEF64},
|
|
|
|
{OPDReg(0xDD, 7) | 0x00, 8, &OpDispatchBuilder::X87FNSTSW},
|
|
|
|
{OPD(0xDD, 0xC0), 8, &OpDispatchBuilder::X87FFREE},
|
|
{OPD(0xDD, 0xD0), 8, &OpDispatchBuilder::FST}, //register-register from regular X87
|
|
{OPD(0xDD, 0xD8), 8, &OpDispatchBuilder::FST}, //^
|
|
|
|
{OPD(0xDD, 0xE0), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xDD, 0xE8), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 0) | 0x00, 8, &OpDispatchBuilder::FADDF64<16, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 1) | 0x00, 8, &OpDispatchBuilder::FMULF64<16, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 2) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<16, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 3) | 0x00, 8, &OpDispatchBuilder::FCOMIF64<16, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 4) | 0x00, 8, &OpDispatchBuilder::FSUBF64<16, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 5) | 0x00, 8, &OpDispatchBuilder::FSUBF64<16, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 6) | 0x00, 8, &OpDispatchBuilder::FDIVF64<16, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 7) | 0x00, 8, &OpDispatchBuilder::FDIVF64<16, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDE, 0xC0), 8, &OpDispatchBuilder::FADDF64<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xC8), 8, &OpDispatchBuilder::FMULF64<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xD9), 1, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, true>},
|
|
{OPD(0xDE, 0xE0), 8, &OpDispatchBuilder::FSUBF64<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xE8), 8, &OpDispatchBuilder::FSUBF64<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xF0), 8, &OpDispatchBuilder::FDIVF64<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xF8), 8, &OpDispatchBuilder::FDIVF64<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
|
|
{OPDReg(0xDF, 0) | 0x00, 8, &OpDispatchBuilder::FILDF64},
|
|
|
|
{OPDReg(0xDF, 1) | 0x00, 8, &OpDispatchBuilder::FISTF64<true>},
|
|
|
|
{OPDReg(0xDF, 2) | 0x00, 8, &OpDispatchBuilder::FISTF64<false>},
|
|
|
|
{OPDReg(0xDF, 3) | 0x00, 8, &OpDispatchBuilder::FISTF64<false>},
|
|
|
|
{OPDReg(0xDF, 4) | 0x00, 8, &OpDispatchBuilder::FBLDF64},
|
|
|
|
{OPDReg(0xDF, 5) | 0x00, 8, &OpDispatchBuilder::FILDF64},
|
|
|
|
{OPDReg(0xDF, 6) | 0x00, 8, &OpDispatchBuilder::FBSTPF64},
|
|
|
|
{OPDReg(0xDF, 7) | 0x00, 8, &OpDispatchBuilder::FISTF64<false>},
|
|
|
|
// XXX: This should also set the x87 tag bits to empty
|
|
// We don't support this currently, so just pop the stack
|
|
{OPD(0xDF, 0xC0), 8, &OpDispatchBuilder::X87ModifySTP<true>},
|
|
|
|
{OPD(0xDF, 0xE0), 8, &OpDispatchBuilder::X87FNSTSW},
|
|
{OPD(0xDF, 0xE8), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
{OPD(0xDF, 0xF0), 8, &OpDispatchBuilder::FCOMIF64<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
};
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> X87OpTable[] = {
|
|
{OPDReg(0xD8, 0) | 0x00, 8, &OpDispatchBuilder::FADD<32, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 1) | 0x00, 8, &OpDispatchBuilder::FMUL<32, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 2) | 0x00, 8, &OpDispatchBuilder::FCOMI<32, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xD8, 3) | 0x00, 8, &OpDispatchBuilder::FCOMI<32, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xD8, 4) | 0x00, 8, &OpDispatchBuilder::FSUB<32, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 5) | 0x00, 8, &OpDispatchBuilder::FSUB<32, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 6) | 0x00, 8, &OpDispatchBuilder::FDIV<32, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD8, 7) | 0x00, 8, &OpDispatchBuilder::FDIV<32, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xD8, 0xC0), 8, &OpDispatchBuilder::FADD<80, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xC8), 8, &OpDispatchBuilder::FMUL<80, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xD0), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xD8, 0xD8), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xD8, 0xE0), 8, &OpDispatchBuilder::FSUB<80, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xE8), 8, &OpDispatchBuilder::FSUB<80, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xF0), 8, &OpDispatchBuilder::FDIV<80, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
{OPD(0xD8, 0xF8), 8, &OpDispatchBuilder::FDIV<80, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xD9, 0) | 0x00, 8, &OpDispatchBuilder::FLD<32>},
|
|
|
|
// 1 = Invalid
|
|
|
|
{OPDReg(0xD9, 2) | 0x00, 8, &OpDispatchBuilder::FST<32>},
|
|
|
|
{OPDReg(0xD9, 3) | 0x00, 8, &OpDispatchBuilder::FST<32>},
|
|
|
|
{OPDReg(0xD9, 4) | 0x00, 8, &OpDispatchBuilder::X87LDENV},
|
|
|
|
{OPDReg(0xD9, 5) | 0x00, 8, &OpDispatchBuilder::X87FLDCW}, // XXX: stubbed FLDCW
|
|
|
|
{OPDReg(0xD9, 6) | 0x00, 8, &OpDispatchBuilder::X87FNSTENV},
|
|
|
|
{OPDReg(0xD9, 7) | 0x00, 8, &OpDispatchBuilder::X87FSTCW},
|
|
|
|
{OPD(0xD9, 0xC0), 8, &OpDispatchBuilder::FLD<80>},
|
|
{OPD(0xD9, 0xC8), 8, &OpDispatchBuilder::FXCH},
|
|
{OPD(0xD9, 0xD0), 1, &OpDispatchBuilder::NOPOp}, // FNOP
|
|
// D1 = Invalid
|
|
// D8 = Invalid
|
|
{OPD(0xD9, 0xE0), 1, &OpDispatchBuilder::FCHS},
|
|
{OPD(0xD9, 0xE1), 1, &OpDispatchBuilder::FABS},
|
|
// E2 = Invalid
|
|
{OPD(0xD9, 0xE4), 1, &OpDispatchBuilder::FTST},
|
|
{OPD(0xD9, 0xE5), 1, &OpDispatchBuilder::X87FXAM},
|
|
// E6 = Invalid
|
|
{OPD(0xD9, 0xE8), 1, &OpDispatchBuilder::FLD_Const<0x8000'0000'0000'0000, 0b0'011'1111'1111'1111>}, // 1.0
|
|
{OPD(0xD9, 0xE9), 1, &OpDispatchBuilder::FLD_Const<0xD49A'784B'CD1B'8AFE, 0x4000>}, // log2l(10)
|
|
{OPD(0xD9, 0xEA), 1, &OpDispatchBuilder::FLD_Const<0xB8AA'3B29'5C17'F0BC, 0x3FFF>}, // log2l(e)
|
|
{OPD(0xD9, 0xEB), 1, &OpDispatchBuilder::FLD_Const<0xC90F'DAA2'2168'C235, 0x4000>}, // pi
|
|
{OPD(0xD9, 0xEC), 1, &OpDispatchBuilder::FLD_Const<0x9A20'9A84'FBCF'F799, 0x3FFD>}, // log10l(2)
|
|
{OPD(0xD9, 0xED), 1, &OpDispatchBuilder::FLD_Const<0xB172'17F7'D1CF'79AC, 0x3FFE>}, // log(2)
|
|
{OPD(0xD9, 0xEE), 1, &OpDispatchBuilder::FLD_Const<0, 0>}, // 0.0
|
|
|
|
// EF = Invalid
|
|
{OPD(0xD9, 0xF0), 1, &OpDispatchBuilder::X87UnaryOp<IR::OP_F80F2XM1>},
|
|
{OPD(0xD9, 0xF1), 1, &OpDispatchBuilder::X87FYL2X},
|
|
{OPD(0xD9, 0xF2), 1, &OpDispatchBuilder::X87TAN},
|
|
{OPD(0xD9, 0xF3), 1, &OpDispatchBuilder::X87ATAN},
|
|
{OPD(0xD9, 0xF4), 1, &OpDispatchBuilder::FXTRACT},
|
|
{OPD(0xD9, 0xF5), 1, &OpDispatchBuilder::X87BinaryOp<IR::OP_F80FPREM1>},
|
|
{OPD(0xD9, 0xF6), 1, &OpDispatchBuilder::X87ModifySTP<false>},
|
|
{OPD(0xD9, 0xF7), 1, &OpDispatchBuilder::X87ModifySTP<true>},
|
|
{OPD(0xD9, 0xF8), 1, &OpDispatchBuilder::X87BinaryOp<IR::OP_F80FPREM>},
|
|
{OPD(0xD9, 0xF9), 1, &OpDispatchBuilder::X87FYL2X},
|
|
{OPD(0xD9, 0xFA), 1, &OpDispatchBuilder::X87UnaryOp<IR::OP_F80SQRT>},
|
|
{OPD(0xD9, 0xFB), 1, &OpDispatchBuilder::X87SinCos},
|
|
{OPD(0xD9, 0xFC), 1, &OpDispatchBuilder::FRNDINT},
|
|
{OPD(0xD9, 0xFD), 1, &OpDispatchBuilder::X87BinaryOp<IR::OP_F80SCALE>},
|
|
{OPD(0xD9, 0xFE), 1, &OpDispatchBuilder::X87UnaryOp<IR::OP_F80SIN>},
|
|
{OPD(0xD9, 0xFF), 1, &OpDispatchBuilder::X87UnaryOp<IR::OP_F80COS>},
|
|
|
|
{OPDReg(0xDA, 0) | 0x00, 8, &OpDispatchBuilder::FADD<32, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 1) | 0x00, 8, &OpDispatchBuilder::FMUL<32, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 2) | 0x00, 8, &OpDispatchBuilder::FCOMI<32, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDA, 3) | 0x00, 8, &OpDispatchBuilder::FCOMI<32, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDA, 4) | 0x00, 8, &OpDispatchBuilder::FSUB<32, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 5) | 0x00, 8, &OpDispatchBuilder::FSUB<32, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 6) | 0x00, 8, &OpDispatchBuilder::FDIV<32, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDA, 7) | 0x00, 8, &OpDispatchBuilder::FDIV<32, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDA, 0xC0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xC8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xD0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDA, 0xD8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
// E0 = Invalid
|
|
// E8 = Invalid
|
|
{OPD(0xDA, 0xE9), 1, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, true>},
|
|
// EA = Invalid
|
|
// F0 = Invalid
|
|
// F8 = Invalid
|
|
|
|
{OPDReg(0xDB, 0) | 0x00, 8, &OpDispatchBuilder::FILD},
|
|
|
|
{OPDReg(0xDB, 1) | 0x00, 8, &OpDispatchBuilder::FIST<true>},
|
|
|
|
{OPDReg(0xDB, 2) | 0x00, 8, &OpDispatchBuilder::FIST<false>},
|
|
|
|
{OPDReg(0xDB, 3) | 0x00, 8, &OpDispatchBuilder::FIST<false>},
|
|
|
|
// 4 = Invalid
|
|
|
|
{OPDReg(0xDB, 5) | 0x00, 8, &OpDispatchBuilder::FLD<80>},
|
|
|
|
// 6 = Invalid
|
|
|
|
{OPDReg(0xDB, 7) | 0x00, 8, &OpDispatchBuilder::FST<80>},
|
|
|
|
|
|
{OPD(0xDB, 0xC0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xC8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xD0), 8, &OpDispatchBuilder::X87FCMOV},
|
|
{OPD(0xDB, 0xD8), 8, &OpDispatchBuilder::X87FCMOV},
|
|
// E0 = Invalid
|
|
{OPD(0xDB, 0xE2), 1, &OpDispatchBuilder::NOPOp}, // FNCLEX
|
|
{OPD(0xDB, 0xE3), 1, &OpDispatchBuilder::FNINIT},
|
|
// E4 = Invalid
|
|
{OPD(0xDB, 0xE8), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
{OPD(0xDB, 0xF0), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
|
|
// F8 = Invalid
|
|
|
|
{OPDReg(0xDC, 0) | 0x00, 8, &OpDispatchBuilder::FADD<64, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 1) | 0x00, 8, &OpDispatchBuilder::FMUL<64, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 2) | 0x00, 8, &OpDispatchBuilder::FCOMI<64, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDC, 3) | 0x00, 8, &OpDispatchBuilder::FCOMI<64, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDC, 4) | 0x00, 8, &OpDispatchBuilder::FSUB<64, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 5) | 0x00, 8, &OpDispatchBuilder::FSUB<64, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 6) | 0x00, 8, &OpDispatchBuilder::FDIV<64, false, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDC, 7) | 0x00, 8, &OpDispatchBuilder::FDIV<64, false, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDC, 0xC0), 8, &OpDispatchBuilder::FADD<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xC8), 8, &OpDispatchBuilder::FMUL<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xE0), 8, &OpDispatchBuilder::FSUB<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xE8), 8, &OpDispatchBuilder::FSUB<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xF0), 8, &OpDispatchBuilder::FDIV<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDC, 0xF8), 8, &OpDispatchBuilder::FDIV<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
|
|
{OPDReg(0xDD, 0) | 0x00, 8, &OpDispatchBuilder::FLD<64>},
|
|
|
|
{OPDReg(0xDD, 1) | 0x00, 8, &OpDispatchBuilder::FIST<true>},
|
|
|
|
{OPDReg(0xDD, 2) | 0x00, 8, &OpDispatchBuilder::FST<64>},
|
|
|
|
{OPDReg(0xDD, 3) | 0x00, 8, &OpDispatchBuilder::FST<64>},
|
|
|
|
{OPDReg(0xDD, 4) | 0x00, 8, &OpDispatchBuilder::X87FRSTOR},
|
|
|
|
// 5 = Invalid
|
|
{OPDReg(0xDD, 6) | 0x00, 8, &OpDispatchBuilder::X87FNSAVE},
|
|
|
|
{OPDReg(0xDD, 7) | 0x00, 8, &OpDispatchBuilder::X87FNSTSW},
|
|
|
|
{OPD(0xDD, 0xC0), 8, &OpDispatchBuilder::X87FFREE},
|
|
{OPD(0xDD, 0xD0), 8, &OpDispatchBuilder::FST},
|
|
{OPD(0xDD, 0xD8), 8, &OpDispatchBuilder::FST},
|
|
|
|
{OPD(0xDD, 0xE0), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
{OPD(0xDD, 0xE8), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 0) | 0x00, 8, &OpDispatchBuilder::FADD<16, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 1) | 0x00, 8, &OpDispatchBuilder::FMUL<16, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 2) | 0x00, 8, &OpDispatchBuilder::FCOMI<16, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 3) | 0x00, 8, &OpDispatchBuilder::FCOMI<16, true, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, false>},
|
|
|
|
{OPDReg(0xDE, 4) | 0x00, 8, &OpDispatchBuilder::FSUB<16, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 5) | 0x00, 8, &OpDispatchBuilder::FSUB<16, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 6) | 0x00, 8, &OpDispatchBuilder::FDIV<16, true, false, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPDReg(0xDE, 7) | 0x00, 8, &OpDispatchBuilder::FDIV<16, true, true, OpDispatchBuilder::OpResult::RES_ST0>},
|
|
|
|
{OPD(0xDE, 0xC0), 8, &OpDispatchBuilder::FADD<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xC8), 8, &OpDispatchBuilder::FMUL<80, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xD9), 1, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_X87, true>},
|
|
{OPD(0xDE, 0xE0), 8, &OpDispatchBuilder::FSUB<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xE8), 8, &OpDispatchBuilder::FSUB<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xF0), 8, &OpDispatchBuilder::FDIV<80, false, false, OpDispatchBuilder::OpResult::RES_STI>},
|
|
{OPD(0xDE, 0xF8), 8, &OpDispatchBuilder::FDIV<80, false, true, OpDispatchBuilder::OpResult::RES_STI>},
|
|
|
|
{OPDReg(0xDF, 0) | 0x00, 8, &OpDispatchBuilder::FILD},
|
|
|
|
{OPDReg(0xDF, 1) | 0x00, 8, &OpDispatchBuilder::FIST<true>},
|
|
|
|
{OPDReg(0xDF, 2) | 0x00, 8, &OpDispatchBuilder::FIST<false>},
|
|
|
|
{OPDReg(0xDF, 3) | 0x00, 8, &OpDispatchBuilder::FIST<false>},
|
|
|
|
{OPDReg(0xDF, 4) | 0x00, 8, &OpDispatchBuilder::FBLD},
|
|
|
|
{OPDReg(0xDF, 5) | 0x00, 8, &OpDispatchBuilder::FILD},
|
|
|
|
{OPDReg(0xDF, 6) | 0x00, 8, &OpDispatchBuilder::FBSTP},
|
|
|
|
{OPDReg(0xDF, 7) | 0x00, 8, &OpDispatchBuilder::FIST<false>},
|
|
|
|
// XXX: This should also set the x87 tag bits to empty
|
|
// We don't support this currently, so just pop the stack
|
|
{OPD(0xDF, 0xC0), 8, &OpDispatchBuilder::X87ModifySTP<true>},
|
|
|
|
{OPD(0xDF, 0xE0), 8, &OpDispatchBuilder::X87FNSTSW},
|
|
{OPD(0xDF, 0xE8), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
{OPD(0xDF, 0xF0), 8, &OpDispatchBuilder::FCOMI<80, false, OpDispatchBuilder::FCOMIFlags::FLAGS_RFLAGS, false>},
|
|
};
|
|
#undef OPD
|
|
#undef OPDReg
|
|
|
|
#define OPD(prefix, opcode) (((prefix) << 8) | opcode)
|
|
constexpr uint16_t PF_38_NONE = 0;
|
|
constexpr uint16_t PF_38_66 = (1U << 0);
|
|
constexpr uint16_t PF_38_F3 = (1U << 2);
|
|
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F38Table[] = {
|
|
{OPD(PF_38_NONE, 0x00), 1, &OpDispatchBuilder::PSHUFBOp},
|
|
{OPD(PF_38_66, 0x00), 1, &OpDispatchBuilder::PSHUFBOp},
|
|
{OPD(PF_38_NONE, 0x01), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADDP, 2>},
|
|
{OPD(PF_38_66, 0x01), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADDP, 2>},
|
|
{OPD(PF_38_NONE, 0x02), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADDP, 4>},
|
|
{OPD(PF_38_66, 0x02), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VADDP, 4>},
|
|
{OPD(PF_38_NONE, 0x03), 1, &OpDispatchBuilder::PHADDS},
|
|
{OPD(PF_38_66, 0x03), 1, &OpDispatchBuilder::PHADDS},
|
|
{OPD(PF_38_NONE, 0x04), 1, &OpDispatchBuilder::PMADDUBSW},
|
|
{OPD(PF_38_66, 0x04), 1, &OpDispatchBuilder::PMADDUBSW},
|
|
{OPD(PF_38_NONE, 0x05), 1, &OpDispatchBuilder::PHSUB<2>},
|
|
{OPD(PF_38_66, 0x05), 1, &OpDispatchBuilder::PHSUB<2>},
|
|
{OPD(PF_38_NONE, 0x06), 1, &OpDispatchBuilder::PHSUB<4>},
|
|
{OPD(PF_38_66, 0x06), 1, &OpDispatchBuilder::PHSUB<4>},
|
|
{OPD(PF_38_NONE, 0x07), 1, &OpDispatchBuilder::PHSUBS},
|
|
{OPD(PF_38_66, 0x07), 1, &OpDispatchBuilder::PHSUBS},
|
|
{OPD(PF_38_NONE, 0x08), 1, &OpDispatchBuilder::PSIGN<1>},
|
|
{OPD(PF_38_66, 0x08), 1, &OpDispatchBuilder::PSIGN<1>},
|
|
{OPD(PF_38_NONE, 0x09), 1, &OpDispatchBuilder::PSIGN<2>},
|
|
{OPD(PF_38_66, 0x09), 1, &OpDispatchBuilder::PSIGN<2>},
|
|
{OPD(PF_38_NONE, 0x0A), 1, &OpDispatchBuilder::PSIGN<4>},
|
|
{OPD(PF_38_66, 0x0A), 1, &OpDispatchBuilder::PSIGN<4>},
|
|
{OPD(PF_38_NONE, 0x0B), 1, &OpDispatchBuilder::PMULHRSW},
|
|
{OPD(PF_38_66, 0x0B), 1, &OpDispatchBuilder::PMULHRSW},
|
|
{OPD(PF_38_66, 0x10), 1, &OpDispatchBuilder::VectorVariableBlend<1>},
|
|
{OPD(PF_38_66, 0x14), 1, &OpDispatchBuilder::VectorVariableBlend<4>},
|
|
{OPD(PF_38_66, 0x15), 1, &OpDispatchBuilder::VectorVariableBlend<8>},
|
|
{OPD(PF_38_66, 0x17), 1, &OpDispatchBuilder::PTestOp},
|
|
{OPD(PF_38_NONE, 0x1C), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 1, false>},
|
|
{OPD(PF_38_66, 0x1C), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 1, false>},
|
|
{OPD(PF_38_NONE, 0x1D), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 2, false>},
|
|
{OPD(PF_38_66, 0x1D), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 2, false>},
|
|
{OPD(PF_38_NONE, 0x1E), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 4, false>},
|
|
{OPD(PF_38_66, 0x1E), 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VABS, 4, false>},
|
|
{OPD(PF_38_66, 0x20), 1, &OpDispatchBuilder::ExtendVectorElements<1, 2, true>},
|
|
{OPD(PF_38_66, 0x21), 1, &OpDispatchBuilder::ExtendVectorElements<1, 4, true>},
|
|
{OPD(PF_38_66, 0x22), 1, &OpDispatchBuilder::ExtendVectorElements<1, 8, true>},
|
|
{OPD(PF_38_66, 0x23), 1, &OpDispatchBuilder::ExtendVectorElements<2, 4, true>},
|
|
{OPD(PF_38_66, 0x24), 1, &OpDispatchBuilder::ExtendVectorElements<2, 8, true>},
|
|
{OPD(PF_38_66, 0x25), 1, &OpDispatchBuilder::ExtendVectorElements<4, 8, true>},
|
|
{OPD(PF_38_66, 0x28), 1, &OpDispatchBuilder::PMULLOp<4, true>},
|
|
{OPD(PF_38_66, 0x29), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPEQ, 8>},
|
|
{OPD(PF_38_66, 0x2A), 1, &OpDispatchBuilder::MOVVectorNTOp},
|
|
{OPD(PF_38_66, 0x2B), 1, &OpDispatchBuilder::PACKUSOp<4>},
|
|
{OPD(PF_38_66, 0x30), 1, &OpDispatchBuilder::ExtendVectorElements<1, 2, false>},
|
|
{OPD(PF_38_66, 0x31), 1, &OpDispatchBuilder::ExtendVectorElements<1, 4, false>},
|
|
{OPD(PF_38_66, 0x32), 1, &OpDispatchBuilder::ExtendVectorElements<1, 8, false>},
|
|
{OPD(PF_38_66, 0x33), 1, &OpDispatchBuilder::ExtendVectorElements<2, 4, false>},
|
|
{OPD(PF_38_66, 0x34), 1, &OpDispatchBuilder::ExtendVectorElements<2, 8, false>},
|
|
{OPD(PF_38_66, 0x35), 1, &OpDispatchBuilder::ExtendVectorElements<4, 8, false>},
|
|
{OPD(PF_38_66, 0x37), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VCMPGT, 8>},
|
|
{OPD(PF_38_66, 0x38), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMIN, 1>},
|
|
{OPD(PF_38_66, 0x39), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMIN, 4>},
|
|
{OPD(PF_38_66, 0x3A), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMIN, 2>},
|
|
{OPD(PF_38_66, 0x3B), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMIN, 4>},
|
|
{OPD(PF_38_66, 0x3C), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMAX, 1>},
|
|
{OPD(PF_38_66, 0x3D), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VSMAX, 4>},
|
|
{OPD(PF_38_66, 0x3E), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMAX, 2>},
|
|
{OPD(PF_38_66, 0x3F), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VUMAX, 4>},
|
|
{OPD(PF_38_66, 0x40), 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VMUL, 4>},
|
|
{OPD(PF_38_66, 0x41), 1, &OpDispatchBuilder::PHMINPOSUWOp},
|
|
|
|
{OPD(PF_38_NONE, 0xF0), 2, &OpDispatchBuilder::MOVBEOp},
|
|
{OPD(PF_38_66, 0xF0), 2, &OpDispatchBuilder::MOVBEOp},
|
|
|
|
{OPD(PF_38_66, 0xF6), 1, &OpDispatchBuilder::ADXOp},
|
|
{OPD(PF_38_F3, 0xF6), 1, &OpDispatchBuilder::ADXOp},
|
|
};
|
|
|
|
#undef OPD
|
|
|
|
#define OPD(REX, prefix, opcode) ((REX << 9) | (prefix << 8) | opcode)
|
|
#define PF_3A_NONE 0
|
|
#define PF_3A_66 1
|
|
constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> H0F3ATable[] = {
|
|
{OPD(0, PF_3A_66, 0x08), 1, &OpDispatchBuilder::VectorRound<4, false>},
|
|
{OPD(0, PF_3A_66, 0x09), 1, &OpDispatchBuilder::VectorRound<8, false>},
|
|
{OPD(0, PF_3A_66, 0x0A), 1, &OpDispatchBuilder::VectorRound<4, true>},
|
|
{OPD(0, PF_3A_66, 0x0B), 1, &OpDispatchBuilder::VectorRound<8, true>},
|
|
{OPD(0, PF_3A_66, 0x0C), 1, &OpDispatchBuilder::VectorBlend<4>},
|
|
{OPD(0, PF_3A_66, 0x0D), 1, &OpDispatchBuilder::VectorBlend<8>},
|
|
{OPD(0, PF_3A_66, 0x0E), 1, &OpDispatchBuilder::VectorBlend<2>},
|
|
|
|
{OPD(0, PF_3A_NONE, 0x0F), 1, &OpDispatchBuilder::PAlignrOp},
|
|
{OPD(0, PF_3A_66, 0x0F), 1, &OpDispatchBuilder::PAlignrOp},
|
|
{OPD(1, PF_3A_66, 0x0F), 1, &OpDispatchBuilder::PAlignrOp},
|
|
|
|
{OPD(0, PF_3A_66, 0x14), 1, &OpDispatchBuilder::PExtrOp<1>},
|
|
{OPD(0, PF_3A_66, 0x15), 1, &OpDispatchBuilder::PExtrOp<2>},
|
|
{OPD(0, PF_3A_66, 0x16), 1, &OpDispatchBuilder::PExtrOp<4>},
|
|
{OPD(1, PF_3A_66, 0x16), 1, &OpDispatchBuilder::PExtrOp<8>},
|
|
{OPD(0, PF_3A_66, 0x17), 1, &OpDispatchBuilder::PExtrOp<4>},
|
|
|
|
{OPD(0, PF_3A_66, 0x20), 1, &OpDispatchBuilder::PINSROp<1>},
|
|
{OPD(0, PF_3A_66, 0x21), 1, &OpDispatchBuilder::InsertPSOp},
|
|
{OPD(0, PF_3A_66, 0x22), 1, &OpDispatchBuilder::PINSROp<4>},
|
|
{OPD(1, PF_3A_66, 0x22), 1, &OpDispatchBuilder::PINSROp<8>},
|
|
{OPD(0, PF_3A_66, 0x40), 1, &OpDispatchBuilder::DPPOp<4>},
|
|
{OPD(0, PF_3A_66, 0x41), 1, &OpDispatchBuilder::DPPOp<8>},
|
|
{OPD(0, PF_3A_66, 0x42), 1, &OpDispatchBuilder::MPSADBWOp},
|
|
|
|
{OPD(0, PF_3A_66, 0x60), 1, &OpDispatchBuilder::VPCMPESTRMOp},
|
|
{OPD(0, PF_3A_66, 0x61), 1, &OpDispatchBuilder::VPCMPESTRIOp},
|
|
{OPD(0, PF_3A_66, 0x62), 1, &OpDispatchBuilder::VPCMPISTRMOp},
|
|
{OPD(0, PF_3A_66, 0x63), 1, &OpDispatchBuilder::VPCMPISTRIOp},
|
|
|
|
{OPD(0, PF_3A_NONE, 0xCC), 1, &OpDispatchBuilder::SHA1RNDS4Op},
|
|
};
|
|
#undef PF_3A_NONE
|
|
#undef PF_3A_66
|
|
|
|
#undef OPD
|
|
|
|
static constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> DDDNowTable[] = {
|
|
{0x0C, 1, &OpDispatchBuilder::PI2FWOp},
|
|
{0x0D, 1, &OpDispatchBuilder::Vector_CVT_Int_To_Float<4, false>},
|
|
{0x1C, 1, &OpDispatchBuilder::PF2IWOp},
|
|
{0x1D, 1, &OpDispatchBuilder::Vector_CVT_Float_To_Int<4, false, false>},
|
|
|
|
{0x86, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRECP, 4, false>},
|
|
{0x87, 1, &OpDispatchBuilder::VectorUnaryOp<IR::OP_VFRSQRT, 4, false>},
|
|
|
|
{0x8A, 1, &OpDispatchBuilder::PFNACCOp},
|
|
{0x8E, 1, &OpDispatchBuilder::PFPNACCOp},
|
|
|
|
{0x90, 1, &OpDispatchBuilder::VPFCMPOp<1>},
|
|
{0x94, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMIN, 4>},
|
|
{0x96, 1, &OpDispatchBuilder::VectorUnaryDuplicateOp<IR::OP_VFRECP, 4>},
|
|
{0x97, 1, &OpDispatchBuilder::VectorUnaryDuplicateOp<IR::OP_VFRSQRT, 4>},
|
|
|
|
{0x9A, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFSUB, 4>},
|
|
{0x9E, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADD, 4>},
|
|
|
|
{0xA0, 1, &OpDispatchBuilder::VPFCMPOp<2>},
|
|
{0xA4, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMAX, 4>},
|
|
// Can be treated as a move
|
|
{0xA6, 1, &OpDispatchBuilder::MOVVectorOp},
|
|
{0xA7, 1, &OpDispatchBuilder::MOVVectorOp},
|
|
|
|
{0xAA, 1, &OpDispatchBuilder::VectorALUROp<IR::OP_VFSUB, 4>},
|
|
{0xAE, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFADDP, 4>},
|
|
|
|
{0xB0, 1, &OpDispatchBuilder::VPFCMPOp<0>},
|
|
{0xB4, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VFMUL, 4>},
|
|
// Can be treated as a move
|
|
{0xB6, 1, &OpDispatchBuilder::MOVVectorOp},
|
|
{0xB7, 1, &OpDispatchBuilder::PMULHRWOp},
|
|
|
|
{0xBB, 1, &OpDispatchBuilder::PSWAPDOp},
|
|
{0xBF, 1, &OpDispatchBuilder::VectorALUOp<IR::OP_VURAVG, 1>},
|
|
};
|
|
|
|
#define OPD(map_select, pp, opcode) (((map_select - 1) << 10) | (pp << 8) | (opcode))
|
|
static constexpr std::tuple<uint16_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> BMITable[] = {
|
|
{OPD(2, 0b00, 0xF2), 1, &OpDispatchBuilder::ANDNBMIOp},
|
|
{OPD(2, 0b00, 0xF5), 1, &OpDispatchBuilder::BZHI},
|
|
{OPD(2, 0b10, 0xF5), 1, &OpDispatchBuilder::PEXT},
|
|
{OPD(2, 0b11, 0xF5), 1, &OpDispatchBuilder::PDEP},
|
|
{OPD(2, 0b11, 0xF6), 1, &OpDispatchBuilder::MULX},
|
|
{OPD(2, 0b00, 0xF7), 1, &OpDispatchBuilder::BEXTRBMIOp},
|
|
{OPD(2, 0b01, 0xF7), 1, &OpDispatchBuilder::BMI2Shift},
|
|
{OPD(2, 0b10, 0xF7), 1, &OpDispatchBuilder::BMI2Shift},
|
|
{OPD(2, 0b11, 0xF7), 1, &OpDispatchBuilder::BMI2Shift},
|
|
|
|
{OPD(3, 0b11, 0xF0), 1, &OpDispatchBuilder::RORX},
|
|
};
|
|
#undef OPD
|
|
|
|
#define OPD(group, pp, opcode) (((group - X86Tables::InstType::TYPE_VEX_GROUP_12) << 4) | (pp << 3) | (opcode))
|
|
constexpr std::tuple<uint8_t, uint8_t, X86Tables::OpDispatchPtr> VEXGroupTable[] = {
|
|
{OPD(X86Tables::InstType::TYPE_VEX_GROUP_17, 0, 0b001), 1, &OpDispatchBuilder::BLSRBMIOp},
|
|
{OPD(X86Tables::InstType::TYPE_VEX_GROUP_17, 0, 0b010), 1, &OpDispatchBuilder::BLSMSKBMIOp},
|
|
{OPD(X86Tables::InstType::TYPE_VEX_GROUP_17, 0, 0b011), 1, &OpDispatchBuilder::BLSIBMIOp},
|
|
};
|
|
#undef OPD
|
|
|
|
constexpr std::tuple<uint8_t, uint8_t, FEXCore::X86Tables::OpDispatchPtr> EVEXTable[] = {
|
|
{0x10, 2, &OpDispatchBuilder::UnimplementedOp},
|
|
{0x59, 1, &OpDispatchBuilder::UnimplementedOp},
|
|
{0x7F, 1, &OpDispatchBuilder::UnimplementedOp},
|
|
};
|
|
|
|
auto InstallToTable = [](auto& FinalTable, auto& LocalTable) {
|
|
for (auto Op : LocalTable) {
|
|
auto OpNum = std::get<0>(Op);
|
|
auto Dispatcher = std::get<2>(Op);
|
|
for (uint8_t i = 0; i < std::get<1>(Op); ++i) {
|
|
LOGMAN_THROW_A_FMT(FinalTable[OpNum + i].OpcodeDispatcher == nullptr, "Duplicate Entry");
|
|
FinalTable[OpNum + i].OpcodeDispatcher = Dispatcher;
|
|
}
|
|
}
|
|
};
|
|
auto InstallToX87Table = [](auto& FinalTable, auto& LocalTable) {
|
|
for (auto Op : LocalTable) {
|
|
auto OpNum = std::get<0>(Op);
|
|
bool Repeat = (OpNum & 0x8000) != 0;
|
|
OpNum = OpNum & 0x7FF;
|
|
auto Dispatcher = std::get<2>(Op);
|
|
for (uint8_t i = 0; i < std::get<1>(Op); ++i) {
|
|
LOGMAN_THROW_A_FMT(FinalTable[OpNum + i].OpcodeDispatcher == nullptr, "Duplicate Entry");
|
|
FinalTable[OpNum + i].OpcodeDispatcher = Dispatcher;
|
|
|
|
// Flag to indicate if we need to repeat this op in {0x40, 0x80} ranges
|
|
if (Repeat) {
|
|
FinalTable[(OpNum | 0x40) + i].OpcodeDispatcher = Dispatcher;
|
|
FinalTable[(OpNum | 0x80) + i].OpcodeDispatcher = Dispatcher;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
InstallToTable(FEXCore::X86Tables::BaseOps, BaseOpTable);
|
|
if (Mode == Context::MODE_32BIT) {
|
|
InstallToTable(FEXCore::X86Tables::BaseOps, BaseOpTable_32);
|
|
InstallToTable(FEXCore::X86Tables::SecondBaseOps, TwoByteOpTable_32);
|
|
}
|
|
else {
|
|
InstallToTable(FEXCore::X86Tables::BaseOps, BaseOpTable_64);
|
|
InstallToTable(FEXCore::X86Tables::SecondBaseOps, TwoByteOpTable_64);
|
|
}
|
|
|
|
InstallToTable(FEXCore::X86Tables::SecondBaseOps, TwoByteOpTable);
|
|
InstallToTable(FEXCore::X86Tables::PrimaryInstGroupOps, PrimaryGroupOpTable);
|
|
|
|
InstallToTable(FEXCore::X86Tables::RepModOps, RepModOpTable);
|
|
InstallToTable(FEXCore::X86Tables::RepNEModOps, RepNEModOpTable);
|
|
InstallToTable(FEXCore::X86Tables::OpSizeModOps, OpSizeModOpTable);
|
|
InstallToTable(FEXCore::X86Tables::SecondInstGroupOps, SecondaryExtensionOpTable);
|
|
if (Mode == Context::MODE_64BIT) {
|
|
InstallToTable(FEXCore::X86Tables::SecondInstGroupOps, SecondaryExtensionOpTable_64);
|
|
}
|
|
|
|
InstallToTable(FEXCore::X86Tables::SecondModRMTableOps, SecondaryModRMExtensionOpTable);
|
|
|
|
FEX_CONFIG_OPT(ReducedPrecision, X87REDUCEDPRECISION);
|
|
if(ReducedPrecision) {
|
|
InstallToX87Table(FEXCore::X86Tables::X87Ops, X87F64OpTable);
|
|
} else {
|
|
InstallToX87Table(FEXCore::X86Tables::X87Ops, X87OpTable);
|
|
}
|
|
|
|
InstallToTable(FEXCore::X86Tables::H0F38TableOps, H0F38Table);
|
|
InstallToTable(FEXCore::X86Tables::H0F3ATableOps, H0F3ATable);
|
|
InstallToTable(FEXCore::X86Tables::DDDNowOps, DDDNowTable);
|
|
InstallToTable(FEXCore::X86Tables::VEXTableOps, BMITable);
|
|
InstallToTable(FEXCore::X86Tables::VEXTableGroupOps, VEXGroupTable);
|
|
InstallToTable(FEXCore::X86Tables::EVEXTableOps, EVEXTable);
|
|
}
|
|
|
|
}
|