| Index: unittest/AssemblerX8632/TestUtil.h
|
| diff --git a/unittest/AssemblerX8632/TestUtil.h b/unittest/AssemblerX8632/TestUtil.h
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..190a5dde05cf52871ab65601f9ecf4aa6f77dadf
|
| --- /dev/null
|
| +++ b/unittest/AssemblerX8632/TestUtil.h
|
| @@ -0,0 +1,848 @@
|
| +//===- 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 "gtest/gtest.h"
|
| +
|
| +#include <cassert>
|
| +#include <sys/mman.h>
|
| +
|
| +namespace Ice {
|
| +namespace X8632 {
|
| +namespace Test {
|
| +
|
| +class AssemblerX8632TestBase : public ::testing::Test {
|
| +protected:
|
| + using Address = AssemblerX8632::Traits::Address;
|
| + using ByteRegister = AssemblerX8632::Traits::ByteRegister;
|
| + using Cond = AssemblerX8632::Traits::Cond;
|
| + using GPRRegister = AssemblerX8632::Traits::GPRRegister;
|
| + using Traits = AssemblerX8632::Traits;
|
| + using XmmRegister = AssemblerX8632::Traits::XmmRegister;
|
| + using X87STRegister = AssemblerX8632::Traits::X87STRegister;
|
| +
|
| + AssemblerX8632TestBase() { reset(); }
|
| +
|
| + void reset() { Assembler.reset(new 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);
|
| + 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);
|
| + 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) {
|
| + munmap(ExecutableData, Size);
|
| + 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;
|
| + 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));
|
| + }
|
| +
|
| +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_
|
|
|