From 88af22507077a2de6f8fd8b00bef9f61cd95a306 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 26 Aug 2014 11:26:41 +0200 Subject: [PATCH 01/11] x64Emitter: Remove a declared function that is never implemented --- Source/Core/Common/x64Emitter.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index fcc182c46f..486ff7494e 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -336,7 +336,6 @@ public: FixupBranch J(bool force5bytes = false); void JMP(const u8 * addr, bool force5Bytes = false); - void JMP(OpArg arg); void JMPptr(const OpArg &arg); void JMPself(); //infinite loop! #ifdef CALL From 9c4daac3a4bb7412203219e0cbfad0057c57bbbe Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 26 Aug 2014 11:27:22 +0200 Subject: [PATCH 02/11] x64Emitter: RDTSC now without a typo'd name --- Source/Core/Common/x64Emitter.cpp | 2 +- Source/Core/Common/x64Emitter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 816dd24081..db9c651215 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -1679,7 +1679,7 @@ void XEmitter::FST(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatST, des void XEmitter::FSTP(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatSTP, dest);} void XEmitter::FNSTSW_AX() { Write8(0xDF); Write8(0xE0); } -void XEmitter::RTDSC() { Write8(0x0F); Write8(0x31); } +void XEmitter::RDTSC() { Write8(0x0F); Write8(0x31); } // helper routines for setting pointers void XEmitter::CallCdeclFunction3(void* fnptr, u32 arg0, u32 arg1, u32 arg2) diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index 486ff7494e..4c93e10978 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -705,7 +705,7 @@ public: void VPAND(X64Reg regOp1, X64Reg regOp2, OpArg arg); void VPANDN(X64Reg regOp1, X64Reg regOp2, OpArg arg); - void RTDSC(); + void RDTSC(); // Utility functions // The difference between this and CALL is that this aligns the stack From d4ec9737bdf0ed69a9fa00978115a036fe622a91 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 26 Aug 2014 11:28:13 +0200 Subject: [PATCH 03/11] x64Emitter: Assert when using an invalid POP instead of generating an INT3 --- Source/Core/Common/x64Emitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index db9c651215..fdbe872795 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -666,7 +666,7 @@ void XEmitter::POP(int /*bits*/, const OpArg ®) if (reg.IsSimpleReg()) POP(reg.GetSimpleReg()); else - INT3(); + _assert_msg_(DYNA_REC, 0, "POP - Unsupported encoding"); } void XEmitter::BSWAP(int bits, X64Reg reg) From f0e8b1fda823363ade7eb21b17228786e377cada Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 26 Aug 2014 11:28:48 +0200 Subject: [PATCH 04/11] x64Emitter: Error out on 8 bits CMOV, and emit 16 bits CMOV properly --- Source/Core/Common/x64Emitter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index fdbe872795..1c8b277455 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -720,6 +720,8 @@ void XEmitter::SETcc(CCFlags flag, OpArg dest) void XEmitter::CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag) { if (src.IsImm()) _assert_msg_(DYNA_REC, 0, "CMOVcc - Imm argument"); + if (bits == 8) _assert_msg_(DYNA_REC, 0, "CMOVcc - 8 bits unsupported"); + if (bits == 16) Write8(0x66); src.operandReg = dest; src.WriteRex(this, bits, bits); Write8(0x0F); From b1738b60fc323bea7e694b08ae662acc15827b51 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 26 Aug 2014 23:22:44 +0200 Subject: [PATCH 05/11] x64Emitter: Fix MUL with AH/BH/CH/DH registers. --- Source/Core/Common/x64Emitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 1c8b277455..276b3a08c9 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -734,7 +734,7 @@ void XEmitter::WriteMulDivType(int bits, OpArg src, int ext) if (src.IsImm()) _assert_msg_(DYNA_REC, 0, "WriteMulDivType - Imm argument"); src.operandReg = ext; if (bits == 16) Write8(0x66); - src.WriteRex(this, bits, bits); + src.WriteRex(this, bits, bits, 0); if (bits == 8) { Write8(0xF6); From f99f302c9188e53eb745afa7bd5cc0b84d9f6092 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 08:55:07 +0200 Subject: [PATCH 06/11] x64Emitter: assert instead of crashing when generating MOVZX with a wrong size --- Source/Core/Common/x64Emitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 276b3a08c9..0a9a10487c 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -833,7 +833,7 @@ void XEmitter::MOVZX(int dbits, int sbits, X64Reg dest, OpArg src) } else { - Crash(); + _assert_msg_(DYNA_REC, 0, "MOVZX - Invalid size"); } src.WriteRest(this); } From cc0b048c0bf1a80039374fbe606c97bd79e71ebc Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 08:55:38 +0200 Subject: [PATCH 07/11] x64Emitter: Support FLD/FSTP with 80 bits operands --- Source/Core/Common/x64Emitter.cpp | 20 +++++++++++--------- Source/Core/Common/x64Emitter.h | 6 +++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 0a9a10487c..036e085d41 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -1660,25 +1660,27 @@ void XEmitter::FWAIT() } // TODO: make this more generic -void XEmitter::WriteFloatLoadStore(int bits, FloatOp op, OpArg arg) +void XEmitter::WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, OpArg arg) { int mf = 0; - + _assert_msg_(DYNA_REC, !(bits == 80 && op_80b == floatINVALID), "WriteFloatLoadStore: 80 bits not supported for this instruction"); switch (bits) { case 32: mf = 0; break; - case 64: mf = 2; break; - default: _assert_msg_(DYNA_REC, 0, "WriteFloatLoadStore: bits is not 32 or 64"); + case 64: mf = 4; break; + case 80: mf = 2; break; + default: _assert_msg_(DYNA_REC, 0, "WriteFloatLoadStore: invalid bits (should be 32/64/80)"); } - - Write8(0xd9 | (mf << 1)); + Write8(0xd9 | mf); // x87 instructions use the reg field of the ModR/M byte as opcode: + if (bits == 80) + op = op_80b; arg.WriteRest(this, 0, (X64Reg) op); } -void XEmitter::FLD(int bits, OpArg src) {WriteFloatLoadStore(bits, floatLD, src);} -void XEmitter::FST(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatST, dest);} -void XEmitter::FSTP(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatSTP, dest);} +void XEmitter::FLD(int bits, OpArg src) {WriteFloatLoadStore(bits, floatLD, floatLD80, src);} +void XEmitter::FST(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatST, floatINVALID, dest);} +void XEmitter::FSTP(int bits, OpArg dest) {WriteFloatLoadStore(bits, floatSTP, floatSTP80, dest);} void XEmitter::FNSTSW_AX() { Write8(0xDF); Write8(0xE0); } void XEmitter::RDTSC() { Write8(0x0F); Write8(0x31); } diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index 4c93e10978..4eb7635c5e 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -105,6 +105,10 @@ enum FloatOp { floatLD = 0, floatST = 2, floatSTP = 3, + floatLD80 = 5, + floatSTP80 = 7, + + floatINVALID = -1, }; class XEmitter; @@ -271,7 +275,7 @@ private: void WriteSSE41Op(int size, u16 sseOp, bool packed, X64Reg regOp, OpArg arg, int extrabytes = 0); void WriteAVXOp(int size, u16 sseOp, bool packed, X64Reg regOp, OpArg arg, int extrabytes = 0); void WriteAVXOp(int size, u16 sseOp, bool packed, X64Reg regOp1, X64Reg regOp2, OpArg arg, int extrabytes = 0); - void WriteFloatLoadStore(int bits, FloatOp op, OpArg arg); + void WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, OpArg arg); void WriteNormalOp(XEmitter *emit, int bits, NormalOp op, const OpArg &a1, const OpArg &a2); protected: From c428c5999fcaa15f277b3732843d1d0511b68f45 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 09:39:22 +0200 Subject: [PATCH 08/11] x64Emitter: UNPCKLPS/HPS are now tested --- Source/Core/Common/x64Emitter.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index 4eb7635c5e..98420f7ee5 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -539,11 +539,8 @@ public: // SSE/SSE2: Useful alternative to shuffle in some cases. void MOVDDUP(X64Reg regOp, OpArg arg); - // THESE TWO ARE NEW AND UNTESTED void UNPCKLPS(X64Reg dest, OpArg src); void UNPCKHPS(X64Reg dest, OpArg src); - - // These are OK. void UNPCKLPD(X64Reg dest, OpArg src); void UNPCKHPD(X64Reg dest, OpArg src); From a79ced2fc233d5b2d6bdba84e6cd2823eeda1ebd Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 09:39:43 +0200 Subject: [PATCH 09/11] x64Emitter: Make it clear for both SSE to int conv that X64 regs are expected --- Source/Core/Common/x64Emitter.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index 98420f7ee5..58dc313419 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -588,21 +588,23 @@ public: void CVTPS2PD(X64Reg dest, OpArg src); void CVTPD2PS(X64Reg dest, OpArg src); void CVTSS2SD(X64Reg dest, OpArg src); - void CVTSS2SI(X64Reg dest, OpArg src); void CVTSI2SS(X64Reg dest, OpArg src); void CVTSD2SS(X64Reg dest, OpArg src); - void CVTSD2SI(X64Reg dest, OpArg src); void CVTSI2SD(X64Reg dest, OpArg src); void CVTDQ2PD(X64Reg regOp, OpArg arg); void CVTPD2DQ(X64Reg regOp, OpArg arg); void CVTDQ2PS(X64Reg regOp, OpArg arg); void CVTPS2DQ(X64Reg regOp, OpArg arg); - void CVTTSS2SI(X64Reg regOp, OpArg arg); - void CVTTSD2SI(X64Reg regOp, OpArg arg); void CVTTPS2DQ(X64Reg regOp, OpArg arg); void CVTTPD2DQ(X64Reg regOp, OpArg arg); + // Destinations are X64 regs (rax, rbx, ...) for these instructions. + void CVTSS2SI(X64Reg xregdest, OpArg src); + void CVTSD2SI(X64Reg xregdest, OpArg src); + void CVTTSS2SI(X64Reg xregdest, OpArg arg); + void CVTTSD2SI(X64Reg xregdest, OpArg arg); + // SSE2: Packed integer instructions void PACKSSDW(X64Reg dest, OpArg arg); void PACKSSWB(X64Reg dest, OpArg arg); From e72146d19c3aec0a11e7ca1229e126c7d544320e Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 10:16:58 +0200 Subject: [PATCH 10/11] x64Emitter: Do not assert-fail on redundant MOVs, instead show an error log --- Source/Core/Common/x64Emitter.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 036e085d41..68101a693b 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -1174,10 +1174,8 @@ void XEmitter::OR (int bits, const OpArg &a1, const OpArg &a2) {WriteNormalOp(t void XEmitter::XOR (int bits, const OpArg &a1, const OpArg &a2) {WriteNormalOp(this, bits, nrmXOR, a1, a2);} void XEmitter::MOV (int bits, const OpArg &a1, const OpArg &a2) { -#ifdef _DEBUG - _assert_msg_(DYNA_REC, !a1.IsSimpleReg() || !a2.IsSimpleReg() || a1.GetSimpleReg() != a2.GetSimpleReg(), "Redundant MOV @ %p - bug in JIT?", - code); -#endif + if (a1.IsSimpleReg() && a2.IsSimpleReg() && a1.GetSimpleReg() == a2.GetSimpleReg()) + ERROR_LOG(DYNA_REC, "Redundant MOV @ %p - bug in JIT?", code); WriteNormalOp(this, bits, nrmMOV, a1, a2); } void XEmitter::TEST(int bits, const OpArg &a1, const OpArg &a2) {WriteNormalOp(this, bits, nrmTEST, a1, a2);} From 5b4f1fe92c6b1ff469c5ca154ec411baf9034d23 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Tue, 2 Sep 2014 09:51:53 +0200 Subject: [PATCH 11/11] UnitTests: Add tests for the x64Emitter --- Source/UnitTests/Common/CMakeLists.txt | 1 + Source/UnitTests/Common/x64EmitterTest.cpp | 833 +++++++++++++++++++++ 2 files changed, 834 insertions(+) create mode 100644 Source/UnitTests/Common/x64EmitterTest.cpp diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index 05337f4321..c7e9f2046c 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -5,3 +5,4 @@ add_dolphin_test(FifoQueueTest FifoQueueTest.cpp) add_dolphin_test(FixedSizeQueueTest FixedSizeQueueTest.cpp) add_dolphin_test(FlagTest FlagTest.cpp) add_dolphin_test(MathUtilTest MathUtilTest.cpp) +add_dolphin_test(x64EmitterTest x64EmitterTest.cpp) diff --git a/Source/UnitTests/Common/x64EmitterTest.cpp b/Source/UnitTests/Common/x64EmitterTest.cpp new file mode 100644 index 0000000000..2752e0a50a --- /dev/null +++ b/Source/UnitTests/Common/x64EmitterTest.cpp @@ -0,0 +1,833 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#include // From Bochs, fallback included in Externals. +#include +#include +#include +#include + +// gtest defines the TEST macro to generate test case functions. It conflicts +// with the TEST method in the x64Emitter. +// +// Since we use TEST_F in this file to attach the test cases to a fixture, we +// can get away with simply undef'ing TEST. Phew. +#undef TEST + +#include "Common/x64Emitter.h" + +namespace Gen +{ + +struct NamedReg +{ + X64Reg reg; + std::string name; +}; + +const std::vector reg8names { + { RAX, "al" }, { RBX, "bl" }, { RCX, "cl" }, { RDX, "dl" }, + { RSI, "sil" }, { RDI, "dil" }, { RBP, "bpl" }, { RSP, "spl" }, + { R8, "r8b" }, { R9, "r9b" }, { R10, "r10b" }, { R11, "r11b" }, + { R12, "r12b" }, { R13, "r13b" }, { R14, "r14b" }, { R15, "r15b" }, +}; + +const std::vector reg8hnames { + { AH, "ah" }, { BH, "bh" }, { CH, "ch" }, { DH, "dh" }, +}; + +const std::vector reg16names { + { RAX, "ax" }, { RBX, "bx" }, { RCX, "cx" }, { RDX, "dx" }, + { RSI, "si" }, { RDI, "di" }, { RBP, "bp" }, { RSP, "sp" }, + { R8, "r8w" }, { R9, "r9w" }, { R10, "r10w" }, { R11, "r11w" }, + { R12, "r12w" }, { R13, "r13w" }, { R14, "r14w" }, { R15, "r15w" }, +}; + +const std::vector reg32names { + { RAX, "eax" }, { RBX, "ebx" }, { RCX, "ecx" }, { RDX, "edx" }, + { RSI, "esi" }, { RDI, "edi" }, { RBP, "ebp" }, { RSP, "esp" }, + { R8, "r8d" }, { R9, "r9d" }, { R10, "r10d" }, { R11, "r11d" }, + { R12, "r12d" }, { R13, "r13d" }, { R14, "r14d" }, { R15, "r15d" }, +}; + +const std::vector reg64names { + { RAX, "rax" }, { RBX, "rbx" }, { RCX, "rcx" }, { RDX, "rdx" }, + { RSI, "rsi" }, { RDI, "rdi" }, { RBP, "rbp" }, { RSP, "rsp" }, + { R8, "r8" }, { R9, "r9" }, { R10, "r10" }, { R11, "r11" }, + { R12, "r12" }, { R13, "r13" }, { R14, "r14" }, { R15, "r15" }, +}; + +const std::vector xmmnames { + { XMM0, "xmm0" }, { XMM1, "xmm1" }, { XMM2, "xmm2" }, { XMM3, "xmm3" }, + { XMM4, "xmm4" }, { XMM5, "xmm5" }, { XMM6, "xmm6" }, { XMM7, "xmm7" }, + { XMM8, "xmm8" }, { XMM9, "xmm9" }, { XMM10, "xmm10" }, { XMM11, "xmm11" }, + { XMM12, "xmm12" }, { XMM13, "xmm13" }, { XMM14, "xmm14" }, { XMM15, "xmm15" }, +}; + +const std::vector ymmnames { + { YMM0, "ymm0" }, { YMM1, "ymm1" }, { YMM2, "ymm2" }, { YMM3, "ymm3" }, + { YMM4, "ymm4" }, { YMM5, "ymm5" }, { YMM6, "ymm6" }, { YMM7, "ymm7" }, + { YMM8, "ymm8" }, { YMM9, "ymm9" }, { YMM10, "ymm10" }, { YMM11, "ymm11" }, + { YMM12, "ymm12" }, { YMM13, "ymm13" }, { YMM14, "ymm14" }, { YMM15, "ymm15" }, +}; + +struct { CCFlags cc; std::string name; } ccnames[] = { + { CC_O, "o" }, { CC_NO, "no" }, + { CC_B, "b" }, { CC_NB, "nb" }, + { CC_Z, "z" }, { CC_NZ, "nz" }, + { CC_BE, "be" }, { CC_NBE, "nbe" }, + { CC_S, "s" }, { CC_NS, "ns" }, + { CC_P, "p" }, { CC_NP, "np" }, + { CC_L, "l" }, { CC_NL, "nl" }, + { CC_LE, "le" }, { CC_NLE, "nle" }, +}; + +class x64EmitterTest : public testing::Test +{ +protected: + void SetUp() override + { + emitter.reset(new X64CodeBlock()); + emitter->AllocCodeSpace(4096); + code_buffer = emitter->GetWritableCodePtr(); + + disasm.reset(new disassembler); + disasm->set_syntax_intel(); + } + + void ExpectDisassembly(const std::string& expected) + { + std::string disasmed; + const u8* generated_code_iterator = code_buffer; + while (generated_code_iterator < emitter->GetCodePtr()) + { + char instr_buffer[1024] = ""; + generated_code_iterator += disasm->disasm64( + (u64)generated_code_iterator, + (u64)generated_code_iterator, + generated_code_iterator, instr_buffer); + disasmed += instr_buffer; + disasmed += "\n"; + } + + auto NormalizeAssembly = [](const std::string& str) -> std::string { + // Normalize assembly code to make it suitable for equality checks. + // In particular: + // * Replace all whitespace characters by a single space. + // * Remove leading and trailing whitespaces. + // * Lowercase everything. + // * Remove all (0x...) addresses. + std::string out; + bool previous_was_space = false; + bool inside_parens = false; + for (auto c : str) + { + c = tolower(c); + if (c == '(') + { + inside_parens = true; + continue; + } + else if (inside_parens) + { + if (c == ')') + inside_parens = false; + continue; + } + else if (isspace(c)) + { + previous_was_space = true; + continue; + } + else if (previous_was_space) + { + previous_was_space = false; + if (!out.empty()) + out += ' '; + } + out += c; + } + return out; + }; + std::string expected_norm = NormalizeAssembly(expected); + std::string disasmed_norm = NormalizeAssembly(disasmed); + + EXPECT_EQ(expected_norm, disasmed_norm); + + // Reset code buffer afterwards. + emitter->SetCodePtr(code_buffer); + } + + std::unique_ptr emitter; + std::unique_ptr disasm; + u8* code_buffer; +}; + +#define TEST_INSTR_NO_OPERANDS(Name, ExpectedDisasm) \ + TEST_F(x64EmitterTest, Name) \ + { \ + emitter->Name(); \ + ExpectDisassembly(ExpectedDisasm); \ + } + +TEST_INSTR_NO_OPERANDS(INT3, "int3") +TEST_INSTR_NO_OPERANDS(NOP, "nop") +TEST_INSTR_NO_OPERANDS(PAUSE, "pause") +TEST_INSTR_NO_OPERANDS(STC, "stc") +TEST_INSTR_NO_OPERANDS(CLC, "clc") +TEST_INSTR_NO_OPERANDS(CMC, "cmc") +TEST_INSTR_NO_OPERANDS(LAHF, "lahf") +TEST_INSTR_NO_OPERANDS(SAHF, "sahf") +TEST_INSTR_NO_OPERANDS(PUSHF, "pushf") +TEST_INSTR_NO_OPERANDS(POPF, "popf") +TEST_INSTR_NO_OPERANDS(RET, "ret") +TEST_INSTR_NO_OPERANDS(RET_FAST, "rep ret") +TEST_INSTR_NO_OPERANDS(UD2, "ud2a") +TEST_INSTR_NO_OPERANDS(JMPself, "jmp .-2") +TEST_INSTR_NO_OPERANDS(LFENCE, "lfence") +TEST_INSTR_NO_OPERANDS(MFENCE, "mfence") +TEST_INSTR_NO_OPERANDS(SFENCE, "sfence") +TEST_INSTR_NO_OPERANDS(CWD, "cwd") +TEST_INSTR_NO_OPERANDS(CDQ, "cdq") +TEST_INSTR_NO_OPERANDS(CQO, "cqo") +TEST_INSTR_NO_OPERANDS(CBW, "cbw") +TEST_INSTR_NO_OPERANDS(CWDE, "cwde") +TEST_INSTR_NO_OPERANDS(CDQE, "cdqe") +TEST_INSTR_NO_OPERANDS(XCHG_AHAL, "xchg al, ah") +TEST_INSTR_NO_OPERANDS(FWAIT, "fwait") +TEST_INSTR_NO_OPERANDS(FNSTSW_AX, "fnstsw ax") +TEST_INSTR_NO_OPERANDS(RDTSC, "rdtsc") + +TEST_F(x64EmitterTest, NOP_MultiByte) +{ + // 2 bytes is "rep nop", still a simple nop. + emitter->NOP(2); + ExpectDisassembly("nop"); + + for (int i = 3; i <= 11; ++i) + { + emitter->NOP(i); + ExpectDisassembly("multibyte nop"); + } + + // Larger NOPs are split into several NOPs. + emitter->NOP(20); + ExpectDisassembly("multibyte nop " + "multibyte nop"); +} + +TEST_F(x64EmitterTest, PUSH_Register) +{ + for (const auto& r : reg64names) + { + emitter->PUSH(r.reg); + ExpectDisassembly("push " + r.name); + } +} + +TEST_F(x64EmitterTest, PUSH_Immediate) +{ + emitter->PUSH(64, Imm8(0xf0)); + ExpectDisassembly("push 0xfffffffffffffff0"); + + // X64 is weird like that... this pushes 2 bytes, not 8 bytes with sext. + emitter->PUSH(64, Imm16(0xe0f0)); + ExpectDisassembly("push 0xe0f0"); + + emitter->PUSH(64, Imm32(0xc0d0e0f0)); + ExpectDisassembly("push 0xffffffffc0d0e0f0"); +} + +TEST_F(x64EmitterTest, PUSH_MComplex) +{ + emitter->PUSH(64, MComplex(RAX, RBX, SCALE_2, 4)); + ExpectDisassembly("push qword ptr ds:[rax+rbx*2+4]"); +} + +TEST_F(x64EmitterTest, POP_Register) +{ + for (const auto& r : reg64names) + { + emitter->POP(r.reg); + ExpectDisassembly("pop " + r.name); + } +} + +TEST_F(x64EmitterTest, JMP) +{ + emitter->NOP(6); + emitter->JMP(code_buffer); + ExpectDisassembly("multibyte nop " + "jmp .-8"); + + emitter->NOP(6); + emitter->JMP(code_buffer, true); + ExpectDisassembly("multibyte nop " + "jmp .-11"); +} + +TEST_F(x64EmitterTest, JMPptr_Register) +{ + for (const auto& r : reg64names) + { + emitter->JMPptr(R(r.reg)); + ExpectDisassembly("jmp " + r.name); + } +} + +// TODO: J/SetJumpTarget + +// TODO: CALL + +// TODO: J_CC + +TEST_F(x64EmitterTest, SETcc) +{ + for (const auto& cc : ccnames) + { + for (const auto& r : reg8names) + { + emitter->SETcc(cc.cc, R(r.reg)); + ExpectDisassembly("set" + cc.name + " " + r.name); + } + for (const auto& r : reg8hnames) + { + emitter->SETcc(cc.cc, R(r.reg)); + ExpectDisassembly("set" + cc.name + " " + r.name); + } + } +} + +TEST_F(x64EmitterTest, CMOVcc_Register) +{ + for (const auto& cc : ccnames) + { + emitter->CMOVcc(64, RAX, R(R12), cc.cc); + emitter->CMOVcc(32, RAX, R(R12), cc.cc); + emitter->CMOVcc(16, RAX, R(R12), cc.cc); + + ExpectDisassembly("cmov" + cc.name + " rax, r12 " + "cmov" + cc.name + " eax, r12d " + "cmov" + cc.name + " ax, r12w"); + } +} + +TEST_F(x64EmitterTest, BSF) +{ + emitter->BSF(64, R12, R(RAX)); + emitter->BSF(32, R12, R(RAX)); + emitter->BSF(16, R12, R(RAX)); + + emitter->BSF(64, R12, MatR(RAX)); + emitter->BSF(32, R12, MatR(RAX)); + emitter->BSF(16, R12, MatR(RAX)); + + ExpectDisassembly("bsf r12, rax " + "bsf r12d, eax " + "bsf r12w, ax " + "bsf r12, qword ptr ds:[rax] " + "bsf r12d, dword ptr ds:[rax] " + "bsf r12w, word ptr ds:[rax]"); +} + +TEST_F(x64EmitterTest, BSR) +{ + emitter->BSR(64, R12, R(RAX)); + emitter->BSR(32, R12, R(RAX)); + emitter->BSR(16, R12, R(RAX)); + + emitter->BSR(64, R12, MatR(RAX)); + emitter->BSR(32, R12, MatR(RAX)); + emitter->BSR(16, R12, MatR(RAX)); + + ExpectDisassembly("bsr r12, rax " + "bsr r12d, eax " + "bsr r12w, ax " + "bsr r12, qword ptr ds:[rax] " + "bsr r12d, dword ptr ds:[rax] " + "bsr r12w, word ptr ds:[rax]"); +} + +TEST_F(x64EmitterTest, PREFETCH) +{ + emitter->PREFETCH(XEmitter::PF_NTA, MatR(R12)); + emitter->PREFETCH(XEmitter::PF_T0, MatR(R12)); + emitter->PREFETCH(XEmitter::PF_T1, MatR(R12)); + emitter->PREFETCH(XEmitter::PF_T2, MatR(R12)); + + ExpectDisassembly("prefetchnta byte ptr ds:[r12] " + "prefetcht0 byte ptr ds:[r12] " + "prefetcht1 byte ptr ds:[r12] " + "prefetcht2 byte ptr ds:[r12]"); +} + +TEST_F(x64EmitterTest, MOVNTI) +{ + emitter->MOVNTI(32, MatR(RAX), R12); + emitter->MOVNTI(32, M(code_buffer), R12); + emitter->MOVNTI(64, MatR(RAX), R12); + emitter->MOVNTI(64, M(code_buffer), R12); + + ExpectDisassembly("movnti dword ptr ds:[rax], r12d " + "movnti dword ptr ds:[rip-12], r12d " + "movnti qword ptr ds:[rax], r12 " + "movnti qword ptr ds:[rip-24], r12"); +} + +// Grouped together since these 3 instructions do exactly the same thing. +TEST_F(x64EmitterTest, MOVNT_DQ_PS_PD) +{ + for (const auto& r : xmmnames) + { + emitter->MOVNTDQ(MatR(RAX), r.reg); + emitter->MOVNTPS(MatR(RAX), r.reg); + emitter->MOVNTPD(MatR(RAX), r.reg); + ExpectDisassembly("movntdq dqword ptr ds:[rax], " + r.name + " " + "movntps dqword ptr ds:[rax], " + r.name + " " + "movntpd dqword ptr ds:[rax], " + r.name); + } +} + +#define MUL_DIV_TEST(Name) \ + TEST_F(x64EmitterTest, Name) \ + { \ + struct { \ + int bits; \ + std::vector regs; \ + std::string out_name; \ + } regsets[] = { \ + { 8, reg8names, "al" }, \ + { 8, reg8hnames, "al" }, \ + { 16, reg16names, "ax" }, \ + { 32, reg32names, "eax" }, \ + { 64, reg64names, "rax" }, \ + }; \ + for (const auto& regset : regsets) \ + for (const auto& r : regset.regs) \ + { \ + emitter->Name(regset.bits, R(r.reg)); \ + ExpectDisassembly(#Name " " + regset.out_name + ", " + r.name); \ + } \ + } + +MUL_DIV_TEST(MUL) +MUL_DIV_TEST(IMUL) +MUL_DIV_TEST(DIV) +MUL_DIV_TEST(IDIV) + +// TODO: More complex IMUL variants. + +#define SHIFT_TEST(Name) \ + TEST_F(x64EmitterTest, Name) \ + { \ + struct { \ + int bits; \ + std::vector regs; \ + } regsets[] = { \ + { 8, reg8names }, \ + { 8, reg8hnames }, \ + { 16, reg16names }, \ + { 32, reg32names }, \ + { 64, reg64names }, \ + }; \ + for (const auto& regset : regsets) \ + for (const auto& r : regset.regs) \ + { \ + emitter->Name(regset.bits, R(r.reg), Imm8(1)); \ + emitter->Name(regset.bits, R(r.reg), Imm8(4)); \ + emitter->Name(regset.bits, R(r.reg), R(CL)); \ + ExpectDisassembly(#Name " " + r.name + ", 1 " \ + #Name " " + r.name + ", 0x04 " \ + #Name " " + r.name + ", cl"); \ + } \ + } + +SHIFT_TEST(ROL) +SHIFT_TEST(ROR) +SHIFT_TEST(RCL) +SHIFT_TEST(RCR) +SHIFT_TEST(SHL) +SHIFT_TEST(SHR) +SHIFT_TEST(SAR) + +#define BT_TEST(Name) \ + TEST_F(x64EmitterTest, Name) \ + { \ + struct { \ + int bits; \ + std::vector regs; \ + std::string out_name; \ + std::string size; \ + } regsets[] = { \ + { 16, reg16names, "ax", "word" }, \ + { 32, reg32names, "eax", "dword" }, \ + { 64, reg64names, "rax", "qword" }, \ + }; \ + for (const auto& regset : regsets) \ + for (const auto& r : regset.regs) \ + { \ + emitter->Name(regset.bits, R(r.reg), R(RAX)); \ + emitter->Name(regset.bits, R(RAX), R(r.reg)); \ + emitter->Name(regset.bits, R(r.reg), Imm8(0x42)); \ + emitter->Name(regset.bits, MatR(R12), R(r.reg)); \ + ExpectDisassembly(#Name " " + r.name + ", " + regset.out_name + " " \ + #Name " " + regset.out_name + ", " + r.name + " " \ + #Name " " + r.name + ", 0x42 " \ + #Name " " + regset.size + " ptr ds:[r12], " + r.name); \ + } \ + } + +BT_TEST(BT) +BT_TEST(BTS) +BT_TEST(BTR) +BT_TEST(BTC) + +// TODO: LEA tests + +#define ONE_OP_ARITH_TEST(Name) \ + TEST_F(x64EmitterTest, Name) \ + { \ + struct { \ + int bits; \ + std::vector regs; \ + std::string size; \ + } regsets[] = { \ + { 8, reg8names, "byte" }, \ + { 8, reg8hnames, "byte" }, \ + { 16, reg16names, "word" }, \ + { 32, reg32names, "dword" }, \ + { 64, reg64names, "qword" }, \ + }; \ + for (const auto& regset : regsets) \ + for (const auto& r : regset.regs) \ + { \ + emitter->Name(regset.bits, R(r.reg)); \ + emitter->Name(regset.bits, MatR(RAX)); \ + emitter->Name(regset.bits, MatR(R12)); \ + ExpectDisassembly(#Name " " + r.name + " " \ + #Name " " + regset.size + " ptr ds:[rax] " \ + #Name " " + regset.size + " ptr ds:[r12]"); \ + } \ + } + +ONE_OP_ARITH_TEST(NOT) +ONE_OP_ARITH_TEST(NEG) + +#define TWO_OP_ARITH_TEST(Name) \ + TEST_F(x64EmitterTest, Name) \ + { \ + struct { \ + int bits; \ + std::vector regs; \ + std::string size; \ + std::string rax_name; \ + } regsets[] = { \ + { 8, reg8names, "byte", "al" }, \ + { 8, reg8hnames, "byte", "al" }, \ + { 16, reg16names, "word", "ax" }, \ + { 32, reg32names, "dword", "eax" }, \ + { 64, reg64names, "qword", "rax" }, \ + }; \ + for (const auto& regset : regsets) \ + for (const auto& r : regset.regs) \ + { \ + emitter->Name(regset.bits, R(r.reg), R(RAX)); \ + emitter->Name(regset.bits, R(RAX), R(r.reg)); \ + emitter->Name(regset.bits, R(r.reg), MatR(RAX)); \ + emitter->Name(regset.bits, MatR(RAX), R(r.reg)); \ + ExpectDisassembly(#Name " " + r.name + ", " + regset.rax_name + " " \ + #Name " " + regset.rax_name + ", " + r.name + " " \ + #Name " " + r.name + ", " + regset.size + " ptr ds:[rax] " \ + #Name " " + regset.size + " ptr ds:[rax], " + r.name); \ + } \ + } + +TWO_OP_ARITH_TEST(ADD) +TWO_OP_ARITH_TEST(ADC) +TWO_OP_ARITH_TEST(SUB) +TWO_OP_ARITH_TEST(SBB) +TWO_OP_ARITH_TEST(AND) +TWO_OP_ARITH_TEST(CMP) +TWO_OP_ARITH_TEST(OR) +TWO_OP_ARITH_TEST(XOR) +TWO_OP_ARITH_TEST(MOV) + +// TODO: Disassembler inverts operands here. +// TWO_OP_ARITH_TEST(XCHG) +// TWO_OP_ARITH_TEST(TEST) + +TEST_F(x64EmitterTest, BSWAP) +{ + struct { + int bits; + std::vector regs; + } regsets[] = { + { 32, reg32names }, + { 64, reg64names }, + }; + for (const auto& regset : regsets) + for (const auto& r : regset.regs) + { + emitter->BSWAP(regset.bits, r.reg); + ExpectDisassembly("bswap " + r.name); + } +} + +TEST_F(x64EmitterTest, MOVSX) +{ + emitter->MOVSX(16, 8, RAX, R(AH)); + emitter->MOVSX(32, 8, RAX, R(R12)); + emitter->MOVSX(32, 16, R12, R(RBX)); + emitter->MOVSX(64, 8, R12, R(RBX)); + emitter->MOVSX(64, 16, RAX, R(R12)); + emitter->MOVSX(64, 32, R12, R(RSP)); + ExpectDisassembly("movsx ax, ah " + "movsx eax, r12b " + "movsx r12d, bx " + "movsx r12, bl " + "movsx rax, r12w " + "movsxd r12, esp"); +} + +TEST_F(x64EmitterTest, MOVZX) +{ + emitter->MOVZX(16, 8, RAX, R(AH)); + emitter->MOVZX(32, 8, R12, R(RBP)); + emitter->MOVZX(64, 8, R12, R(RDI)); + emitter->MOVZX(32, 16, RAX, R(R12)); + emitter->MOVZX(64, 16, RCX, R(RSI)); + ExpectDisassembly("movzx ax, ah " + "movzx r12d, bpl " + "movzx r12d, dil " // Generates 32 bit movzx + "movzx eax, r12w " + "movzx ecx, si"); +} + +TEST_F(x64EmitterTest, MOVBE) +{ + emitter->MOVBE(16, R(RAX), MatR(R12)); + emitter->MOVBE(16, MatR(RAX), R(R12)); + emitter->MOVBE(32, R(RAX), MatR(R12)); + emitter->MOVBE(32, MatR(RAX), R(R12)); + emitter->MOVBE(64, R(RAX), MatR(R12)); + emitter->MOVBE(64, MatR(RAX), R(R12)); + ExpectDisassembly("movbe ax, word ptr ds:[r12] " + "movbe word ptr ds:[rax], r12w " + "movbe eax, dword ptr ds:[r12] " + "movbe dword ptr ds:[rax], r12d " + "movbe rax, qword ptr ds:[r12] " + "movbe qword ptr ds:[rax], r12"); +} + +TEST_F(x64EmitterTest, STMXCSR) +{ + emitter->STMXCSR(MatR(R12)); + ExpectDisassembly("stmxcsr dword ptr ds:[r12]"); +} + +TEST_F(x64EmitterTest, LDMXCSR) +{ + emitter->LDMXCSR(MatR(R12)); + ExpectDisassembly("ldmxcsr dword ptr ds:[r12]"); +} + +TEST_F(x64EmitterTest, FLD_FST_FSTP) +{ + emitter->FLD(32, MatR(RBP)); + emitter->FLD(64, MatR(RBP)); + emitter->FLD(80, MatR(RBP)); + + emitter->FST(32, MatR(RBP)); + emitter->FST(64, MatR(RBP)); + // No 80 bit version of FST + + emitter->FSTP(32, MatR(RBP)); + emitter->FSTP(64, MatR(RBP)); + emitter->FSTP(80, MatR(RBP)); + + ExpectDisassembly("fld dword ptr ss:[rbp] " + "fld qword ptr ss:[rbp] " + "fld tbyte ptr ss:[rbp] " + "fst dword ptr ss:[rbp] " + "fst qword ptr ss:[rbp] " + "fstp dword ptr ss:[rbp] " + "fstp qword ptr ss:[rbp] " + "fstp tbyte ptr ss:[rbp]"); +} + +#define TWO_OP_SSE_TEST(Name, MemBits) \ + TEST_F(x64EmitterTest, Name) \ + { \ + for (const auto& r1 : xmmnames) \ + { \ + for (const auto& r2 : xmmnames) \ + { \ + emitter->Name(r1.reg, R(r2.reg)); \ + ExpectDisassembly(#Name " " + r1.name + ", " + r2.name); \ + } \ + emitter->Name(r1.reg, MatR(R12)); \ + ExpectDisassembly(#Name " " + r1.name + ", " MemBits " ptr ds:[r12]"); \ + } \ + } + +TWO_OP_SSE_TEST(ADDSS, "dword") +TWO_OP_SSE_TEST(SUBSS, "dword") +TWO_OP_SSE_TEST(MULSS, "dword") +TWO_OP_SSE_TEST(DIVSS, "dword") +TWO_OP_SSE_TEST(MINSS, "dword") +TWO_OP_SSE_TEST(MAXSS, "dword") +TWO_OP_SSE_TEST(SQRTSS, "dword") +TWO_OP_SSE_TEST(RSQRTSS, "dword") + +TWO_OP_SSE_TEST(ADDSD, "qword") +TWO_OP_SSE_TEST(SUBSD, "qword") +TWO_OP_SSE_TEST(MULSD, "qword") +TWO_OP_SSE_TEST(DIVSD, "qword") +TWO_OP_SSE_TEST(MINSD, "qword") +TWO_OP_SSE_TEST(MAXSD, "qword") +TWO_OP_SSE_TEST(SQRTSD, "qword") + +TWO_OP_SSE_TEST(ADDPS, "dqword") +TWO_OP_SSE_TEST(SUBPS, "dqword") +TWO_OP_SSE_TEST(MULPS, "dqword") +TWO_OP_SSE_TEST(DIVPS, "dqword") +TWO_OP_SSE_TEST(MINPS, "dqword") +TWO_OP_SSE_TEST(MAXPS, "dqword") +TWO_OP_SSE_TEST(SQRTPS, "dqword") +TWO_OP_SSE_TEST(RSQRTPS, "dqword") +TWO_OP_SSE_TEST(ANDPS, "dqword") +TWO_OP_SSE_TEST(ANDNPS, "dqword") +TWO_OP_SSE_TEST(ORPS, "dqword") +TWO_OP_SSE_TEST(XORPS, "dqword") + +TWO_OP_SSE_TEST(ADDPD, "dqword") +TWO_OP_SSE_TEST(SUBPD, "dqword") +TWO_OP_SSE_TEST(MULPD, "dqword") +TWO_OP_SSE_TEST(DIVPD, "dqword") +TWO_OP_SSE_TEST(MINPD, "dqword") +TWO_OP_SSE_TEST(MAXPD, "dqword") +TWO_OP_SSE_TEST(SQRTPD, "dqword") +TWO_OP_SSE_TEST(ANDPD, "dqword") +TWO_OP_SSE_TEST(ANDNPD, "dqword") +TWO_OP_SSE_TEST(ORPD, "dqword") +TWO_OP_SSE_TEST(XORPD, "dqword") + +TWO_OP_SSE_TEST(MOVDDUP, "qword") + +TWO_OP_SSE_TEST(UNPCKLPS, "dqword") +TWO_OP_SSE_TEST(UNPCKHPS, "dqword") +TWO_OP_SSE_TEST(UNPCKLPD, "dqword") +TWO_OP_SSE_TEST(UNPCKHPD, "dqword") + +TWO_OP_SSE_TEST(COMISS, "dword") +TWO_OP_SSE_TEST(UCOMISS, "dword") +TWO_OP_SSE_TEST(COMISD, "qword") +TWO_OP_SSE_TEST(UCOMISD, "qword") + +// TODO: CMPSS/SD +// TODO: SHUFPS/PD +// TODO: SSE MOVs +// TODO: MOVMSK + +TEST_F(x64EmitterTest, MASKMOVDQU) +{ + for (const auto& r1 : xmmnames) + { + for (const auto& r2 : xmmnames) + { + emitter->MASKMOVDQU(r1.reg, r2.reg); + ExpectDisassembly("maskmovdqu " + r1.name + ", " + r2.name + ", dqword ptr ds:[rdi]"); + } + } +} + +TEST_F(x64EmitterTest, LDDQU) +{ + for (const auto& r : xmmnames) + { + emitter->LDDQU(r.reg, MatR(R12)); + ExpectDisassembly("lddqu " + r.name + ", dqword ptr ds:[r12]"); + } +} + +TWO_OP_SSE_TEST(CVTPS2PD, "dqword") +TWO_OP_SSE_TEST(CVTPD2PS, "dqword") +TWO_OP_SSE_TEST(CVTSS2SD, "dword") +TWO_OP_SSE_TEST(CVTSD2SS, "qword") +TWO_OP_SSE_TEST(CVTDQ2PD, "qword") +TWO_OP_SSE_TEST(CVTPD2DQ, "dqword") +TWO_OP_SSE_TEST(CVTDQ2PS, "dqword") +TWO_OP_SSE_TEST(CVTPS2DQ, "dqword") +TWO_OP_SSE_TEST(CVTTPS2DQ, "dqword") +TWO_OP_SSE_TEST(CVTTPD2DQ, "dqword") + +// TODO: CVT2SI + +TWO_OP_SSE_TEST(PACKSSDW, "dqword") +TWO_OP_SSE_TEST(PACKSSWB, "dqword") +TWO_OP_SSE_TEST(PACKUSDW, "dqword") +TWO_OP_SSE_TEST(PACKUSWB, "dqword") + +TWO_OP_SSE_TEST(PUNPCKLBW, "dqword") +TWO_OP_SSE_TEST(PUNPCKLWD, "dqword") +TWO_OP_SSE_TEST(PUNPCKLDQ, "dqword") + +TWO_OP_SSE_TEST(PTEST, "dqword") +TWO_OP_SSE_TEST(PAND, "dqword") +TWO_OP_SSE_TEST(PANDN, "dqword") +TWO_OP_SSE_TEST(POR, "dqword") +TWO_OP_SSE_TEST(PXOR, "dqword") +TWO_OP_SSE_TEST(PADDB, "dqword") +TWO_OP_SSE_TEST(PADDW, "dqword") +TWO_OP_SSE_TEST(PADDD, "dqword") +TWO_OP_SSE_TEST(PADDQ, "dqword") +TWO_OP_SSE_TEST(PADDSB, "dqword") +TWO_OP_SSE_TEST(PADDSW, "dqword") +TWO_OP_SSE_TEST(PADDUSB, "dqword") +TWO_OP_SSE_TEST(PADDUSW, "dqword") +TWO_OP_SSE_TEST(PSUBB, "dqword") +TWO_OP_SSE_TEST(PSUBW, "dqword") +TWO_OP_SSE_TEST(PSUBD, "dqword") +TWO_OP_SSE_TEST(PSUBQ, "dqword") +TWO_OP_SSE_TEST(PSUBUSB, "dqword") +TWO_OP_SSE_TEST(PSUBUSW, "dqword") +TWO_OP_SSE_TEST(PAVGB, "dqword") +TWO_OP_SSE_TEST(PAVGW, "dqword") +TWO_OP_SSE_TEST(PCMPEQB, "dqword") +TWO_OP_SSE_TEST(PCMPEQW, "dqword") +TWO_OP_SSE_TEST(PCMPEQD, "dqword") +TWO_OP_SSE_TEST(PCMPGTB, "dqword") +TWO_OP_SSE_TEST(PCMPGTW, "dqword") +TWO_OP_SSE_TEST(PCMPGTD, "dqword") +TWO_OP_SSE_TEST(PMADDWD, "dqword") +TWO_OP_SSE_TEST(PSADBW, "dqword") +TWO_OP_SSE_TEST(PMAXSW, "dqword") +TWO_OP_SSE_TEST(PMAXUB, "dqword") +TWO_OP_SSE_TEST(PMINSW, "dqword") +TWO_OP_SSE_TEST(PMINUB, "dqword") +TWO_OP_SSE_TEST(PSHUFB, "dqword") + +// TODO: PEXT/INS/SHUF/MOVMSK + +TWO_OP_SSE_TEST(PMOVSXBW, "qword") +TWO_OP_SSE_TEST(PMOVSXBD, "dword") +TWO_OP_SSE_TEST(PMOVSXBQ, "word") +TWO_OP_SSE_TEST(PMOVSXWD, "qword") +TWO_OP_SSE_TEST(PMOVSXWQ, "dword") +TWO_OP_SSE_TEST(PMOVSXDQ, "qword") + +TWO_OP_SSE_TEST(PMOVZXBW, "qword") +TWO_OP_SSE_TEST(PMOVZXBD, "dword") +TWO_OP_SSE_TEST(PMOVZXBQ, "word") +TWO_OP_SSE_TEST(PMOVZXWD, "qword") +TWO_OP_SSE_TEST(PMOVZXWQ, "dword") +TWO_OP_SSE_TEST(PMOVZXDQ, "qword") + +// TODO: BLEND + +// TODO: AVX + +} // namespace Gen