|  | //===- subzero/unittest/unittest/AssemblerX8632/TestUtil.h ------*- C++ -*-===// | 
|  | // | 
|  | //                        The Subzero Code Generator | 
|  | // | 
|  | // This file is distributed under the University of Illinois Open Source | 
|  | // License. See LICENSE.TXT for details. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // Utility classes for testing the X8632 Assembler. | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #ifndef ASSEMBLERX8632_TESTUTIL_H_ | 
|  | #define ASSEMBLERX8632_TESTUTIL_H_ | 
|  |  | 
|  | #include "IceAssemblerX8632.h" | 
|  | #include "IceDefs.h" | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | #if defined(__unix__) | 
|  | #include <sys/mman.h> | 
|  | #elif defined(_WIN32) | 
|  | #define NOMINMAX | 
|  | #include <Windows.h> | 
|  | #else | 
|  | #error "Platform unsupported" | 
|  | #endif | 
|  |  | 
|  | #include <cassert> | 
|  |  | 
|  | namespace Ice { | 
|  | namespace X8632 { | 
|  | namespace Test { | 
|  |  | 
|  | class AssemblerX8632TestBase : public ::testing::Test { | 
|  | protected: | 
|  | using Address = AssemblerX8632::Traits::Address; | 
|  | using Cond = AssemblerX8632::Traits::Cond; | 
|  | using GPRRegister = AssemblerX8632::Traits::GPRRegister; | 
|  | using ByteRegister = AssemblerX8632::Traits::ByteRegister; | 
|  | using Label = ::Ice::X8632::Label; | 
|  | using Traits = AssemblerX8632::Traits; | 
|  | using XmmRegister = AssemblerX8632::Traits::XmmRegister; | 
|  | using X87STRegister = AssemblerX8632::Traits::X87STRegister; | 
|  |  | 
|  | AssemblerX8632TestBase() { reset(); } | 
|  |  | 
|  | void reset() { Assembler = makeUnique<AssemblerX8632>(); } | 
|  |  | 
|  | AssemblerX8632 *assembler() const { return Assembler.get(); } | 
|  |  | 
|  | size_t codeBytesSize() const { return Assembler->getBufferView().size(); } | 
|  |  | 
|  | const uint8_t *codeBytes() const { | 
|  | return static_cast<const uint8_t *>( | 
|  | static_cast<const void *>(Assembler->getBufferView().data())); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<AssemblerX8632> Assembler; | 
|  | }; | 
|  |  | 
|  | // __ is a helper macro. It allows test cases to emit X8632 assembly | 
|  | // instructions with | 
|  | // | 
|  | //   __ mov(GPRRegister::Reg_Eax, 1); | 
|  | //   __ ret(); | 
|  | // | 
|  | // and so on. The idea of having this was "stolen" from dart's unit tests. | 
|  | #define __ (this->assembler())-> | 
|  |  | 
|  | // AssemblerX8632LowLevelTest verify that the "basic" instructions the tests | 
|  | // rely on are encoded correctly. Therefore, instead of executing the assembled | 
|  | // code, these tests will verify that the assembled bytes are sane. | 
|  | class AssemblerX8632LowLevelTest : public AssemblerX8632TestBase { | 
|  | protected: | 
|  | // verifyBytes is a template helper that takes a Buffer, and a variable number | 
|  | // of bytes. As the name indicates, it is used to verify the bytes for an | 
|  | // instruction encoding. | 
|  | template <int N, int I> static bool verifyBytes(const uint8_t *) { | 
|  | static_assert(I == N, "Invalid template instantiation."); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | template <int N, int I = 0, typename... Args> | 
|  | static bool verifyBytes(const uint8_t *Buffer, uint8_t Byte, | 
|  | Args... OtherBytes) { | 
|  | static_assert(I < N, "Invalid template instantiation."); | 
|  | EXPECT_EQ(Byte, Buffer[I]) << "Byte " << (I + 1) << " of " << N; | 
|  | return verifyBytes<N, I + 1>(Buffer, OtherBytes...) && Buffer[I] == Byte; | 
|  | } | 
|  | }; | 
|  |  | 
|  | // After these tests we should have a sane environment; we know the following | 
|  | // work: | 
|  | // | 
|  | //  (*) zeroing eax, ebx, ecx, edx, edi, and esi; | 
|  | //  (*) call $4 instruction (used for ip materialization); | 
|  | //  (*) register push and pop; | 
|  | //  (*) cmp reg, reg; and | 
|  | //  (*) returning from functions. | 
|  | // | 
|  | // We can now dive into testing each emitting method in AssemblerX8632. Each | 
|  | // test will emit some instructions for performing the test. The assembled | 
|  | // instructions will operate in a "safe" environment. All x86-32 registers are | 
|  | // spilled to the program stack, and the registers are then zeroed out, with the | 
|  | // exception of %esp and %ebp. | 
|  | // | 
|  | // The jitted code and the unittest code will share the same stack. Therefore, | 
|  | // test harnesses need to ensure it does not leave anything it pushed on the | 
|  | // stack. | 
|  | // | 
|  | // %ebp is initialized with a pointer for rIP-based addressing. This pointer is | 
|  | // used for position-independent access to a scratchpad area for use in tests. | 
|  | // This mechanism is used because the test framework needs to generate addresses | 
|  | // that work on both x86-32 and x86-64 hosts, but are encodable using our x86-32 | 
|  | // assembler. This is made possible because the encoding for | 
|  | // | 
|  | //    pushq %rax (x86-64 only) | 
|  | // | 
|  | // is the same as the one for | 
|  | // | 
|  | //    pushl %eax (x86-32 only; not encodable in x86-64) | 
|  | // | 
|  | // Likewise, the encodings for | 
|  | // | 
|  | //    movl offset(%ebp), %reg (32-bit only) | 
|  | //    movl <src>, offset(%ebp) (32-bit only) | 
|  | // | 
|  | // and | 
|  | // | 
|  | //    movl offset(%rbp), %reg (64-bit only) | 
|  | //    movl <src>, offset(%rbp) (64-bit only) | 
|  | // | 
|  | // are also the same. | 
|  | // | 
|  | // We use a call instruction in order to generate a natural sized address on the | 
|  | // stack. Said address is then removed from the stack with a pop %rBP, which can | 
|  | // then be used to address memory safely in either x86-32 or x86-64, as long as | 
|  | // the test code does not perform any arithmetic operation that writes to %rBP. | 
|  | // This PC materialization technique is very common in x86-32 PIC. | 
|  | // | 
|  | // %rBP is used to provide the tests with a scratchpad area that can safely and | 
|  | // portably be written to and read from. This scratchpad area is also used to | 
|  | // store the "final" values in eax, ebx, ecx, edx, esi, and edi, allowing the | 
|  | // harnesses access to 6 "return values" instead of the usual single return | 
|  | // value supported by C++. | 
|  | // | 
|  | // The jitted code will look like the following: | 
|  | // | 
|  | // test: | 
|  | //       push %eax | 
|  | //       push %ebx | 
|  | //       push %ecx | 
|  | //       push %edx | 
|  | //       push %edi | 
|  | //       push %esi | 
|  | //       push %ebp | 
|  | //       call test$materialize_ip | 
|  | // test$materialize_ip:                           <<------- %eBP will point here | 
|  | //       pop  %ebp | 
|  | //       mov  $0, %eax | 
|  | //       mov  $0, %ebx | 
|  | //       mov  $0, %ecx | 
|  | //       mov  $0, %edx | 
|  | //       mov  $0, %edi | 
|  | //       mov  $0, %esi | 
|  | // | 
|  | //       << test code goes here >> | 
|  | // | 
|  | //       mov %eax, { 0 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %ebx, { 4 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %ecx, { 8 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %edx, {12 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %edi, {16 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %esi, {20 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %ebp, {24 + $ScratchpadOffset}(%ebp) | 
|  | //       mov %esp, {28 + $ScratchpadOffset}(%ebp) | 
|  | //       movups %xmm0, {32 + $ScratchpadOffset}(%ebp) | 
|  | //       movups %xmm1, {48 + $ScratchpadOffset}(%ebp) | 
|  | //       movups %xmm2, {64 + $ScratchpadOffset}(%ebp) | 
|  | //       movusp %xmm3, {80 + $ScratchpadOffset}(%ebp) | 
|  | //       movusp %xmm4, {96 + $ScratchpadOffset}(%ebp) | 
|  | //       movusp %xmm5, {112 + $ScratchpadOffset}(%ebp) | 
|  | //       movusp %xmm6, {128 + $ScratchpadOffset}(%ebp) | 
|  | //       movusp %xmm7, {144 + $ScratchpadOffset}(%ebp) | 
|  | // | 
|  | //       pop %ebp | 
|  | //       pop %esi | 
|  | //       pop %edi | 
|  | //       pop %edx | 
|  | //       pop %ecx | 
|  | //       pop %ebx | 
|  | //       pop %eax | 
|  | //       ret | 
|  | // | 
|  | //      << ... >> | 
|  | // | 
|  | // scratchpad:                              <<------- accessed via $Offset(%ebp) | 
|  | // | 
|  | //      << test scratch area >> | 
|  | // | 
|  | // TODO(jpp): test the | 
|  | // | 
|  | //    mov %reg, $Offset(%ebp) | 
|  | //    movups %xmm, $Offset(%ebp) | 
|  | // | 
|  | // encodings using the low level assembler test ensuring that the register | 
|  | // values can be written to the scratchpad area. | 
|  | class AssemblerX8632Test : public AssemblerX8632TestBase { | 
|  | protected: | 
|  | // Dqword is used to represent 128-bit data types. The Dqword's contents are | 
|  | // the same as the contents read from memory. Tests can then use the union | 
|  | // members to verify the tests' outputs. | 
|  | // | 
|  | // NOTE: We want sizeof(Dqword) == sizeof(uint64_t) * 2. In other words, we | 
|  | // want Dqword's contents to be **exactly** what the memory contents were so | 
|  | // that we can do, e.g., | 
|  | // | 
|  | // ... | 
|  | // float Ret[4]; | 
|  | // // populate Ret | 
|  | // return *reinterpret_cast<Dqword *>(&Ret); | 
|  | // | 
|  | // While being an ugly hack, this kind of return statements are used | 
|  | // extensively in the PackedArith (see below) class. | 
|  | union Dqword { | 
|  | template <typename T0, typename T1, typename T2, typename T3, | 
|  | typename = typename std::enable_if< | 
|  | std::is_floating_point<T0>::value>::type> | 
|  | Dqword(T0 F0, T1 F1, T2 F2, T3 F3) { | 
|  | F32[0] = F0; | 
|  | F32[1] = F1; | 
|  | F32[2] = F2; | 
|  | F32[3] = F3; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | Dqword(typename std::enable_if<std::is_same<T, int32_t>::value, T>::type I0, | 
|  | T I1, T I2, T I3) { | 
|  | I32[0] = I0; | 
|  | I32[1] = I1; | 
|  | I32[2] = I2; | 
|  | I32[3] = I3; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | Dqword(typename std::enable_if<std::is_same<T, uint64_t>::value, T>::type | 
|  | U64_0, | 
|  | T U64_1) { | 
|  | U64[0] = U64_0; | 
|  | U64[1] = U64_1; | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | Dqword(typename std::enable_if<std::is_same<T, double>::value, T>::type D0, | 
|  | T D1) { | 
|  | F64[0] = D0; | 
|  | F64[1] = D1; | 
|  | } | 
|  |  | 
|  | bool operator==(const Dqword &Rhs) const { | 
|  | return std::memcmp(this, &Rhs, sizeof(*this)) == 0; | 
|  | } | 
|  |  | 
|  | double F64[2]; | 
|  | uint64_t U64[2]; | 
|  | int64_t I64[2]; | 
|  |  | 
|  | float F32[4]; | 
|  | uint32_t U32[4]; | 
|  | int32_t I32[4]; | 
|  |  | 
|  | uint16_t U16[8]; | 
|  | int16_t I16[8]; | 
|  |  | 
|  | uint8_t U8[16]; | 
|  | int8_t I8[16]; | 
|  |  | 
|  | private: | 
|  | Dqword() = delete; | 
|  | }; | 
|  |  | 
|  | // As stated, we want this condition to hold, so we assert. | 
|  | static_assert(sizeof(Dqword) == 2 * sizeof(uint64_t), | 
|  | "Dqword has the wrong size."); | 
|  |  | 
|  | // PackedArith is an interface provider for Dqwords. PackedArith's C argument | 
|  | // is the undelying Dqword's type, which is then used so that we can define | 
|  | // operators in terms of C++ operators on the underlying elements' type. | 
|  | template <typename C> class PackedArith { | 
|  | public: | 
|  | static constexpr uint32_t N = sizeof(Dqword) / sizeof(C); | 
|  | static_assert(N * sizeof(C) == sizeof(Dqword), | 
|  | "Invalid template paramenter."); | 
|  | static_assert((N & 1) == 0, "N should be divisible by 2"); | 
|  |  | 
|  | #define DefinePackedComparisonOperator(Op)                                     \ | 
|  | template <typename Container = C, int Size = N>                              \ | 
|  | typename std::enable_if<std::is_floating_point<Container>::value,            \ | 
|  | Dqword>::type                                        \ | 
|  | operator Op(const Dqword &Rhs) const {                                       \ | 
|  | using ElemType =                                                           \ | 
|  | typename std::conditional<std::is_same<float, Container>::value,       \ | 
|  | int32_t, int64_t>::type;                     \ | 
|  | static_assert(sizeof(ElemType) == sizeof(Container),                       \ | 
|  | "Check ElemType definition.");                               \ | 
|  | const ElemType *const RhsPtr =                                             \ | 
|  | reinterpret_cast<const ElemType *const>(&Rhs);                         \ | 
|  | const ElemType *const LhsPtr =                                             \ | 
|  | reinterpret_cast<const ElemType *const>(&Lhs);                         \ | 
|  | ElemType Ret[N];                                                           \ | 
|  | for (uint32_t i = 0; i < N; ++i) {                                         \ | 
|  | Ret[i] = (LhsPtr[i] Op RhsPtr[i]) ? -1 : 0;                              \ | 
|  | }                                                                          \ | 
|  | return *reinterpret_cast<Dqword *>(&Ret);                                  \ | 
|  | } | 
|  |  | 
|  | DefinePackedComparisonOperator(< ); | 
|  | DefinePackedComparisonOperator(<= ); | 
|  | DefinePackedComparisonOperator(> ); | 
|  | DefinePackedComparisonOperator(>= ); | 
|  | DefinePackedComparisonOperator(== ); | 
|  | DefinePackedComparisonOperator(!= ); | 
|  |  | 
|  | #undef DefinePackedComparisonOperator | 
|  |  | 
|  | #define DefinePackedOrdUnordComparisonOperator(Op, Ordered)                    \ | 
|  | template <typename Container = C, int Size = N>                              \ | 
|  | typename std::enable_if<std::is_floating_point<Container>::value,            \ | 
|  | Dqword>::type                                        \ | 
|  | Op(const Dqword &Rhs) const {                                                \ | 
|  | using ElemType =                                                           \ | 
|  | typename std::conditional<std::is_same<float, Container>::value,       \ | 
|  | int32_t, int64_t>::type;                     \ | 
|  | static_assert(sizeof(ElemType) == sizeof(Container),                       \ | 
|  | "Check ElemType definition.");                               \ | 
|  | const Container *const RhsPtr =                                            \ | 
|  | reinterpret_cast<const Container *const>(&Rhs);                        \ | 
|  | const Container *const LhsPtr =                                            \ | 
|  | reinterpret_cast<const Container *const>(&Lhs);                        \ | 
|  | ElemType Ret[N];                                                           \ | 
|  | for (uint32_t i = 0; i < N; ++i) {                                         \ | 
|  | Ret[i] = (!(LhsPtr[i] == LhsPtr[i]) || !(RhsPtr[i] == RhsPtr[i])) !=     \ | 
|  | (Ordered)                                               \ | 
|  | ? -1                                                        \ | 
|  | : 0;                                                        \ | 
|  | }                                                                          \ | 
|  | return *reinterpret_cast<Dqword *>(&Ret);                                  \ | 
|  | } | 
|  |  | 
|  | DefinePackedOrdUnordComparisonOperator(ord, true); | 
|  | DefinePackedOrdUnordComparisonOperator(unord, false); | 
|  | #undef DefinePackedOrdUnordComparisonOperator | 
|  |  | 
|  | #define DefinePackedArithOperator(Op, RhsIndexChanges, NeedsInt)               \ | 
|  | template <typename Container = C, int Size = N>                              \ | 
|  | Dqword operator Op(const Dqword &Rhs) const {                                \ | 
|  | using ElemTypeForFp = typename std::conditional<                           \ | 
|  | !(NeedsInt), Container,                                                \ | 
|  | typename std::conditional<                                             \ | 
|  | std::is_same<Container, float>::value, uint32_t,                   \ | 
|  | typename std::conditional<std::is_same<Container, double>::value,  \ | 
|  | uint64_t, void>::type>::type>::type;     \ | 
|  | using ElemType =                                                           \ | 
|  | typename std::conditional<std::is_integral<Container>::value,          \ | 
|  | Container, ElemTypeForFp>::type;             \ | 
|  | static_assert(!std::is_same<void, ElemType>::value,                        \ | 
|  | "Check ElemType definition.");                               \ | 
|  | const ElemType *const RhsPtr =                                             \ | 
|  | reinterpret_cast<const ElemType *const>(&Rhs);                         \ | 
|  | const ElemType *const LhsPtr =                                             \ | 
|  | reinterpret_cast<const ElemType *const>(&Lhs);                         \ | 
|  | ElemType Ret[N];                                                           \ | 
|  | for (uint32_t i = 0; i < N; ++i) {                                         \ | 
|  | Ret[i] = LhsPtr[i] Op RhsPtr[(RhsIndexChanges) ? i : 0];                 \ | 
|  | }                                                                          \ | 
|  | return *reinterpret_cast<Dqword *>(&Ret);                                  \ | 
|  | } | 
|  |  | 
|  | DefinePackedArithOperator(>>, false, true); | 
|  | DefinePackedArithOperator(<<, false, true); | 
|  | DefinePackedArithOperator(+, true, false); | 
|  | DefinePackedArithOperator(-, true, false); | 
|  | DefinePackedArithOperator(/, true, false); | 
|  | DefinePackedArithOperator(&, true, true); | 
|  | DefinePackedArithOperator(|, true, true); | 
|  | DefinePackedArithOperator (^, true, true); | 
|  |  | 
|  | #undef DefinePackedArithOperator | 
|  |  | 
|  | #define DefinePackedArithShiftImm(Op)                                          \ | 
|  | template <typename Container = C, int Size = N>                              \ | 
|  | Dqword operator Op(uint8_t imm) const {                                      \ | 
|  | const Container *const LhsPtr =                                            \ | 
|  | reinterpret_cast<const Container *const>(&Lhs);                        \ | 
|  | Container Ret[N];                                                          \ | 
|  | for (uint32_t i = 0; i < N; ++i) {                                         \ | 
|  | Ret[i] = LhsPtr[i] Op imm;                                               \ | 
|  | }                                                                          \ | 
|  | return *reinterpret_cast<Dqword *>(&Ret);                                  \ | 
|  | } | 
|  |  | 
|  | DefinePackedArithShiftImm(>> ); | 
|  | DefinePackedArithShiftImm(<< ); | 
|  |  | 
|  | #undef DefinePackedArithShiftImm | 
|  |  | 
|  | template <typename Container = C, int Size = N> | 
|  | typename std::enable_if<std::is_signed<Container>::value || | 
|  | std::is_floating_point<Container>::value, | 
|  | Dqword>::type | 
|  | operator*(const Dqword &Rhs) const { | 
|  | static_assert((std::is_integral<Container>::value && | 
|  | sizeof(Container) < sizeof(uint64_t)) || | 
|  | std::is_floating_point<Container>::value, | 
|  | "* is only defined for i(8|16|32), and fp types."); | 
|  |  | 
|  | const Container *const RhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Rhs); | 
|  | const Container *const LhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Lhs); | 
|  | Container Ret[Size]; | 
|  | for (uint32_t i = 0; i < Size; ++i) { | 
|  | Ret[i] = LhsPtr[i] * RhsPtr[i]; | 
|  | } | 
|  | return *reinterpret_cast<Dqword *>(&Ret); | 
|  | } | 
|  |  | 
|  | template <typename Container = C, int Size = N, | 
|  | typename = typename std::enable_if< | 
|  | !std::is_signed<Container>::value>::type> | 
|  | Dqword operator*(const Dqword &Rhs) const { | 
|  | static_assert(std::is_integral<Container>::value && | 
|  | sizeof(Container) < sizeof(uint64_t), | 
|  | "* is only defined for ui(8|16|32)"); | 
|  | using NextType = typename std::conditional< | 
|  | sizeof(Container) == 1, uint16_t, | 
|  | typename std::conditional<sizeof(Container) == 2, uint32_t, | 
|  | uint64_t>::type>::type; | 
|  | static_assert(sizeof(Container) * 2 == sizeof(NextType), | 
|  | "Unexpected size"); | 
|  |  | 
|  | const Container *const RhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Rhs); | 
|  | const Container *const LhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Lhs); | 
|  | NextType Ret[Size / 2]; | 
|  | for (uint32_t i = 0; i < Size; i += 2) { | 
|  | Ret[i / 2] = | 
|  | static_cast<NextType>(LhsPtr[i]) * static_cast<NextType>(RhsPtr[i]); | 
|  | } | 
|  | return *reinterpret_cast<Dqword *>(&Ret); | 
|  | } | 
|  |  | 
|  | template <typename Container = C, int Size = N> | 
|  | PackedArith<Container> operator~() const { | 
|  | const Container *const LhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Lhs); | 
|  | Container Ret[Size]; | 
|  | for (uint32_t i = 0; i < Size; ++i) { | 
|  | Ret[i] = ~LhsPtr[i]; | 
|  | } | 
|  | return PackedArith<Container>(*reinterpret_cast<Dqword *>(&Ret)); | 
|  | } | 
|  |  | 
|  | #define MinMaxOperations(Name, Suffix)                                         \ | 
|  | template <typename Container = C, int Size = N>                              \ | 
|  | Dqword Name##Suffix(const Dqword &Rhs) const {                               \ | 
|  | static_assert(std::is_floating_point<Container>::value,                    \ | 
|  | #Name #Suffix "ps is only available for fp.");               \ | 
|  | const Container *const RhsPtr =                                            \ | 
|  | reinterpret_cast<const Container *const>(&Rhs);                        \ | 
|  | const Container *const LhsPtr =                                            \ | 
|  | reinterpret_cast<const Container *const>(&Lhs);                        \ | 
|  | Container Ret[Size];                                                       \ | 
|  | for (uint32_t i = 0; i < Size; ++i) {                                      \ | 
|  | Ret[i] = std::Name(LhsPtr[i], RhsPtr[i]);                                \ | 
|  | }                                                                          \ | 
|  | return *reinterpret_cast<Dqword *>(&Ret);                                  \ | 
|  | } | 
|  |  | 
|  | MinMaxOperations(max, ps); | 
|  | MinMaxOperations(max, pd); | 
|  | MinMaxOperations(min, ps); | 
|  | MinMaxOperations(min, pd); | 
|  | #undef MinMaxOperations | 
|  |  | 
|  | template <typename Container = C, int Size = N> | 
|  | Dqword blendWith(const Dqword &Rhs, const Dqword &Mask) const { | 
|  | using MaskType = typename std::conditional< | 
|  | sizeof(Container) == 1, int8_t, | 
|  | typename std::conditional<sizeof(Container) == 2, int16_t, | 
|  | int32_t>::type>::type; | 
|  | static_assert(sizeof(MaskType) == sizeof(Container), | 
|  | "MaskType has the wrong size."); | 
|  | const Container *const RhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Rhs); | 
|  | const Container *const LhsPtr = | 
|  | reinterpret_cast<const Container *const>(&Lhs); | 
|  | const MaskType *const MaskPtr = | 
|  | reinterpret_cast<const MaskType *const>(&Mask); | 
|  | Container Ret[Size]; | 
|  | for (int i = 0; i < Size; ++i) { | 
|  | Ret[i] = ((MaskPtr[i] < 0) ? RhsPtr : LhsPtr)[i]; | 
|  | } | 
|  | return *reinterpret_cast<Dqword *>(&Ret); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // The AssemblerX8632Test class needs to be a friend so that it can create | 
|  | // PackedArith objects (see below.) | 
|  | friend class AssemblerX8632Test; | 
|  |  | 
|  | explicit PackedArith(const Dqword &MyLhs) : Lhs(MyLhs) {} | 
|  |  | 
|  | // Lhs can't be a & because operator~ returns a temporary object that needs | 
|  | // access to its own Dqword. | 
|  | const Dqword Lhs; | 
|  | }; | 
|  |  | 
|  | // Named constructor for PackedArith objects. | 
|  | template <typename C> static PackedArith<C> packedAs(const Dqword &D) { | 
|  | return PackedArith<C>(D); | 
|  | } | 
|  |  | 
|  | AssemblerX8632Test() { reset(); } | 
|  |  | 
|  | void reset() { | 
|  | AssemblerX8632TestBase::reset(); | 
|  |  | 
|  | NeedsEpilogue = true; | 
|  | // These dwords are allocated for saving the GPR state after the jitted code | 
|  | // runs. | 
|  | NumAllocatedDwords = AssembledTest::ScratchpadSlots; | 
|  | addPrologue(); | 
|  | } | 
|  |  | 
|  | // AssembledTest is a wrapper around a PROT_EXEC mmap'ed buffer. This buffer | 
|  | // contains both the test code as well as prologue/epilogue, and the | 
|  | // scratchpad area that tests may use -- all tests use this scratchpad area | 
|  | // for storing the processor's registers after the tests executed. This class | 
|  | // also exposes helper methods for reading the register state after test | 
|  | // execution, as well as for reading the scratchpad area. | 
|  | class AssembledTest { | 
|  | AssembledTest() = delete; | 
|  | AssembledTest(const AssembledTest &) = delete; | 
|  | AssembledTest &operator=(const AssembledTest &) = delete; | 
|  |  | 
|  | public: | 
|  | static constexpr uint32_t MaximumCodeSize = 1 << 20; | 
|  | static constexpr uint32_t EaxSlot = 0; | 
|  | static constexpr uint32_t EbxSlot = 1; | 
|  | static constexpr uint32_t EcxSlot = 2; | 
|  | static constexpr uint32_t EdxSlot = 3; | 
|  | static constexpr uint32_t EdiSlot = 4; | 
|  | static constexpr uint32_t EsiSlot = 5; | 
|  | static constexpr uint32_t EbpSlot = 6; | 
|  | static constexpr uint32_t EspSlot = 7; | 
|  | // save 4 dwords for each xmm registers. | 
|  | static constexpr uint32_t Xmm0Slot = 8; | 
|  | static constexpr uint32_t Xmm1Slot = 12; | 
|  | static constexpr uint32_t Xmm2Slot = 16; | 
|  | static constexpr uint32_t Xmm3Slot = 20; | 
|  | static constexpr uint32_t Xmm4Slot = 24; | 
|  | static constexpr uint32_t Xmm5Slot = 28; | 
|  | static constexpr uint32_t Xmm6Slot = 32; | 
|  | static constexpr uint32_t Xmm7Slot = 36; | 
|  | static constexpr uint32_t ScratchpadSlots = 40; | 
|  |  | 
|  | AssembledTest(const uint8_t *Data, const size_t MySize, | 
|  | const size_t ExtraStorageDwords) | 
|  | : Size(MaximumCodeSize + 4 * ExtraStorageDwords) { | 
|  | // MaxCodeSize is needed because EXPECT_LT needs a symbol with a name -- | 
|  | // probably a compiler bug? | 
|  | uint32_t MaxCodeSize = MaximumCodeSize; | 
|  | EXPECT_LT(MySize, MaxCodeSize); | 
|  | assert(MySize < MaximumCodeSize); | 
|  |  | 
|  | #if defined(__unix__) | 
|  | ExecutableData = mmap(nullptr, Size, PROT_WRITE | PROT_READ | PROT_EXEC, | 
|  | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | 
|  | EXPECT_NE(MAP_FAILED, ExecutableData) << strerror(errno); | 
|  | assert(MAP_FAILED != ExecutableData); | 
|  | #elif defined(_WIN32) | 
|  | ExecutableData = VirtualAlloc(NULL, Size, MEM_COMMIT | MEM_RESERVE, | 
|  | PAGE_EXECUTE_READWRITE); | 
|  | EXPECT_NE(nullptr, ExecutableData) << strerror(errno); | 
|  | assert(nullptr != ExecutableData); | 
|  | #else | 
|  | #error "Platform unsupported" | 
|  | #endif | 
|  |  | 
|  | std::memcpy(ExecutableData, Data, MySize); | 
|  | } | 
|  |  | 
|  | // We allow AssembledTest to be moved so that we can return objects of | 
|  | // this type. | 
|  | AssembledTest(AssembledTest &&Buffer) | 
|  | : ExecutableData(Buffer.ExecutableData), Size(Buffer.Size) { | 
|  | Buffer.ExecutableData = nullptr; | 
|  | Buffer.Size = 0; | 
|  | } | 
|  |  | 
|  | AssembledTest &operator=(AssembledTest &&Buffer) { | 
|  | ExecutableData = Buffer.ExecutableData; | 
|  | Buffer.ExecutableData = nullptr; | 
|  | Size = Buffer.Size; | 
|  | Buffer.Size = 0; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | ~AssembledTest() { | 
|  | if (ExecutableData != nullptr) { | 
|  | #if defined(__unix__) | 
|  | munmap(ExecutableData, Size); | 
|  | #elif defined(_WIN32) | 
|  | VirtualFree(ExecutableData, 0, MEM_RELEASE); | 
|  | #endif | 
|  | ExecutableData = nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | void run() const { reinterpret_cast<void (*)()>(ExecutableData)(); } | 
|  |  | 
|  | uint32_t eax() const { return contentsOfDword(AssembledTest::EaxSlot); } | 
|  |  | 
|  | uint32_t ebx() const { return contentsOfDword(AssembledTest::EbxSlot); } | 
|  |  | 
|  | uint32_t ecx() const { return contentsOfDword(AssembledTest::EcxSlot); } | 
|  |  | 
|  | uint32_t edx() const { return contentsOfDword(AssembledTest::EdxSlot); } | 
|  |  | 
|  | uint32_t edi() const { return contentsOfDword(AssembledTest::EdiSlot); } | 
|  |  | 
|  | uint32_t esi() const { return contentsOfDword(AssembledTest::EsiSlot); } | 
|  |  | 
|  | uint32_t ebp() const { return contentsOfDword(AssembledTest::EbpSlot); } | 
|  |  | 
|  | uint32_t esp() const { return contentsOfDword(AssembledTest::EspSlot); } | 
|  |  | 
|  | template <typename T> T xmm0() const { | 
|  | return xmm<T>(AssembledTest::Xmm0Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm1() const { | 
|  | return xmm<T>(AssembledTest::Xmm1Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm2() const { | 
|  | return xmm<T>(AssembledTest::Xmm2Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm3() const { | 
|  | return xmm<T>(AssembledTest::Xmm3Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm4() const { | 
|  | return xmm<T>(AssembledTest::Xmm4Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm5() const { | 
|  | return xmm<T>(AssembledTest::Xmm5Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm6() const { | 
|  | return xmm<T>(AssembledTest::Xmm6Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> T xmm7() const { | 
|  | return xmm<T>(AssembledTest::Xmm7Slot); | 
|  | } | 
|  |  | 
|  | // contentsOfDword is used for reading the values in the scratchpad area. | 
|  | // Valid arguments are the dword ids returned by | 
|  | // AssemblerX8632Test::allocateDword() -- other inputs are considered | 
|  | // invalid, and are not guaranteed to work if the implementation changes. | 
|  | template <typename T = uint32_t, typename = typename std::enable_if< | 
|  | sizeof(T) == sizeof(uint32_t)>::type> | 
|  | T contentsOfDword(uint32_t Dword) const { | 
|  | return *reinterpret_cast<T *>(static_cast<uint8_t *>(ExecutableData) + | 
|  | dwordOffset(Dword)); | 
|  | } | 
|  |  | 
|  | template <typename T = uint64_t, typename = typename std::enable_if< | 
|  | sizeof(T) == sizeof(uint64_t)>::type> | 
|  | T contentsOfQword(uint32_t InitialDword) const { | 
|  | return *reinterpret_cast<T *>(static_cast<uint8_t *>(ExecutableData) + | 
|  | dwordOffset(InitialDword)); | 
|  | } | 
|  |  | 
|  | Dqword contentsOfDqword(uint32_t InitialDword) const { | 
|  | return *reinterpret_cast<Dqword *>( | 
|  | static_cast<uint8_t *>(ExecutableData) + | 
|  | dwordOffset(InitialDword)); | 
|  | } | 
|  |  | 
|  | template <typename T = uint32_t, typename = typename std::enable_if< | 
|  | sizeof(T) == sizeof(uint32_t)>::type> | 
|  | void setDwordTo(uint32_t Dword, T value) { | 
|  | *reinterpret_cast<uint32_t *>(static_cast<uint8_t *>(ExecutableData) + | 
|  | dwordOffset(Dword)) = | 
|  | *reinterpret_cast<uint32_t *>(&value); | 
|  | } | 
|  |  | 
|  | template <typename T = uint64_t, typename = typename std::enable_if< | 
|  | sizeof(T) == sizeof(uint64_t)>::type> | 
|  | void setQwordTo(uint32_t InitialDword, T value) { | 
|  | *reinterpret_cast<uint64_t *>(static_cast<uint8_t *>(ExecutableData) + | 
|  | dwordOffset(InitialDword)) = | 
|  | *reinterpret_cast<uint64_t *>(&value); | 
|  | } | 
|  |  | 
|  | void setDqwordTo(uint32_t InitialDword, const Dqword &qdword) { | 
|  | setQwordTo(InitialDword, qdword.U64[0]); | 
|  | setQwordTo(InitialDword + 2, qdword.U64[1]); | 
|  | } | 
|  |  | 
|  | private: | 
|  | template <typename T> | 
|  | typename std::enable_if<std::is_same<T, Dqword>::value, Dqword>::type | 
|  | xmm(uint8_t Slot) const { | 
|  | return contentsOfDqword(Slot); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | typename std::enable_if<!std::is_same<T, Dqword>::value, T>::type | 
|  | xmm(uint8_t Slot) const { | 
|  | constexpr bool TIs64Bit = sizeof(T) == sizeof(uint64_t); | 
|  | using _64BitType = typename std::conditional<TIs64Bit, T, uint64_t>::type; | 
|  | using _32BitType = typename std::conditional<TIs64Bit, uint32_t, T>::type; | 
|  | if (TIs64Bit) { | 
|  | return contentsOfQword<_64BitType>(Slot); | 
|  | } | 
|  | return contentsOfDword<_32BitType>(Slot); | 
|  | } | 
|  |  | 
|  | static uint32_t dwordOffset(uint32_t Index) { | 
|  | return MaximumCodeSize + (Index * 4); | 
|  | } | 
|  |  | 
|  | void *ExecutableData = nullptr; | 
|  | size_t Size; | 
|  | }; | 
|  |  | 
|  | // assemble created an AssembledTest with the jitted code. The first time | 
|  | // assemble is executed it will add the epilogue to the jitted code (which is | 
|  | // the reason why this method is not const qualified. | 
|  | AssembledTest assemble() { | 
|  | if (NeedsEpilogue) { | 
|  | addEpilogue(); | 
|  | } | 
|  | NeedsEpilogue = false; | 
|  |  | 
|  | for (const auto *Fixup : assembler()->fixups()) { | 
|  | Fixup->emitOffset(assembler()); | 
|  | } | 
|  |  | 
|  | return AssembledTest(codeBytes(), codeBytesSize(), NumAllocatedDwords); | 
|  | } | 
|  |  | 
|  | // Allocates a new dword slot in the test's scratchpad area. | 
|  | uint32_t allocateDword() { return NumAllocatedDwords++; } | 
|  |  | 
|  | // Allocates a new qword slot in the test's scratchpad area. | 
|  | uint32_t allocateQword() { | 
|  | uint32_t InitialDword = allocateDword(); | 
|  | allocateDword(); | 
|  | return InitialDword; | 
|  | } | 
|  |  | 
|  | // Allocates a new dqword slot in the test's scratchpad area. | 
|  | uint32_t allocateDqword() { | 
|  | uint32_t InitialDword = allocateQword(); | 
|  | allocateQword(); | 
|  | return InitialDword; | 
|  | } | 
|  |  | 
|  | Address dwordAddress(uint32_t Dword) { | 
|  | return Address(GPRRegister::Encoded_Reg_ebp, dwordDisp(Dword), nullptr); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // e??SlotAddress returns an AssemblerX8632::Traits::Address that can be used | 
|  | // by the test cases to encode an address operand for accessing the slot for | 
|  | // the specified register. These are all private for, when jitting the test | 
|  | // code, tests should not tamper with these values. Besides, during the test | 
|  | // execution these slots' contents are undefined and should not be accessed. | 
|  | Address eaxSlotAddress() { return dwordAddress(AssembledTest::EaxSlot); } | 
|  | Address ebxSlotAddress() { return dwordAddress(AssembledTest::EbxSlot); } | 
|  | Address ecxSlotAddress() { return dwordAddress(AssembledTest::EcxSlot); } | 
|  | Address edxSlotAddress() { return dwordAddress(AssembledTest::EdxSlot); } | 
|  | Address ediSlotAddress() { return dwordAddress(AssembledTest::EdiSlot); } | 
|  | Address esiSlotAddress() { return dwordAddress(AssembledTest::EsiSlot); } | 
|  | Address ebpSlotAddress() { return dwordAddress(AssembledTest::EbpSlot); } | 
|  | Address espSlotAddress() { return dwordAddress(AssembledTest::EspSlot); } | 
|  | Address xmm0SlotAddress() { return dwordAddress(AssembledTest::Xmm0Slot); } | 
|  | Address xmm1SlotAddress() { return dwordAddress(AssembledTest::Xmm1Slot); } | 
|  | Address xmm2SlotAddress() { return dwordAddress(AssembledTest::Xmm2Slot); } | 
|  | Address xmm3SlotAddress() { return dwordAddress(AssembledTest::Xmm3Slot); } | 
|  | Address xmm4SlotAddress() { return dwordAddress(AssembledTest::Xmm4Slot); } | 
|  | Address xmm5SlotAddress() { return dwordAddress(AssembledTest::Xmm5Slot); } | 
|  | Address xmm6SlotAddress() { return dwordAddress(AssembledTest::Xmm6Slot); } | 
|  | Address xmm7SlotAddress() { return dwordAddress(AssembledTest::Xmm7Slot); } | 
|  |  | 
|  | // Returns the displacement that should be used when accessing the specified | 
|  | // Dword in the scratchpad area. It needs to adjust for the initial | 
|  | // instructions that are emitted before the call that materializes the IP | 
|  | // register. | 
|  | uint32_t dwordDisp(uint32_t Dword) const { | 
|  | EXPECT_LT(Dword, NumAllocatedDwords); | 
|  | assert(Dword < NumAllocatedDwords); | 
|  | static constexpr uint8_t PushBytes = 1; | 
|  | static constexpr uint8_t CallImmBytes = 5; | 
|  | return AssembledTest::MaximumCodeSize + (Dword * 4) - | 
|  | (7 * PushBytes + CallImmBytes); | 
|  | } | 
|  |  | 
|  | void addPrologue() { | 
|  | __ pushl(GPRRegister::Encoded_Reg_eax); | 
|  | __ pushl(GPRRegister::Encoded_Reg_ebx); | 
|  | __ pushl(GPRRegister::Encoded_Reg_ecx); | 
|  | __ pushl(GPRRegister::Encoded_Reg_edx); | 
|  | __ pushl(GPRRegister::Encoded_Reg_edi); | 
|  | __ pushl(GPRRegister::Encoded_Reg_esi); | 
|  | __ pushl(GPRRegister::Encoded_Reg_ebp); | 
|  |  | 
|  | __ call(Immediate(4)); | 
|  | __ popl(GPRRegister::Encoded_Reg_ebp); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_eax, Immediate(0x00)); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_ebx, Immediate(0x00)); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_ecx, Immediate(0x00)); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_edx, Immediate(0x00)); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_edi, Immediate(0x00)); | 
|  | __ mov(IceType_i32, GPRRegister::Encoded_Reg_esi, Immediate(0x00)); | 
|  | } | 
|  |  | 
|  | void addEpilogue() { | 
|  | __ mov(IceType_i32, eaxSlotAddress(), GPRRegister::Encoded_Reg_eax); | 
|  | __ mov(IceType_i32, ebxSlotAddress(), GPRRegister::Encoded_Reg_ebx); | 
|  | __ mov(IceType_i32, ecxSlotAddress(), GPRRegister::Encoded_Reg_ecx); | 
|  | __ mov(IceType_i32, edxSlotAddress(), GPRRegister::Encoded_Reg_edx); | 
|  | __ mov(IceType_i32, ediSlotAddress(), GPRRegister::Encoded_Reg_edi); | 
|  | __ mov(IceType_i32, esiSlotAddress(), GPRRegister::Encoded_Reg_esi); | 
|  | __ mov(IceType_i32, ebpSlotAddress(), GPRRegister::Encoded_Reg_ebp); | 
|  | __ mov(IceType_i32, espSlotAddress(), GPRRegister::Encoded_Reg_esp); | 
|  | __ movups(xmm0SlotAddress(), XmmRegister::Encoded_Reg_xmm0); | 
|  | __ movups(xmm1SlotAddress(), XmmRegister::Encoded_Reg_xmm1); | 
|  | __ movups(xmm2SlotAddress(), XmmRegister::Encoded_Reg_xmm2); | 
|  | __ movups(xmm3SlotAddress(), XmmRegister::Encoded_Reg_xmm3); | 
|  | __ movups(xmm4SlotAddress(), XmmRegister::Encoded_Reg_xmm4); | 
|  | __ movups(xmm5SlotAddress(), XmmRegister::Encoded_Reg_xmm5); | 
|  | __ movups(xmm6SlotAddress(), XmmRegister::Encoded_Reg_xmm6); | 
|  | __ movups(xmm7SlotAddress(), XmmRegister::Encoded_Reg_xmm7); | 
|  |  | 
|  | __ popl(GPRRegister::Encoded_Reg_ebp); | 
|  | __ popl(GPRRegister::Encoded_Reg_esi); | 
|  | __ popl(GPRRegister::Encoded_Reg_edi); | 
|  | __ popl(GPRRegister::Encoded_Reg_edx); | 
|  | __ popl(GPRRegister::Encoded_Reg_ecx); | 
|  | __ popl(GPRRegister::Encoded_Reg_ebx); | 
|  | __ popl(GPRRegister::Encoded_Reg_eax); | 
|  |  | 
|  | __ ret(); | 
|  | } | 
|  |  | 
|  | bool NeedsEpilogue; | 
|  | uint32_t NumAllocatedDwords; | 
|  | }; | 
|  |  | 
|  | } // end of namespace Test | 
|  | } // end of namespace X8632 | 
|  | } // end of namespace Ice | 
|  |  | 
|  | #endif // ASSEMBLERX8632_TESTUTIL_H_ |