This first version lays the foundations for AArch32 support in JITLink. ELFLinkGraphBuilder_aarch32 processes REL-type relocations and populates LinkGraphs from ELF object files for both big- and little-endian systems. The ArmCfg member controls subarchitecture-specific details throughout the linking process (i.e. it's passed to ELFJITLinker_aarch32). Relocation types follow the ABI documentation's division into classes: Data (endian-sensitive), Arm (32-bit little-endian) and Thumb (2x 16-bit little-endian, "Thumb32" in the docs). The implementation of instruction encoding/decoding for relocation resolution is implemented symmetrically and is testable in isolation (see AArch32 category in JITLinkTests). Callable Thumb functions are marked with a ThumbSymbol target-flag and stored in the LinkGraph with their real addresses. The thumb-bit is added back in when the owning JITDylib requests the address for such a symbol. The StubsManager can generate (absolute) Thumb-state stubs for branch range extensions on v7+ targets. Proper GOT/PLT handling is not yet implemented. This patch is based on the backend implementation in ez-clang and has just enough functionality to model the infrastructure and link a Thumb function `main()` that calls `printf()` to dump "Hello Arm!" on Armv7a. It was tested on Raspberry Pi with 32-bit Raspbian OS. Reviewed By: lhames Differential Revision: https://reviews.llvm.org/D144083
201 lines
7.7 KiB
C++
201 lines
7.7 KiB
C++
//===------- AArch32Tests.cpp - Unit tests for the AArch32 backend --------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include <llvm/BinaryFormat/ELF.h>
|
|
#include <llvm/ExecutionEngine/JITLink/aarch32.h>
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::jitlink;
|
|
using namespace llvm::jitlink::aarch32;
|
|
using namespace llvm::support;
|
|
using namespace llvm::support::endian;
|
|
|
|
struct MutableHalfWords {
|
|
MutableHalfWords(HalfWords Preset) : Hi(Preset.Hi), Lo(Preset.Lo) {}
|
|
|
|
void patch(HalfWords Value, HalfWords Mask) {
|
|
Hi = (Hi & ~Mask.Hi) | Value.Hi;
|
|
Lo = (Lo & ~Mask.Lo) | Value.Lo;
|
|
}
|
|
|
|
uint16_t Hi; // First halfword
|
|
uint16_t Lo; // Second halfword
|
|
};
|
|
|
|
namespace llvm {
|
|
namespace jitlink {
|
|
|
|
Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType);
|
|
Expected<uint32_t> getELFRelocationType(Edge::Kind Kind);
|
|
|
|
} // namespace jitlink
|
|
} // namespace llvm
|
|
|
|
TEST(AArch32_ELF, EdgeKinds) {
|
|
// Fails: Invalid ELF type -> JITLink kind
|
|
Expected<uint32_t> ErrKind = getJITLinkEdgeKind(ELF::R_ARM_NONE);
|
|
EXPECT_TRUE(errorToBool(ErrKind.takeError()));
|
|
|
|
// Fails: Invalid JITLink kind -> ELF type
|
|
Expected<uint32_t> ErrType = getELFRelocationType(Edge::Invalid);
|
|
EXPECT_TRUE(errorToBool(ErrType.takeError()));
|
|
|
|
for (Edge::Kind K = FirstDataRelocation; K < LastThumbRelocation; K += 1) {
|
|
Expected<uint32_t> ELFType = getELFRelocationType(K);
|
|
EXPECT_FALSE(errorToBool(ELFType.takeError()))
|
|
<< "Failed to translate JITLink kind -> ELF type";
|
|
|
|
Expected<Edge::Kind> JITLinkKind = getJITLinkEdgeKind(*ELFType);
|
|
EXPECT_FALSE(errorToBool(JITLinkKind.takeError()))
|
|
<< "Failed to translate ELF type -> JITLink kind";
|
|
|
|
EXPECT_EQ(*JITLinkKind, K) << "Round-trip value inconsistent?";
|
|
}
|
|
}
|
|
|
|
namespace llvm {
|
|
namespace jitlink {
|
|
namespace aarch32 {
|
|
|
|
HalfWords encodeImmBT4BlT1BlxT2(int64_t Value);
|
|
HalfWords encodeImmBT4BlT1BlxT2_J1J2(int64_t Value);
|
|
HalfWords encodeImmMovtT1MovwT3(uint16_t Value);
|
|
HalfWords encodeRegMovtT1MovwT3(int64_t Value);
|
|
|
|
int64_t decodeImmBT4BlT1BlxT2(uint32_t Hi, uint32_t Lo);
|
|
int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo);
|
|
uint16_t decodeImmMovtT1MovwT3(uint32_t Hi, uint32_t Lo);
|
|
int64_t decodeRegMovtT1MovwT3(uint32_t Hi, uint32_t Lo);
|
|
|
|
} // namespace aarch32
|
|
} // namespace jitlink
|
|
} // namespace llvm
|
|
|
|
// Big-endian for v7 and v8 (and v6 unless in legacy backwards compatible mode
|
|
// be32) have little-endian instructions and big-endian data. In ELF relocatable
|
|
// objects big-endian instructions may still be encountered. A be8 supporting
|
|
// linker is expected to endian-reverse instructions for the executable.
|
|
template <endianness Endian>
|
|
static HalfWords makeHalfWords(std::array<uint8_t, 4> Mem) {
|
|
return HalfWords{read16<Endian>(Mem.data()), read16<Endian>(Mem.data() + 2)};
|
|
}
|
|
|
|
/// 25-bit branch with link (with J1J2 range extension)
|
|
TEST(AArch32_Relocations, Thumb_Call_J1J2) {
|
|
static_assert(isInt<25>(16777215), "Max value");
|
|
static_assert(isInt<25>(-16777215), "Min value");
|
|
static_assert(!isInt<25>(16777217), "First overflow");
|
|
static_assert(!isInt<25>(-16777217), "First underflow");
|
|
|
|
constexpr HalfWords ImmMask = FixupInfo<Thumb_Call>::ImmMask;
|
|
|
|
static std::array<HalfWords, 3> MemPresets{
|
|
makeHalfWords<little>({0xff, 0xf7, 0xfe, 0xef}), // common
|
|
makeHalfWords<little>({0x00, 0x00, 0x00, 0x00}), // zeros
|
|
makeHalfWords<little>({0xff, 0xff, 0xff, 0xff}), // ones
|
|
};
|
|
|
|
auto EncodeDecode = [ImmMask](int64_t In, MutableHalfWords &Mem) {
|
|
Mem.patch(encodeImmBT4BlT1BlxT2_J1J2(In), ImmMask);
|
|
return decodeImmBT4BlT1BlxT2_J1J2(Mem.Hi, Mem.Lo);
|
|
};
|
|
|
|
for (MutableHalfWords Mem : MemPresets) {
|
|
HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo);
|
|
|
|
EXPECT_EQ(EncodeDecode(1, Mem), 0); // Zero value
|
|
EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40); // Common value
|
|
EXPECT_EQ(EncodeDecode(16777215, Mem), 16777214); // Maximum value
|
|
EXPECT_EQ(EncodeDecode(-16777215, Mem), -16777216); // Minimum value
|
|
EXPECT_NE(EncodeDecode(16777217, Mem), 16777217); // First overflow
|
|
EXPECT_NE(EncodeDecode(-16777217, Mem), -16777217); // First underflow
|
|
|
|
EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) &&
|
|
UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo))
|
|
<< "Diff outside immediate field";
|
|
}
|
|
}
|
|
|
|
/// 22-bit branch with link (without J1J2 range extension)
|
|
TEST(AArch32_Relocations, Thumb_Call_Bare) {
|
|
static_assert(isInt<22>(2097151), "Max value");
|
|
static_assert(isInt<22>(-2097151), "Min value");
|
|
static_assert(!isInt<22>(2097153), "First overflow");
|
|
static_assert(!isInt<22>(-2097153), "First underflow");
|
|
|
|
constexpr HalfWords ImmMask = FixupInfo<Thumb_Call>::ImmMask;
|
|
|
|
static std::array<HalfWords, 3> MemPresets{
|
|
makeHalfWords<little>({0xff, 0xf7, 0xfe, 0xef}), // common
|
|
makeHalfWords<little>({0x00, 0x00, 0x00, 0x00}), // zeros
|
|
makeHalfWords<little>({0xff, 0xff, 0xff, 0xff}), // ones
|
|
};
|
|
|
|
auto EncodeDecode = [ImmMask](int64_t In, MutableHalfWords &Mem) {
|
|
Mem.patch(encodeImmBT4BlT1BlxT2_J1J2(In), ImmMask);
|
|
return decodeImmBT4BlT1BlxT2_J1J2(Mem.Hi, Mem.Lo);
|
|
};
|
|
|
|
for (MutableHalfWords Mem : MemPresets) {
|
|
HalfWords UnaffectedBits(Mem.Hi & ~ImmMask.Hi, Mem.Lo & ~ImmMask.Lo);
|
|
|
|
EXPECT_EQ(EncodeDecode(1, Mem), 0); // Zero value
|
|
EXPECT_EQ(EncodeDecode(0x41, Mem), 0x40); // Common value
|
|
EXPECT_EQ(EncodeDecode(2097151, Mem), 2097150); // Maximum value
|
|
EXPECT_EQ(EncodeDecode(-2097151, Mem), -2097152); // Minimum value
|
|
EXPECT_NE(EncodeDecode(2097153, Mem), 2097153); // First overflow
|
|
EXPECT_NE(EncodeDecode(-2097153, Mem), -2097153); // First underflow
|
|
|
|
EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~ImmMask.Hi) &&
|
|
UnaffectedBits.Lo == (Mem.Lo & ~ImmMask.Lo))
|
|
<< "Diff outside immediate field";
|
|
}
|
|
}
|
|
|
|
/// Write immediate value to the top halfword of the destination register
|
|
TEST(AArch32_Relocations, Thumb_MovtAbs) {
|
|
static_assert(isUInt<16>(65535), "Max value");
|
|
static_assert(!isUInt<16>(65536), "First overflow");
|
|
|
|
constexpr HalfWords ImmMask = FixupInfo<Thumb_MovtAbs>::ImmMask;
|
|
constexpr HalfWords RegMask = FixupInfo<Thumb_MovtAbs>::RegMask;
|
|
|
|
static std::array<uint8_t, 3> Registers{0, 5, 12};
|
|
static std::array<HalfWords, 3> MemPresets{
|
|
makeHalfWords<little>({0xff, 0xf7, 0xfe, 0xef}), // common
|
|
makeHalfWords<little>({0x00, 0x00, 0x00, 0x00}), // zeros
|
|
makeHalfWords<little>({0xff, 0xff, 0xff, 0xff}), // ones
|
|
};
|
|
|
|
auto EncodeDecode = [ImmMask](uint32_t In, MutableHalfWords &Mem) {
|
|
Mem.patch(encodeImmMovtT1MovwT3(In), ImmMask);
|
|
return decodeImmMovtT1MovwT3(Mem.Hi, Mem.Lo);
|
|
};
|
|
|
|
for (MutableHalfWords Mem : MemPresets) {
|
|
for (uint8_t Reg : Registers) {
|
|
HalfWords UnaffectedBits(Mem.Hi & ~(ImmMask.Hi | RegMask.Hi),
|
|
Mem.Lo & ~(ImmMask.Lo | RegMask.Lo));
|
|
|
|
Mem.patch(encodeRegMovtT1MovwT3(Reg), RegMask);
|
|
EXPECT_EQ(EncodeDecode(0x76bb, Mem), 0x76bb); // Common value
|
|
EXPECT_EQ(EncodeDecode(0, Mem), 0); // Minimum value
|
|
EXPECT_EQ(EncodeDecode(0xffff, Mem), 0xffff); // Maximum value
|
|
EXPECT_NE(EncodeDecode(0x10000, Mem), 0x10000); // First overflow
|
|
|
|
// Destination register as well as unaffacted bits should be intact
|
|
EXPECT_EQ(decodeRegMovtT1MovwT3(Mem.Hi, Mem.Lo), Reg);
|
|
EXPECT_TRUE(UnaffectedBits.Hi == (Mem.Hi & ~(ImmMask.Hi | RegMask.Hi)) &&
|
|
UnaffectedBits.Lo == (Mem.Lo & ~(ImmMask.Lo | RegMask.Lo)))
|
|
<< "Diff outside immediate/register field";
|
|
}
|
|
}
|
|
}
|