//===------- 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 #include #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 }; struct MutableWord { MutableWord(uint32_t Preset) : Wd(Preset) {} void patch(uint32_t Value, uint32_t Mask) { Wd = (Wd & ~Mask) | Value; } uint32_t Wd; }; namespace llvm { namespace jitlink { Expected getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &Cfg); Expected getELFRelocationType(Edge::Kind Kind); } // namespace jitlink } // namespace llvm TEST(AArch32_ELF, EdgeKinds) { // Fails: Invalid ELF type -> JITLink kind aarch32::ArmConfig Cfg; Expected ErrKind = getJITLinkEdgeKind(ELF::R_ARM_ME_TOO, Cfg); EXPECT_TRUE(errorToBool(ErrKind.takeError())); // Fails: Invalid JITLink kind -> ELF type Expected ErrType = getELFRelocationType(Edge::Invalid); EXPECT_TRUE(errorToBool(ErrType.takeError())); for (Edge::Kind K = FirstDataRelocation; K < LastThumbRelocation; K += 1) { Expected ELFType = getELFRelocationType(K); EXPECT_FALSE(errorToBool(ELFType.takeError())) << "Failed to translate JITLink kind -> ELF type"; Expected JITLinkKind = getJITLinkEdgeKind(*ELFType, Cfg); EXPECT_FALSE(errorToBool(JITLinkKind.takeError())) << "Failed to translate ELF type -> JITLink kind"; EXPECT_EQ(*JITLinkKind, K) << "Round-trip value inconsistent?"; } } TEST(AArch32_ELF, DynFixupInfos) { // We can do an opcode check for all Arm edges for (Edge::Kind K = FirstArmRelocation; K < LastArmRelocation; K += 1) { const auto *Info = FixupInfoBase::getDynFixupInfo(K); EXPECT_NE(Info, nullptr); const auto *InfoArm = static_cast(Info); EXPECT_NE(InfoArm->checkOpcode, nullptr); EXPECT_FALSE(InfoArm->checkOpcode(0x00000000)); } // We can do an opcode check for all Thumb edges for (Edge::Kind K = FirstThumbRelocation; K < LastThumbRelocation; K += 1) { const auto *Info = FixupInfoBase::getDynFixupInfo(K); EXPECT_NE(Info, nullptr); const auto *InfoThumb = static_cast(Info); EXPECT_NE(InfoThumb->checkOpcode, nullptr); EXPECT_FALSE(InfoThumb->checkOpcode(0x0000, 0x0000)); } // We cannot do it for Data and generic edges EXPECT_EQ(FixupInfoBase::getDynFixupInfo(FirstDataRelocation), nullptr); EXPECT_EQ(FixupInfoBase::getDynFixupInfo(Edge::GenericEdgeKind::Invalid), nullptr); } namespace llvm { namespace jitlink { namespace aarch32 { HalfWords encodeImmBT4BlT1BlxT2(int64_t Value); HalfWords encodeImmBT4BlT1BlxT2_J1J2(int64_t Value); uint32_t encodeImmBA1BlA1BlxA2(int64_t Value); HalfWords encodeImmMovtT1MovwT3(uint16_t Value); HalfWords encodeRegMovtT1MovwT3(int64_t Value); uint32_t encodeImmMovtA1MovwA2(uint16_t Value); uint32_t encodeRegMovtA1MovwA2(int64_t Value); int64_t decodeImmBT4BlT1BlxT2(uint32_t Hi, uint32_t Lo); int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo); int64_t decodeImmBA1BlA1BlxA2(int64_t Value); uint16_t decodeImmMovtT1MovwT3(uint32_t Hi, uint32_t Lo); int64_t decodeRegMovtT1MovwT3(uint32_t Hi, uint32_t Lo); uint16_t decodeImmMovtA1MovwA2(uint64_t Value); int64_t decodeRegMovtA1MovwA2(uint64_t Value); } // 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 static HalfWords makeHalfWords(std::array Mem) { return HalfWords{read16(Mem.data()), read16(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::ImmMask; static std::array MemPresets{ makeHalfWords({0xff, 0xf7, 0xfe, 0xef}), // common makeHalfWords({0x00, 0x00, 0x00, 0x00}), // zeros makeHalfWords({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::ImmMask; static std::array MemPresets{ makeHalfWords({0xff, 0xf7, 0xfe, 0xef}), // common makeHalfWords({0x00, 0x00, 0x00, 0x00}), // zeros makeHalfWords({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"; } } /// 26-bit branch with link TEST(AArch32_Relocations, Arm_Call_Bare) { static_assert(isInt<26>(33554430), "Max value"); static_assert(isInt<26>(-33554432), "Min value"); static_assert(!isInt<26>(33554432), "First overflow"); static_assert(!isInt<26>(-33554434), "First underflow"); constexpr uint32_t ImmMask = FixupInfo::ImmMask; static std::array MemPresets{ 0xfeeffff7, // common 0x00000000, // zeros 0xffffffff, // ones }; auto EncodeDecode = [=](int64_t In, MutableWord &Mem) { Mem.patch(encodeImmBA1BlA1BlxA2(In), ImmMask); return decodeImmBA1BlA1BlxA2(Mem.Wd); }; for (MutableWord Mem : MemPresets) { MutableWord UnaffectedBits(Mem.Wd & ~ImmMask); EXPECT_EQ(EncodeDecode(0, Mem), 0); // Zero value EXPECT_EQ(EncodeDecode(0x40, Mem), 0x40); // Common value EXPECT_EQ(EncodeDecode(33554428, Mem), 33554428); // Maximum value EXPECT_EQ(EncodeDecode(-33554432, Mem), -33554432); // Minimum value EXPECT_NE(EncodeDecode(33554434, Mem), 33554434); // First overflow EXPECT_NE(EncodeDecode(-33554434, Mem), -33554434); // First underflow EXPECT_TRUE(UnaffectedBits.Wd == (Mem.Wd & ~ImmMask)) << "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::ImmMask; constexpr HalfWords RegMask = FixupInfo::RegMask; static std::array Registers{0, 5, 12}; static std::array MemPresets{ makeHalfWords({0xff, 0xf7, 0xfe, 0xef}), // common makeHalfWords({0x00, 0x00, 0x00, 0x00}), // zeros makeHalfWords({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 unaffected 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"; } } } /// Write immediate value to the top halfword of the destination register TEST(AArch32_Relocations, Arm_MovtAbs) { static_assert(isUInt<16>(65535), "Max value"); static_assert(!isUInt<16>(65536), "First overflow"); constexpr uint32_t ImmMask = FixupInfo::ImmMask; constexpr uint32_t RegMask = FixupInfo::RegMask; static std::array Registers{0, 5, 12}; static std::array MemPresets{ 0xfeeffff7, // common 0x00000000, // zeros 0xffffffff, // ones }; auto EncodeDecode = [=](uint64_t In, MutableWord &Mem) { Mem.patch(encodeImmMovtA1MovwA2(In), ImmMask); return decodeImmMovtA1MovwA2(Mem.Wd); }; for (MutableWord Mem : MemPresets) { for (uint8_t Reg : Registers) { MutableWord UnaffectedBits(Mem.Wd & ~(ImmMask | RegMask)); Mem.patch(encodeRegMovtA1MovwA2(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 unaffected bits should be intact EXPECT_EQ(decodeRegMovtA1MovwA2(Mem.Wd), Reg); EXPECT_TRUE(UnaffectedBits.Wd == (Mem.Wd & ~(ImmMask | RegMask))) << "Diff outside immediate/register field"; } } }