//===- 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 #elif defined(_WIN32) #define NOMINMAX #include #else #error "Platform unsupported" #endif #include 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 *assembler() const { return Assembler.get(); } size_t codeBytesSize() const { return Assembler->getBufferView().size(); } const uint8_t *codeBytes() const { return static_cast( static_cast(Assembler->getBufferView().data())); } private: std::unique_ptr 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 static bool verifyBytes(const uint8_t *) { static_assert(I == N, "Invalid template instantiation."); return true; } template 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(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 , offset(%ebp) (32-bit only) // // and // // movl offset(%rbp), %reg (64-bit only) // movl , 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(&Ret); // // While being an ugly hack, this kind of return statements are used // extensively in the PackedArith (see below) class. union Dqword { template ::value>::type> Dqword(T0 F0, T1 F1, T2 F2, T3 F3) { F32[0] = F0; F32[1] = F1; F32[2] = F2; F32[3] = F3; } template Dqword(typename std::enable_if::value, T>::type I0, T I1, T I2, T I3) { I32[0] = I0; I32[1] = I1; I32[2] = I2; I32[3] = I3; } template Dqword(typename std::enable_if::value, T>::type U64_0, T U64_1) { U64[0] = U64_0; U64[1] = U64_1; } template Dqword(typename std::enable_if::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 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 std::enable_if::value, \ Dqword>::type \ operator Op(const Dqword &Rhs) const { \ using ElemType = \ typename std::conditional::value, \ int32_t, int64_t>::type; \ static_assert(sizeof(ElemType) == sizeof(Container), \ "Check ElemType definition."); \ const ElemType *const RhsPtr = \ reinterpret_cast(&Rhs); \ const ElemType *const LhsPtr = \ reinterpret_cast(&Lhs); \ ElemType Ret[N]; \ for (uint32_t i = 0; i < N; ++i) { \ Ret[i] = (LhsPtr[i] Op RhsPtr[i]) ? -1 : 0; \ } \ return *reinterpret_cast(&Ret); \ } DefinePackedComparisonOperator(< ); DefinePackedComparisonOperator(<= ); DefinePackedComparisonOperator(> ); DefinePackedComparisonOperator(>= ); DefinePackedComparisonOperator(== ); DefinePackedComparisonOperator(!= ); #undef DefinePackedComparisonOperator #define DefinePackedOrdUnordComparisonOperator(Op, Ordered) \ template \ typename std::enable_if::value, \ Dqword>::type \ Op(const Dqword &Rhs) const { \ using ElemType = \ typename std::conditional::value, \ int32_t, int64_t>::type; \ static_assert(sizeof(ElemType) == sizeof(Container), \ "Check ElemType definition."); \ const Container *const RhsPtr = \ reinterpret_cast(&Rhs); \ const Container *const LhsPtr = \ reinterpret_cast(&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(&Ret); \ } DefinePackedOrdUnordComparisonOperator(ord, true); DefinePackedOrdUnordComparisonOperator(unord, false); #undef DefinePackedOrdUnordComparisonOperator #define DefinePackedArithOperator(Op, RhsIndexChanges, NeedsInt) \ template \ Dqword operator Op(const Dqword &Rhs) const { \ using ElemTypeForFp = typename std::conditional< \ !(NeedsInt), Container, \ typename std::conditional< \ std::is_same::value, uint32_t, \ typename std::conditional::value, \ uint64_t, void>::type>::type>::type; \ using ElemType = \ typename std::conditional::value, \ Container, ElemTypeForFp>::type; \ static_assert(!std::is_same::value, \ "Check ElemType definition."); \ const ElemType *const RhsPtr = \ reinterpret_cast(&Rhs); \ const ElemType *const LhsPtr = \ reinterpret_cast(&Lhs); \ ElemType Ret[N]; \ for (uint32_t i = 0; i < N; ++i) { \ Ret[i] = LhsPtr[i] Op RhsPtr[(RhsIndexChanges) ? i : 0]; \ } \ return *reinterpret_cast(&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 \ Dqword operator Op(uint8_t imm) const { \ const Container *const LhsPtr = \ reinterpret_cast(&Lhs); \ Container Ret[N]; \ for (uint32_t i = 0; i < N; ++i) { \ Ret[i] = LhsPtr[i] Op imm; \ } \ return *reinterpret_cast(&Ret); \ } DefinePackedArithShiftImm(>> ); DefinePackedArithShiftImm(<< ); #undef DefinePackedArithShiftImm template typename std::enable_if::value || std::is_floating_point::value, Dqword>::type operator*(const Dqword &Rhs) const { static_assert((std::is_integral::value && sizeof(Container) < sizeof(uint64_t)) || std::is_floating_point::value, "* is only defined for i(8|16|32), and fp types."); const Container *const RhsPtr = reinterpret_cast(&Rhs); const Container *const LhsPtr = reinterpret_cast(&Lhs); Container Ret[Size]; for (uint32_t i = 0; i < Size; ++i) { Ret[i] = LhsPtr[i] * RhsPtr[i]; } return *reinterpret_cast(&Ret); } template ::value>::type> Dqword operator*(const Dqword &Rhs) const { static_assert(std::is_integral::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::type>::type; static_assert(sizeof(Container) * 2 == sizeof(NextType), "Unexpected size"); const Container *const RhsPtr = reinterpret_cast(&Rhs); const Container *const LhsPtr = reinterpret_cast(&Lhs); NextType Ret[Size / 2]; for (uint32_t i = 0; i < Size; i += 2) { Ret[i / 2] = static_cast(LhsPtr[i]) * static_cast(RhsPtr[i]); } return *reinterpret_cast(&Ret); } template PackedArith operator~() const { const Container *const LhsPtr = reinterpret_cast(&Lhs); Container Ret[Size]; for (uint32_t i = 0; i < Size; ++i) { Ret[i] = ~LhsPtr[i]; } return PackedArith(*reinterpret_cast(&Ret)); } #define MinMaxOperations(Name, Suffix) \ template \ Dqword Name##Suffix(const Dqword &Rhs) const { \ static_assert(std::is_floating_point::value, \ #Name #Suffix "ps is only available for fp."); \ const Container *const RhsPtr = \ reinterpret_cast(&Rhs); \ const Container *const LhsPtr = \ reinterpret_cast(&Lhs); \ Container Ret[Size]; \ for (uint32_t i = 0; i < Size; ++i) { \ Ret[i] = std::Name(LhsPtr[i], RhsPtr[i]); \ } \ return *reinterpret_cast(&Ret); \ } MinMaxOperations(max, ps); MinMaxOperations(max, pd); MinMaxOperations(min, ps); MinMaxOperations(min, pd); #undef MinMaxOperations template Dqword blendWith(const Dqword &Rhs, const Dqword &Mask) const { using MaskType = typename std::conditional< sizeof(Container) == 1, int8_t, typename std::conditional::type>::type; static_assert(sizeof(MaskType) == sizeof(Container), "MaskType has the wrong size."); const Container *const RhsPtr = reinterpret_cast(&Rhs); const Container *const LhsPtr = reinterpret_cast(&Lhs); const MaskType *const MaskPtr = reinterpret_cast(&Mask); Container Ret[Size]; for (int i = 0; i < Size; ++i) { Ret[i] = ((MaskPtr[i] < 0) ? RhsPtr : LhsPtr)[i]; } return *reinterpret_cast(&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 static PackedArith packedAs(const Dqword &D) { return PackedArith(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(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 T xmm0() const { return xmm(AssembledTest::Xmm0Slot); } template T xmm1() const { return xmm(AssembledTest::Xmm1Slot); } template T xmm2() const { return xmm(AssembledTest::Xmm2Slot); } template T xmm3() const { return xmm(AssembledTest::Xmm3Slot); } template T xmm4() const { return xmm(AssembledTest::Xmm4Slot); } template T xmm5() const { return xmm(AssembledTest::Xmm5Slot); } template T xmm6() const { return xmm(AssembledTest::Xmm6Slot); } template T xmm7() const { return xmm(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 ::type> T contentsOfDword(uint32_t Dword) const { return *reinterpret_cast(static_cast(ExecutableData) + dwordOffset(Dword)); } template ::type> T contentsOfQword(uint32_t InitialDword) const { return *reinterpret_cast(static_cast(ExecutableData) + dwordOffset(InitialDword)); } Dqword contentsOfDqword(uint32_t InitialDword) const { return *reinterpret_cast( static_cast(ExecutableData) + dwordOffset(InitialDword)); } template ::type> void setDwordTo(uint32_t Dword, T value) { *reinterpret_cast(static_cast(ExecutableData) + dwordOffset(Dword)) = *reinterpret_cast(&value); } template ::type> void setQwordTo(uint32_t InitialDword, T value) { *reinterpret_cast(static_cast(ExecutableData) + dwordOffset(InitialDword)) = *reinterpret_cast(&value); } void setDqwordTo(uint32_t InitialDword, const Dqword &qdword) { setQwordTo(InitialDword, qdword.U64[0]); setQwordTo(InitialDword + 2, qdword.U64[1]); } private: template typename std::enable_if::value, Dqword>::type xmm(uint8_t Slot) const { return contentsOfDqword(Slot); } template typename std::enable_if::value, T>::type xmm(uint8_t Slot) const { constexpr bool TIs64Bit = sizeof(T) == sizeof(uint64_t); using _64BitType = typename std::conditional::type; using _32BitType = typename std::conditional::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_