//===------- 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 }; namespace llvm { namespace jitlink { Expected getJITLinkEdgeKind(uint32_t ELFType); Expected getELFRelocationType(Edge::Kind Kind); } // namespace jitlink } // namespace llvm TEST(AArch32_ELF, EdgeKinds) { // Fails: Invalid ELF type -> JITLink kind Expected ErrKind = getJITLinkEdgeKind(ELF::R_ARM_NONE); 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); 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 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"; } } /// 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 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"; } } }