You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1132 lines
49 KiB
1132 lines
49 KiB
|
|
//===- subzero/unittest/unittest/AssemblerX8664/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 X8664 Assembler.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef ASSEMBLERX8664_TESTUTIL_H_
|
|
#define ASSEMBLERX8664_TESTUTIL_H_
|
|
|
|
#include "IceAssemblerX8664.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 X8664 {
|
|
namespace Test {
|
|
|
|
class AssemblerX8664TestBase : public ::testing::Test {
|
|
protected:
|
|
using Address = AssemblerX8664::Traits::Address;
|
|
using Cond = AssemblerX8664::Traits::Cond;
|
|
using GPRRegister = AssemblerX8664::Traits::GPRRegister;
|
|
using ByteRegister = AssemblerX8664::Traits::ByteRegister;
|
|
using Traits = AssemblerX8664::Traits;
|
|
using XmmRegister = AssemblerX8664::Traits::XmmRegister;
|
|
|
|
// The following are "nicknames" for all possible GPRs in x86-64. With those, we
|
|
// can use, e.g.,
|
|
//
|
|
// Encoded_GPR_al()
|
|
//
|
|
// instead of GPRRegister::Encoded_Reg_eax for 8 bit operands. They also
|
|
// introduce "regular" nicknames for legacy x86-32 register (e.g., eax becomes
|
|
// r1; esp, r0).
|
|
#define LegacyRegAliases(NewName, Name64, Name32, Name16, Name8) \
|
|
static constexpr GPRRegister Encoded_GPR_##NewName() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##NewName##q() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##NewName##d() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##NewName##w() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##NewName##l() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr ByteRegister Encoded_Bytereg_##NewName() { \
|
|
return ByteRegister::Encoded_8_Reg_##Name8; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name64() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name32() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name16() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name8() { \
|
|
return GPRRegister::Encoded_Reg_##Name32; \
|
|
}
|
|
#define NewRegAliases(Name) \
|
|
static constexpr GPRRegister Encoded_GPR_##Name() { \
|
|
return GPRRegister::Encoded_Reg_##Name##d; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name##q() { \
|
|
return GPRRegister::Encoded_Reg_##Name##d; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name##d() { \
|
|
return GPRRegister::Encoded_Reg_##Name##d; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name##w() { \
|
|
return GPRRegister::Encoded_Reg_##Name##d; \
|
|
} \
|
|
static constexpr GPRRegister Encoded_GPR_##Name##l() { \
|
|
return GPRRegister::Encoded_Reg_##Name##d; \
|
|
} \
|
|
static constexpr ByteRegister Encoded_Bytereg_##Name() { \
|
|
return ByteRegister::Encoded_8_Reg_##Name##l; \
|
|
}
|
|
#define XmmRegAliases(Name) \
|
|
static constexpr XmmRegister Encoded_Xmm_##Name() { \
|
|
return XmmRegister::Encoded_Reg_##Name; \
|
|
}
|
|
LegacyRegAliases(r0, rsp, esp, sp, spl);
|
|
LegacyRegAliases(r1, rax, eax, ax, al);
|
|
LegacyRegAliases(r2, rbx, ebx, bx, bl);
|
|
LegacyRegAliases(r3, rcx, ecx, cx, cl);
|
|
LegacyRegAliases(r4, rdx, edx, dx, dl);
|
|
LegacyRegAliases(r5, rbp, ebp, bp, bpl);
|
|
LegacyRegAliases(r6, rsi, esi, si, sil);
|
|
LegacyRegAliases(r7, rdi, edi, di, dil);
|
|
NewRegAliases(r8);
|
|
NewRegAliases(r9);
|
|
NewRegAliases(r10);
|
|
NewRegAliases(r11);
|
|
NewRegAliases(r12);
|
|
NewRegAliases(r13);
|
|
NewRegAliases(r14);
|
|
NewRegAliases(r15);
|
|
XmmRegAliases(xmm0);
|
|
XmmRegAliases(xmm1);
|
|
XmmRegAliases(xmm2);
|
|
XmmRegAliases(xmm3);
|
|
XmmRegAliases(xmm4);
|
|
XmmRegAliases(xmm5);
|
|
XmmRegAliases(xmm6);
|
|
XmmRegAliases(xmm7);
|
|
XmmRegAliases(xmm8);
|
|
XmmRegAliases(xmm9);
|
|
XmmRegAliases(xmm10);
|
|
XmmRegAliases(xmm11);
|
|
XmmRegAliases(xmm12);
|
|
XmmRegAliases(xmm13);
|
|
XmmRegAliases(xmm14);
|
|
XmmRegAliases(xmm15);
|
|
#undef XmmRegAliases
|
|
#undef NewRegAliases
|
|
#undef LegacyRegAliases
|
|
|
|
AssemblerX8664TestBase() { reset(); }
|
|
|
|
void reset() { Assembler = makeUnique<AssemblerX8664>(); }
|
|
|
|
AssemblerX8664 *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<AssemblerX8664> Assembler;
|
|
};
|
|
|
|
// __ is a helper macro. It allows test cases to emit X8664 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())->
|
|
|
|
// AssemblerX8664LowLevelTest 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 AssemblerX8664LowLevelTest : public AssemblerX8664TestBase {
|
|
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 AssemblerX8664. Each
|
|
// test will emit some instructions for performing the test. The assembled
|
|
// instructions will operate in a "safe" environment. All x86-64 registers are
|
|
// spilled to the program stack, and the registers are then zeroed out, with the
|
|
// exception of %esp and %r9.
|
|
//
|
|
// 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.
|
|
//
|
|
// %r9 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.
|
|
// In theory we could use rip-based addressing, but in practice that would
|
|
// require creating fixups, which would, in turn, require creating a global
|
|
// context. We therefore rely on the same technique used for pic code in x86-32
|
|
// (i.e., IP materialization). Upon a test start up, a call(NextInstruction) is
|
|
// executed. We then pop the return address from the stack, and use it for pic
|
|
// addressing.
|
|
//
|
|
// The jitted code will look like the following:
|
|
//
|
|
// test:
|
|
// push %r9
|
|
// call test$materialize_ip
|
|
// test$materialize_ip: <<------- %r9 will point here
|
|
// pop %r9
|
|
// push %rax
|
|
// push %rbx
|
|
// push %rcx
|
|
// push %rdx
|
|
// push %rbp
|
|
// push %rdi
|
|
// push %rsi
|
|
// push %r8
|
|
// push %r10
|
|
// push %r11
|
|
// push %r12
|
|
// push %r13
|
|
// push %r14
|
|
// push %r15
|
|
// mov $0, %rax
|
|
// mov $0, %rbx
|
|
// mov $0, %rcx
|
|
// mov $0, %rdx
|
|
// mov $0, %rbp
|
|
// mov $0, %rdi
|
|
// mov $0, %rsi
|
|
// mov $0, %r8
|
|
// mov $0, %r10
|
|
// mov $0, %r11
|
|
// mov $0, %r12
|
|
// mov $0, %r13
|
|
// mov $0, %r14
|
|
// mov $0, %r15
|
|
//
|
|
// << test code goes here >>
|
|
//
|
|
// mov %rax, { 0 + $ScratchpadOffset}(%rbp)
|
|
// mov %rbx, { 8 + $ScratchpadOffset}(%rbp)
|
|
// mov %rcx, { 16 + $ScratchpadOffset}(%rbp)
|
|
// mov %rdx, { 24 + $ScratchpadOffset}(%rbp)
|
|
// mov %rdi, { 32 + $ScratchpadOffset}(%rbp)
|
|
// mov %rsi, { 40 + $ScratchpadOffset}(%rbp)
|
|
// mov %rbp, { 48 + $ScratchpadOffset}(%rbp)
|
|
// mov %rsp, { 56 + $ScratchpadOffset}(%rbp)
|
|
// mov %r8, { 64 + $ScratchpadOffset}(%rbp)
|
|
// mov %r9, { 72 + $ScratchpadOffset}(%rbp)
|
|
// mov %r10, { 80 + $ScratchpadOffset}(%rbp)
|
|
// mov %r11, { 88 + $ScratchpadOffset}(%rbp)
|
|
// mov %r12, { 96 + $ScratchpadOffset}(%rbp)
|
|
// mov %r13, {104 + $ScratchpadOffset}(%rbp)
|
|
// mov %r14, {112 + $ScratchpadOffset}(%rbp)
|
|
// mov %r15, {120 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm0, {128 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm1, {136 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm2, {144 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm3, {152 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm4, {160 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm5, {168 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm6, {176 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm7, {184 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm8, {192 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm9, {200 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm10, {208 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm11, {216 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm12, {224 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm13, {232 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm14, {240 + $ScratchpadOffset}(%rbp)
|
|
// movups %xmm15, {248 + $ScratchpadOffset}(%rbp)
|
|
//
|
|
// pop %r15
|
|
// pop %r14
|
|
// pop %r13
|
|
// pop %r12
|
|
// pop %r11
|
|
// pop %r10
|
|
// pop %r8
|
|
// pop %rsi
|
|
// pop %rdi
|
|
// pop %rbp
|
|
// pop %rdx
|
|
// pop %rcx
|
|
// pop %rbx
|
|
// pop %rax
|
|
// pop %r9
|
|
// 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.
|
|
//
|
|
// r9 was deliberately choosen so that every instruction accessing memory would
|
|
// fail if the rex prefix was not emitted for it.
|
|
class AssemblerX8664Test : public AssemblerX8664TestBase {
|
|
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 AssemblerX8664Test class needs to be a friend so that it can create
|
|
// PackedArith objects (see below.)
|
|
friend class AssemblerX8664Test;
|
|
|
|
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);
|
|
}
|
|
|
|
AssemblerX8664Test() { reset(); }
|
|
|
|
void reset() {
|
|
AssemblerX8664TestBase::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 raxSlot() { return 0; }
|
|
static constexpr uint32_t rbxSlot() { return 2; }
|
|
static constexpr uint32_t rcxSlot() { return 4; }
|
|
static constexpr uint32_t rdxSlot() { return 6; }
|
|
static constexpr uint32_t rdiSlot() { return 8; }
|
|
static constexpr uint32_t rsiSlot() { return 10; }
|
|
static constexpr uint32_t rbpSlot() { return 12; }
|
|
static constexpr uint32_t rspSlot() { return 14; }
|
|
static constexpr uint32_t r8Slot() { return 16; }
|
|
static constexpr uint32_t r9Slot() { return 18; }
|
|
static constexpr uint32_t r10Slot() { return 20; }
|
|
static constexpr uint32_t r11Slot() { return 22; }
|
|
static constexpr uint32_t r12Slot() { return 24; }
|
|
static constexpr uint32_t r13Slot() { return 26; }
|
|
static constexpr uint32_t r14Slot() { return 28; }
|
|
static constexpr uint32_t r15Slot() { return 30; }
|
|
|
|
// save 4 dwords for each xmm registers.
|
|
static constexpr uint32_t xmm0Slot() { return 32; }
|
|
static constexpr uint32_t xmm1Slot() { return 36; }
|
|
static constexpr uint32_t xmm2Slot() { return 40; }
|
|
static constexpr uint32_t xmm3Slot() { return 44; }
|
|
static constexpr uint32_t xmm4Slot() { return 48; }
|
|
static constexpr uint32_t xmm5Slot() { return 52; }
|
|
static constexpr uint32_t xmm6Slot() { return 56; }
|
|
static constexpr uint32_t xmm7Slot() { return 60; }
|
|
static constexpr uint32_t xmm8Slot() { return 64; }
|
|
static constexpr uint32_t xmm9Slot() { return 68; }
|
|
static constexpr uint32_t xmm10Slot() { return 72; }
|
|
static constexpr uint32_t xmm11Slot() { return 76; }
|
|
static constexpr uint32_t xmm12Slot() { return 80; }
|
|
static constexpr uint32_t xmm13Slot() { return 84; }
|
|
static constexpr uint32_t xmm14Slot() { return 88; }
|
|
static constexpr uint32_t xmm15Slot() { return 92; }
|
|
|
|
static constexpr uint32_t ScratchpadSlots = 96;
|
|
|
|
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 | MAP_32BIT, -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);
|
|
#else
|
|
#error "Platform unsupported"
|
|
#endif
|
|
ExecutableData = nullptr;
|
|
}
|
|
}
|
|
|
|
void run() const { reinterpret_cast<void (*)()>(ExecutableData)(); }
|
|
|
|
#define LegacyRegAccessors(NewName, Name64, Name32, Name16, Name8) \
|
|
static_assert(Encoded_GPR_##NewName() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint64_t NewName() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##NewName##q() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint64_t NewName##q() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##NewName##d() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint32_t NewName##d() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##NewName##w() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint16_t NewName##w() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##NewName##l() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint8_t NewName##l() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##Name64() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint64_t Name64() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##Name32() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint32_t Name32() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##Name16() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint16_t Name16() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
} \
|
|
static_assert(Encoded_GPR_##Name8() == Encoded_GPR_##Name64(), \
|
|
"Invalid aliasing."); \
|
|
uint8_t Name8() const { \
|
|
return contentsOfQword(AssembledTest::Name64##Slot()); \
|
|
}
|
|
#define NewRegAccessors(NewName) \
|
|
uint64_t NewName() const { \
|
|
return contentsOfQword(AssembledTest::NewName##Slot()); \
|
|
} \
|
|
uint64_t NewName##q() const { \
|
|
return contentsOfQword(AssembledTest::NewName##Slot()); \
|
|
} \
|
|
uint32_t NewName##d() const { \
|
|
return contentsOfQword(AssembledTest::NewName##Slot()); \
|
|
} \
|
|
uint16_t NewName##w() const { \
|
|
return contentsOfQword(AssembledTest::NewName##Slot()); \
|
|
} \
|
|
uint8_t NewName##l() const { \
|
|
return contentsOfQword(AssembledTest::NewName##Slot()); \
|
|
}
|
|
#define XmmRegAccessor(Name) \
|
|
template <typename T> T Name() const { \
|
|
return xmm<T>(AssembledTest::Name##Slot()); \
|
|
}
|
|
LegacyRegAccessors(r0, rsp, esp, sp, spl);
|
|
LegacyRegAccessors(r1, rax, eax, ax, al);
|
|
LegacyRegAccessors(r2, rbx, ebx, bx, bl);
|
|
LegacyRegAccessors(r3, rcx, ecx, cx, cl);
|
|
LegacyRegAccessors(r4, rdx, edx, dx, dl);
|
|
LegacyRegAccessors(r5, rbp, ebp, bp, bpl);
|
|
LegacyRegAccessors(r6, rsi, esi, si, sil);
|
|
LegacyRegAccessors(r7, rdi, edi, di, dil);
|
|
NewRegAccessors(r8);
|
|
NewRegAccessors(r9);
|
|
NewRegAccessors(r10);
|
|
NewRegAccessors(r11);
|
|
NewRegAccessors(r12);
|
|
NewRegAccessors(r13);
|
|
NewRegAccessors(r14);
|
|
NewRegAccessors(r15);
|
|
XmmRegAccessor(xmm0);
|
|
XmmRegAccessor(xmm1);
|
|
XmmRegAccessor(xmm2);
|
|
XmmRegAccessor(xmm3);
|
|
XmmRegAccessor(xmm4);
|
|
XmmRegAccessor(xmm5);
|
|
XmmRegAccessor(xmm6);
|
|
XmmRegAccessor(xmm7);
|
|
XmmRegAccessor(xmm8);
|
|
XmmRegAccessor(xmm9);
|
|
XmmRegAccessor(xmm10);
|
|
XmmRegAccessor(xmm11);
|
|
XmmRegAccessor(xmm12);
|
|
XmmRegAccessor(xmm13);
|
|
XmmRegAccessor(xmm14);
|
|
XmmRegAccessor(xmm15);
|
|
#undef XmmRegAccessor
|
|
#undef NewRegAccessors
|
|
#undef LegacyRegAccessors
|
|
|
|
// contentsOfDword is used for reading the values in the scratchpad area.
|
|
// Valid arguments are the dword ids returned by
|
|
// AssemblerX8664Test::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(Encoded_GPR_r9(), dwordDisp(Dword), nullptr);
|
|
}
|
|
|
|
private:
|
|
// e??SlotAddress returns an AssemblerX8664::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 raxSlotAddress() { return dwordAddress(AssembledTest::raxSlot()); }
|
|
Address rbxSlotAddress() { return dwordAddress(AssembledTest::rbxSlot()); }
|
|
Address rcxSlotAddress() { return dwordAddress(AssembledTest::rcxSlot()); }
|
|
Address rdxSlotAddress() { return dwordAddress(AssembledTest::rdxSlot()); }
|
|
Address rdiSlotAddress() { return dwordAddress(AssembledTest::rdiSlot()); }
|
|
Address rsiSlotAddress() { return dwordAddress(AssembledTest::rsiSlot()); }
|
|
Address rbpSlotAddress() { return dwordAddress(AssembledTest::rbpSlot()); }
|
|
Address rspSlotAddress() { return dwordAddress(AssembledTest::rspSlot()); }
|
|
Address r8SlotAddress() { return dwordAddress(AssembledTest::r8Slot()); }
|
|
Address r9SlotAddress() { return dwordAddress(AssembledTest::r9Slot()); }
|
|
Address r10SlotAddress() { return dwordAddress(AssembledTest::r10Slot()); }
|
|
Address r11SlotAddress() { return dwordAddress(AssembledTest::r11Slot()); }
|
|
Address r12SlotAddress() { return dwordAddress(AssembledTest::r12Slot()); }
|
|
Address r13SlotAddress() { return dwordAddress(AssembledTest::r13Slot()); }
|
|
Address r14SlotAddress() { return dwordAddress(AssembledTest::r14Slot()); }
|
|
Address r15SlotAddress() { return dwordAddress(AssembledTest::r15Slot()); }
|
|
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()); }
|
|
Address xmm8SlotAddress() { return dwordAddress(AssembledTest::xmm8Slot()); }
|
|
Address xmm9SlotAddress() { return dwordAddress(AssembledTest::xmm9Slot()); }
|
|
Address xmm10SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm10Slot());
|
|
}
|
|
Address xmm11SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm11Slot());
|
|
}
|
|
Address xmm12SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm12Slot());
|
|
}
|
|
Address xmm13SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm13Slot());
|
|
}
|
|
Address xmm14SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm14Slot());
|
|
}
|
|
Address xmm15SlotAddress() {
|
|
return dwordAddress(AssembledTest::xmm15Slot());
|
|
}
|
|
|
|
// 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 PushR9Bytes = 2;
|
|
static constexpr uint8_t CallImmBytes = 5;
|
|
return AssembledTest::MaximumCodeSize + (Dword * 4) -
|
|
(PushR9Bytes + CallImmBytes);
|
|
}
|
|
|
|
void addPrologue() {
|
|
__ pushl(Encoded_GPR_r9());
|
|
__ call(Immediate(4));
|
|
__ popl(Encoded_GPR_r9());
|
|
|
|
__ pushl(Encoded_GPR_rax());
|
|
__ pushl(Encoded_GPR_rbx());
|
|
__ pushl(Encoded_GPR_rcx());
|
|
__ pushl(Encoded_GPR_rdx());
|
|
__ pushl(Encoded_GPR_rbp());
|
|
__ pushl(Encoded_GPR_rdi());
|
|
__ pushl(Encoded_GPR_rsi());
|
|
__ pushl(Encoded_GPR_r8());
|
|
__ pushl(Encoded_GPR_r10());
|
|
__ pushl(Encoded_GPR_r11());
|
|
__ pushl(Encoded_GPR_r12());
|
|
__ pushl(Encoded_GPR_r13());
|
|
__ pushl(Encoded_GPR_r14());
|
|
__ pushl(Encoded_GPR_r15());
|
|
|
|
__ mov(IceType_i32, Encoded_GPR_rax(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rbx(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rcx(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rdx(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rbp(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rdi(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_rsi(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r8(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r10(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r11(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r12(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r13(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r14(), Immediate(0x00));
|
|
__ mov(IceType_i32, Encoded_GPR_r15(), Immediate(0x00));
|
|
}
|
|
|
|
void addEpilogue() {
|
|
__ mov(IceType_i64, raxSlotAddress(), Encoded_GPR_rax());
|
|
__ mov(IceType_i64, rbxSlotAddress(), Encoded_GPR_rbx());
|
|
__ mov(IceType_i64, rcxSlotAddress(), Encoded_GPR_rcx());
|
|
__ mov(IceType_i64, rdxSlotAddress(), Encoded_GPR_rdx());
|
|
__ mov(IceType_i64, rdiSlotAddress(), Encoded_GPR_rdi());
|
|
__ mov(IceType_i64, rsiSlotAddress(), Encoded_GPR_rsi());
|
|
__ mov(IceType_i64, rbpSlotAddress(), Encoded_GPR_rbp());
|
|
__ mov(IceType_i64, rspSlotAddress(), Encoded_GPR_rsp());
|
|
__ mov(IceType_i64, r8SlotAddress(), Encoded_GPR_r8());
|
|
__ mov(IceType_i64, r9SlotAddress(), Encoded_GPR_r9());
|
|
__ mov(IceType_i64, r10SlotAddress(), Encoded_GPR_r10());
|
|
__ mov(IceType_i64, r11SlotAddress(), Encoded_GPR_r11());
|
|
__ mov(IceType_i64, r12SlotAddress(), Encoded_GPR_r12());
|
|
__ mov(IceType_i64, r13SlotAddress(), Encoded_GPR_r13());
|
|
__ mov(IceType_i64, r14SlotAddress(), Encoded_GPR_r14());
|
|
__ mov(IceType_i64, r15SlotAddress(), Encoded_GPR_r15());
|
|
__ movups(xmm0SlotAddress(), Encoded_Xmm_xmm0());
|
|
__ movups(xmm1SlotAddress(), Encoded_Xmm_xmm1());
|
|
__ movups(xmm2SlotAddress(), Encoded_Xmm_xmm2());
|
|
__ movups(xmm3SlotAddress(), Encoded_Xmm_xmm3());
|
|
__ movups(xmm4SlotAddress(), Encoded_Xmm_xmm4());
|
|
__ movups(xmm5SlotAddress(), Encoded_Xmm_xmm5());
|
|
__ movups(xmm6SlotAddress(), Encoded_Xmm_xmm6());
|
|
__ movups(xmm7SlotAddress(), Encoded_Xmm_xmm7());
|
|
__ movups(xmm8SlotAddress(), Encoded_Xmm_xmm8());
|
|
__ movups(xmm9SlotAddress(), Encoded_Xmm_xmm9());
|
|
__ movups(xmm10SlotAddress(), Encoded_Xmm_xmm10());
|
|
__ movups(xmm11SlotAddress(), Encoded_Xmm_xmm11());
|
|
__ movups(xmm12SlotAddress(), Encoded_Xmm_xmm12());
|
|
__ movups(xmm13SlotAddress(), Encoded_Xmm_xmm13());
|
|
__ movups(xmm14SlotAddress(), Encoded_Xmm_xmm14());
|
|
__ movups(xmm15SlotAddress(), Encoded_Xmm_xmm15());
|
|
|
|
__ popl(Encoded_GPR_r15());
|
|
__ popl(Encoded_GPR_r14());
|
|
__ popl(Encoded_GPR_r13());
|
|
__ popl(Encoded_GPR_r12());
|
|
__ popl(Encoded_GPR_r11());
|
|
__ popl(Encoded_GPR_r10());
|
|
__ popl(Encoded_GPR_r8());
|
|
__ popl(Encoded_GPR_rsi());
|
|
__ popl(Encoded_GPR_rdi());
|
|
__ popl(Encoded_GPR_rbp());
|
|
__ popl(Encoded_GPR_rdx());
|
|
__ popl(Encoded_GPR_rcx());
|
|
__ popl(Encoded_GPR_rbx());
|
|
__ popl(Encoded_GPR_rax());
|
|
__ popl(Encoded_GPR_r9());
|
|
|
|
__ ret();
|
|
}
|
|
|
|
bool NeedsEpilogue;
|
|
uint32_t NumAllocatedDwords;
|
|
};
|
|
|
|
} // end of namespace Test
|
|
} // end of namespace X8664
|
|
} // end of namespace Ice
|
|
|
|
#endif // ASSEMBLERX8664_TESTUTIL_H_
|