From e317424b86bca6eb44d20abaeab20a6558ec14fd Mon Sep 17 00:00:00 2001 From: lioncash Date: Thu, 4 Nov 2021 15:33:12 -0400 Subject: [PATCH 1/4] X86Tables: Increase InstFlags to uint64_t We've run out of the range of 32 bits already and will need to use another flag in upcoming changes, so we need to expand our flags to be 64-bit. While we're at it, we can use a dedicated type alias for the instruction flags to make the interface changeable from one spot in the future. --- .../Source/Interface/Core/Frontend.cpp | 4 +- .../FEXCore/include/FEXCore/Debug/X86Tables.h | 105 +++++++++--------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/External/FEXCore/Source/Interface/Core/Frontend.cpp b/External/FEXCore/Source/Interface/Core/Frontend.cpp index 60ad0722d..1f32dcb4c 100644 --- a/External/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/External/FEXCore/Source/Interface/Core/Frontend.cpp @@ -450,8 +450,8 @@ bool Decoder::NormalOp(FEXCore::X86Tables::X86InstInfo const *Info, uint16_t Op, // New instruction size decoding { // Decode destinations first - uint32_t DstSizeFlag = FEXCore::X86Tables::InstFlags::GetSizeDstFlags(Info->Flags); - uint32_t SrcSizeFlag = FEXCore::X86Tables::InstFlags::GetSizeSrcFlags(Info->Flags); + const auto DstSizeFlag = FEXCore::X86Tables::InstFlags::GetSizeDstFlags(Info->Flags); + const auto SrcSizeFlag = FEXCore::X86Tables::InstFlags::GetSizeSrcFlags(Info->Flags); if (DstSizeFlag == FEXCore::X86Tables::InstFlags::SIZE_8BIT) { DecodeInst->Flags |= DecodeFlags::GenSizeDstSize(DecodeFlags::SIZE_8BIT); diff --git a/External/FEXCore/include/FEXCore/Debug/X86Tables.h b/External/FEXCore/include/FEXCore/Debug/X86Tables.h index 43a58c214..5ef66bc72 100644 --- a/External/FEXCore/include/FEXCore/Debug/X86Tables.h +++ b/External/FEXCore/include/FEXCore/Debug/X86Tables.h @@ -272,97 +272,100 @@ enum InstType { }; namespace InstFlags { -constexpr uint32_t FLAGS_NONE = 0; -constexpr uint32_t FLAGS_DEBUG = (1 << 1); -constexpr uint32_t FLAGS_DEBUG_MEM_ACCESS = (1 << 2); -constexpr uint32_t FLAGS_SUPPORTS_REP = (1 << 3); -constexpr uint32_t FLAGS_BLOCK_END = (1 << 4); -constexpr uint32_t FLAGS_SETS_RIP = (1 << 5); -constexpr uint32_t FLAGS_DISPLACE_SIZE_MUL_2 = (1 << 6); -constexpr uint32_t FLAGS_DISPLACE_SIZE_DIV_2 = (1 << 7); -constexpr uint32_t FLAGS_SRC_SEXT = (1 << 8); -constexpr uint32_t FLAGS_MEM_OFFSET = (1 << 9); +using InstFlagType = uint64_t; + +constexpr InstFlagType FLAGS_NONE = 0; +constexpr InstFlagType FLAGS_DEBUG = (1ULL << 1); +constexpr InstFlagType FLAGS_DEBUG_MEM_ACCESS = (1ULL << 2); +constexpr InstFlagType FLAGS_SUPPORTS_REP = (1ULL << 3); +constexpr InstFlagType FLAGS_BLOCK_END = (1ULL << 4); +constexpr InstFlagType FLAGS_SETS_RIP = (1ULL << 5); + +constexpr InstFlagType FLAGS_DISPLACE_SIZE_MUL_2 = (1ULL << 6); +constexpr InstFlagType FLAGS_DISPLACE_SIZE_DIV_2 = (1ULL << 7); +constexpr InstFlagType FLAGS_SRC_SEXT = (1ULL << 8); +constexpr InstFlagType FLAGS_MEM_OFFSET = (1ULL << 9); // Enables XMM based subflags // Current reserved range for this SF is [10, 15] -constexpr uint32_t FLAGS_XMM_FLAGS = (1 << 10); +constexpr InstFlagType FLAGS_XMM_FLAGS = (1ULL << 10); // X87 flags aliased to XMM flags selection // Allows X87 instruction table that is abusing the flag for 64BIT selection to work -constexpr uint32_t FLAGS_X87_FLAGS = (1 << 10); +constexpr InstFlagType FLAGS_X87_FLAGS = (1ULL << 10); // Non-XMM subflags - constexpr uint32_t FLAGS_SF_DST_RAX = (1 << 11); - constexpr uint32_t FLAGS_SF_DST_RDX = (1 << 12); - constexpr uint32_t FLAGS_SF_SRC_RAX = (1 << 13); - constexpr uint32_t FLAGS_SF_SRC_RCX = (1 << 14); - constexpr uint32_t FLAGS_SF_REX_IN_BYTE = (1 << 15); + constexpr InstFlagType FLAGS_SF_DST_RAX = (1ULL << 11); + constexpr InstFlagType FLAGS_SF_DST_RDX = (1ULL << 12); + constexpr InstFlagType FLAGS_SF_SRC_RAX = (1ULL << 13); + constexpr InstFlagType FLAGS_SF_SRC_RCX = (1ULL << 14); + constexpr InstFlagType FLAGS_SF_REX_IN_BYTE = (1ULL << 15); // XMM subflags - constexpr uint32_t FLAGS_SF_HIGH_XMM_REG = (1 << 11); - constexpr uint32_t FLAGS_SF_DST_GPR = (1 << 12); - constexpr uint32_t FLAGS_SF_SRC_GPR = (1 << 13); - constexpr uint32_t FLAGS_SF_MMX = (3 << 14); // MMX_DST | MMX_SRC - constexpr uint32_t FLAGS_SF_MMX_DST = (1 << 14); - constexpr uint32_t FLAGS_SF_MMX_SRC = (1 << 15); + constexpr InstFlagType FLAGS_SF_HIGH_XMM_REG = (1ULL << 11); + constexpr InstFlagType FLAGS_SF_DST_GPR = (1ULL << 12); + constexpr InstFlagType FLAGS_SF_SRC_GPR = (1ULL << 13); + constexpr InstFlagType FLAGS_SF_MMX_DST = (1ULL << 14); + constexpr InstFlagType FLAGS_SF_MMX_SRC = (1ULL << 15); + constexpr InstFlagType FLAGS_SF_MMX = FLAGS_SF_MMX_DST | FLAGS_SF_MMX_SRC; // Enables MODRM specific subflags // Current reserved range for this SF is [14, 17] -constexpr uint32_t FLAGS_MODRM = (1 << 16); +constexpr InstFlagType FLAGS_MODRM = (1ULL << 16); // With ModRM SF flag enabled // Direction of ModRM. Dst ^ Src // Set means destination is rm bits // Unset means src is rm bits - constexpr uint32_t FLAGS_SF_MOD_DST = (1 << 17); + constexpr InstFlagType FLAGS_SF_MOD_DST = (1ULL << 17); // If the instruction is restricted to mem or reg only // 0b00 = Regular ModRM support // 0b01 = Memory accesses only // 0b10 = Register accesses only // 0b11 = - constexpr uint32_t FLAGS_SF_MOD_MEM_ONLY = (1 << 18); - constexpr uint32_t FLAGS_SF_MOD_REG_ONLY = (1 << 19); + constexpr InstFlagType FLAGS_SF_MOD_MEM_ONLY = (1ULL << 18); + constexpr InstFlagType FLAGS_SF_MOD_REG_ONLY = (1ULL << 19); // The secondary Opcode Map uses prefix bytes to overlay more instruction // But some instructions need to ignore this overlay and consume these prefixes. -constexpr uint32_t FLAGS_NO_OVERLAY = (1 << 20); +constexpr InstFlagType FLAGS_NO_OVERLAY = (1ULL << 20); // Some instructions partially ignore overlay // Ignore OpSize (0x66) in this case -constexpr uint32_t FLAGS_NO_OVERLAY66 = (1 << 21); +constexpr InstFlagType FLAGS_NO_OVERLAY66 = (1ULL << 21); // x87 -constexpr uint32_t FLAGS_POP = (1 << 22); +constexpr InstFlagType FLAGS_POP = (1ULL << 22); // Only SEXT if the instruction is operating in 64bit operand size -constexpr uint32_t FLAGS_SRC_SEXT64BIT = (1 << 23); +constexpr InstFlagType FLAGS_SRC_SEXT64BIT = (1ULL << 23); // Whether or not the instruction has a VEX prefix for the first source operand -constexpr uint32_t FLAGS_VEX_1ST_SRC = (1 << 24); +constexpr InstFlagType FLAGS_VEX_1ST_SRC = (1ULL << 24); // Whether or not the instruction has a VEX prefix for the second source operand -constexpr uint32_t FLAGS_VEX_2ND_SRC = (1 << 25); +constexpr InstFlagType FLAGS_VEX_2ND_SRC = (1ULL << 25); -constexpr uint32_t FLAGS_SIZE_DST_OFF = 26; -constexpr uint32_t FLAGS_SIZE_SRC_OFF = FLAGS_SIZE_DST_OFF + 3; +constexpr InstFlagType FLAGS_SIZE_DST_OFF = 26; +constexpr InstFlagType FLAGS_SIZE_SRC_OFF = FLAGS_SIZE_DST_OFF + 3; -constexpr uint32_t SIZE_MASK = 0b111; -constexpr uint32_t SIZE_DEF = 0b000; -constexpr uint32_t SIZE_8BIT = 0b001; -constexpr uint32_t SIZE_16BIT = 0b010; -constexpr uint32_t SIZE_32BIT = 0b011; -constexpr uint32_t SIZE_64BIT = 0b100; -constexpr uint32_t SIZE_128BIT = 0b101; -constexpr uint32_t SIZE_256BIT = 0b110; -constexpr uint32_t SIZE_64BITDEF = 0b111; // Default mode is 64bit instead of typical 32bit +constexpr InstFlagType SIZE_MASK = 0b111; +constexpr InstFlagType SIZE_DEF = 0b000; +constexpr InstFlagType SIZE_8BIT = 0b001; +constexpr InstFlagType SIZE_16BIT = 0b010; +constexpr InstFlagType SIZE_32BIT = 0b011; +constexpr InstFlagType SIZE_64BIT = 0b100; +constexpr InstFlagType SIZE_128BIT = 0b101; +constexpr InstFlagType SIZE_256BIT = 0b110; +constexpr InstFlagType SIZE_64BITDEF = 0b111; // Default mode is 64bit instead of typical 32bit -constexpr uint32_t GetSizeDstFlags(uint32_t Flags) { return (Flags >> FLAGS_SIZE_DST_OFF) & SIZE_MASK; } -constexpr uint32_t GetSizeSrcFlags(uint32_t Flags) { return (Flags >> FLAGS_SIZE_SRC_OFF) & SIZE_MASK; } +constexpr InstFlagType GetSizeDstFlags(InstFlagType Flags) { return (Flags >> FLAGS_SIZE_DST_OFF) & SIZE_MASK; } +constexpr InstFlagType GetSizeSrcFlags(InstFlagType Flags) { return (Flags >> FLAGS_SIZE_SRC_OFF) & SIZE_MASK; } -constexpr uint32_t GenFlagsDstSize(uint32_t Size) { return Size << FLAGS_SIZE_DST_OFF; } -constexpr uint32_t GenFlagsSrcSize(uint32_t Size) { return Size << FLAGS_SIZE_SRC_OFF; } -constexpr uint32_t GenFlagsSameSize(uint32_t Size) { return (Size << FLAGS_SIZE_DST_OFF) | (Size << FLAGS_SIZE_SRC_OFF); } -constexpr uint32_t GenFlagsSizes(uint32_t Dest, uint32_t Src) { return (Dest << FLAGS_SIZE_DST_OFF) | (Src << FLAGS_SIZE_SRC_OFF); } +constexpr InstFlagType GenFlagsDstSize(InstFlagType Size) { return Size << FLAGS_SIZE_DST_OFF; } +constexpr InstFlagType GenFlagsSrcSize(InstFlagType Size) { return Size << FLAGS_SIZE_SRC_OFF; } +constexpr InstFlagType GenFlagsSameSize(InstFlagType Size) { return (Size << FLAGS_SIZE_DST_OFF) | (Size << FLAGS_SIZE_SRC_OFF); } +constexpr InstFlagType GenFlagsSizes(InstFlagType Dest, InstFlagType Src) { return (Dest << FLAGS_SIZE_DST_OFF) | (Src << FLAGS_SIZE_SRC_OFF); } // If it has an xmm subflag #define HAS_XMM_SUBFLAG(x, flag) (((x) & (FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS | (flag))) == (FEXCore::X86Tables::InstFlags::FLAGS_XMM_FLAGS | (flag))) @@ -424,7 +427,7 @@ void InstallDebugInfo(); struct X86InstInfo { char const *Name; InstType Type; - uint32_t Flags; ///< Must be larger than InstFlags enum + InstFlags::InstFlagType Flags; ///< Must be larger than InstFlags enum uint8_t MoreBytes; OpDispatchPtr OpcodeDispatcher; #ifndef NDEBUG From ff24fe872d11d981b2eb8f6d8af016d5cd029c64 Mon Sep 17 00:00:00 2001 From: lioncash Date: Thu, 4 Nov 2021 15:59:16 -0400 Subject: [PATCH 2/4] X86Tables: Relocate size descriptors at the end of uint64_t This leaves the remaining bits available for use without needing to work around the size fields. --- External/FEXCore/include/FEXCore/Debug/X86Tables.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/External/FEXCore/include/FEXCore/Debug/X86Tables.h b/External/FEXCore/include/FEXCore/Debug/X86Tables.h index 5ef66bc72..8e7c35890 100644 --- a/External/FEXCore/include/FEXCore/Debug/X86Tables.h +++ b/External/FEXCore/include/FEXCore/Debug/X86Tables.h @@ -346,7 +346,7 @@ constexpr InstFlagType FLAGS_VEX_1ST_SRC = (1ULL << 24); // Whether or not the instruction has a VEX prefix for the second source operand constexpr InstFlagType FLAGS_VEX_2ND_SRC = (1ULL << 25); -constexpr InstFlagType FLAGS_SIZE_DST_OFF = 26; +constexpr InstFlagType FLAGS_SIZE_DST_OFF = 58; constexpr InstFlagType FLAGS_SIZE_SRC_OFF = FLAGS_SIZE_DST_OFF + 3; constexpr InstFlagType SIZE_MASK = 0b111; From 166c96320c5a4e42961d80f5cf7a33e874278175 Mon Sep 17 00:00:00 2001 From: lioncash Date: Thu, 4 Nov 2021 16:13:05 -0400 Subject: [PATCH 3/4] Frontend: Handle VEX-encoded destination operands BLSI, BLSMSK, and BLSR make use of these, for example. --- External/FEXCore/Source/Interface/Core/Frontend.cpp | 6 ++++++ External/FEXCore/include/FEXCore/Debug/X86Tables.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/External/FEXCore/Source/Interface/Core/Frontend.cpp b/External/FEXCore/Source/Interface/Core/Frontend.cpp index 1f32dcb4c..ad83c9f0b 100644 --- a/External/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/External/FEXCore/Source/Interface/Core/Frontend.cpp @@ -634,6 +634,12 @@ bool Decoder::NormalOp(FEXCore::X86Tables::X86InstInfo const *Info, uint16_t Op, ++CurrentSrc; } + if ((Info->Flags & FEXCore::X86Tables::InstFlags::FLAGS_VEX_DST) != 0) { + CurrentDest->Type = DecodedOperand::OpType::GPR; + CurrentDest->Data.GPR.HighBits = false; + CurrentDest->Data.GPR.GPR = MapVEXToReg(Options.vvvv, HasXMMDst); + } + if (Bytes != 0) { LOGMAN_THROW_A(Bytes <= 8, "Number of bytes should be <= 8 for literal src"); diff --git a/External/FEXCore/include/FEXCore/Debug/X86Tables.h b/External/FEXCore/include/FEXCore/Debug/X86Tables.h index 8e7c35890..b0fef1c39 100644 --- a/External/FEXCore/include/FEXCore/Debug/X86Tables.h +++ b/External/FEXCore/include/FEXCore/Debug/X86Tables.h @@ -345,6 +345,8 @@ constexpr InstFlagType FLAGS_SRC_SEXT64BIT = (1ULL << 23); constexpr InstFlagType FLAGS_VEX_1ST_SRC = (1ULL << 24); // Whether or not the instruction has a VEX prefix for the second source operand constexpr InstFlagType FLAGS_VEX_2ND_SRC = (1ULL << 25); +// Whether or not the instruction has a VEX prefix for the destination +constexpr InstFlagType FLAGS_VEX_DST = (1ULL << 26); constexpr InstFlagType FLAGS_SIZE_DST_OFF = 58; constexpr InstFlagType FLAGS_SIZE_SRC_OFF = FLAGS_SIZE_DST_OFF + 3; From b47cb20619064771276a6798dec43aed22aa963a Mon Sep 17 00:00:00 2001 From: lioncash Date: Thu, 4 Nov 2021 16:25:27 -0400 Subject: [PATCH 4/4] OpcodeDispatcher: Implement handling for BLSI Now all that remains is handling for BLSMSK and BLSR --- .../Interface/Core/OpcodeDispatcher.cpp | 64 +++++++++++++++++++ .../Source/Interface/Core/OpcodeDispatcher.h | 1 + .../Interface/Core/X86Tables/VEXTables.cpp | 2 +- unittests/ASM/VEX/blsi.asm | 34 ++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 unittests/ASM/VEX/blsi.asm diff --git a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 01b174599..a1aacd267 100644 --- a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -2204,6 +2204,63 @@ void OpDispatchBuilder::BEXTRBMIOp(OpcodeArgs) { SetRFLAG(ZeroOp); } +void OpDispatchBuilder::BLSIBMIOp(OpcodeArgs) { + // Equivalent to performing: SRC & -SRC + + auto* Src = LoadSource(GPRClass, Op, Op->Src[0], Op->Flags, -1); + auto NegatedSrc = _Neg(Src); + auto Result = _And(Src, NegatedSrc); + + // ...and we're done. Painless! + StoreResult(GPRClass, Op, Result, -1); + + // Now for the flags: + // + // Only CF, SF, ZF and OF are defined as being updated + // CF is cleared if Src is zero, otherwise it's set. + // SF is set to the value of the most significant operand bit of Result. + // OF is always cleared + // ZF is set, as usual, if Result is zero or not. + // + // AF and PF are documented as being in an undefined state after + // a BLSI operation, however, we choose to reliably clear them. + + auto Zero = _Constant(0); + auto One = _Constant(1); + + SetRFLAG(Zero); + SetRFLAG(Zero); + if (CTX->Config.ABINoPF) { + _InvalidateFlags(1UL << X86State::RFLAG_PF_LOC); + } else { + SetRFLAG(Zero); + } + + // ZF + { + auto ZFOp = _Select(IR::COND_EQ, + Result, Zero, + One, Zero); + SetRFLAG(ZFOp); + } + + // CF + { + auto CFOp = _Select(IR::COND_EQ, + Src, Zero, + Zero, One); + SetRFLAG(CFOp); + } + + // SF + { + auto SignBit = _Constant((GetSrcSize(Op) * 8) - 1); + auto SFOp = _Lshr(Result, SignBit); + + SetRFLAG(SFOp); + } +} + void OpDispatchBuilder::RCROp1Bit(OpcodeArgs) { OrderedNode *Dest = LoadSource(GPRClass, Op, Op->Dest, Op->Flags, -1); auto Size = GetSrcSize(Op) * 8; @@ -5876,6 +5933,12 @@ constexpr uint16_t PF_F2 = 3; }; #undef OPD + #define OPD(group, pp, opcode) (((group - X86Tables::InstType::TYPE_VEX_GROUP_12) << 4) | (pp << 3) | (opcode)) + const std::vector> VEXGroupTable = { + {OPD(X86Tables::InstType::TYPE_VEX_GROUP_17, 0, 0b011), 1, &OpDispatchBuilder::BLSIBMIOp}, + }; + #undef OPD + const std::vector> EVEXTable = { {0x10, 2, &OpDispatchBuilder::UnimplementedOp}, {0x59, 1, &OpDispatchBuilder::UnimplementedOp}, @@ -5936,6 +5999,7 @@ constexpr uint16_t PF_F2 = 3; InstallToTable(FEXCore::X86Tables::H0F38TableOps, H0F38Table); InstallToTable(FEXCore::X86Tables::H0F3ATableOps, H0F3ATable); InstallToTable(FEXCore::X86Tables::VEXTableOps, VEXTable); + InstallToTable(FEXCore::X86Tables::VEXTableGroupOps, VEXGroupTable); InstallToTable(FEXCore::X86Tables::EVEXTableOps, EVEXTable); } diff --git a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.h b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.h index a440b707e..331d46917 100644 --- a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.h +++ b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.h @@ -327,6 +327,7 @@ public: // BMI Ops void ANDNBMIOp(OpcodeArgs); void BEXTRBMIOp(OpcodeArgs); + void BLSIBMIOp(OpcodeArgs); // X87 Ops template diff --git a/External/FEXCore/Source/Interface/Core/X86Tables/VEXTables.cpp b/External/FEXCore/Source/Interface/Core/X86Tables/VEXTables.cpp index 8b5de202a..e9ae5b6a1 100644 --- a/External/FEXCore/Source/Interface/Core/X86Tables/VEXTables.cpp +++ b/External/FEXCore/Source/Interface/Core/X86Tables/VEXTables.cpp @@ -505,7 +505,7 @@ void InitializeVEXTables() { {OPD(TYPE_VEX_GROUP_17, 0, 0b001), 1, X86InstInfo{"BLSR", TYPE_UNDEC, FLAGS_MODRM, 0, nullptr}}, {OPD(TYPE_VEX_GROUP_17, 0, 0b010), 1, X86InstInfo{"BLSMSK", TYPE_UNDEC, FLAGS_MODRM, 0, nullptr}}, - {OPD(TYPE_VEX_GROUP_17, 0, 0b011), 1, X86InstInfo{"BLSI", TYPE_UNDEC, FLAGS_MODRM, 0, nullptr}}, + {OPD(TYPE_VEX_GROUP_17, 0, 0b011), 1, X86InstInfo{"BLSI", TYPE_INST, FLAGS_MODRM | FLAGS_VEX_DST, 0, nullptr}}, }; #undef OPD diff --git a/unittests/ASM/VEX/blsi.asm b/unittests/ASM/VEX/blsi.asm new file mode 100644 index 000000000..ccddd8802 --- /dev/null +++ b/unittests/ASM/VEX/blsi.asm @@ -0,0 +1,34 @@ +%ifdef CONFIG +{ + "RegData": { + "RAX": "1", + "RBX": "0xFF00000000000000", + "RCX": "0x0100000000000000", + "RDX": "1", + "RSI": "0xFF000000", + "RDI": "0x01000000" + } +} +%endif + +; Trivial test, this should result in 1. +mov rax, 11 +blsi rax, rax + +; Results in the lowest set bit (bit 56) being extracted +mov rbx, 0xFF00000000000000 +mov rcx, 0 +blsi rcx, rbx + +; Same tests but with 32-bit registers + +; Trivial test, this should result in 1. +mov edx, 11 +blsi edx, edx + +; Results in the lowest set bit (bit 24) being extracted +mov rsi, 0xFF000000 +mov rdi, 0 +blsi edi, esi + +hlt \ No newline at end of file