diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h --- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h @@ -48,7 +48,8 @@ /// FirstArmRelocation, - /// TODO: Arm_Call is here only as a placeholder for now. + /// Write immediate value for PC-relative branch with link (can bridge between + /// Arm and Thumb). Arm_Call = FirstArmRelocation, LastArmRelocation = Arm_Call, @@ -148,6 +149,16 @@ /// template struct FixupInfo {}; +template <> struct FixupInfo { + static constexpr uint32_t Opcode = 0x0a000000; + static constexpr uint32_t OpcodeMask = 0x0e000000; + static constexpr uint32_t ImmMask = 0x00ffffff; + static constexpr uint32_t Unconditional = 0xe0000000; + static constexpr uint32_t CondMask = 0xe0000000; // excluding BLX bit + static constexpr uint32_t BitH = 0x01000000; + static constexpr uint32_t BitBlx = 0x10000000; +}; + template <> struct FixupInfo { static constexpr HalfWords Opcode{0xf000, 0x8000}; static constexpr HalfWords OpcodeMask{0xf800, 0x8000}; diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp --- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp @@ -83,6 +83,24 @@ return SignExtend64<25>(S << 14 | I1 | I2 | Imm10 << 12 | Imm11 << 1); } +/// Encode 26-bit immediate value for branch instructions +/// (formats B A1, BL A1 and BLX A2). +/// +/// 00000:Imm24:00 -> 00000:Imm24 +/// +uint32_t encodeImmBA1BlA1BlxA2(int64_t Value) { + return (Value >> 2) & 0x00ffffff; +} + +/// Decode 26-bit immediate value for branch instructions +/// (formats B A1, BL A1 and BLX A2). +/// +/// 00000:Imm24 -> 00000:Imm24:00 +/// +int64_t decodeImmBA1BlA1BlxA2(int64_t Value) { + return SignExtend64<26>((Value & 0x00ffffff) << 2); +} + /// Encode 16-bit immediate value for move instruction formats MOVT T1 and /// MOVW T3. /// @@ -156,6 +174,23 @@ const support::ulittle16_t &Lo; // Second halfword }; +struct WritableArmRelocation { + WritableArmRelocation(char *FixupPtr) + : Wd{*reinterpret_cast(FixupPtr)} {} + + support::ulittle32_t &Wd; +}; + +struct ArmRelocation { + + ArmRelocation(const char *FixupPtr) + : Wd{*reinterpret_cast(FixupPtr)} {} + + ArmRelocation(WritableArmRelocation &Writable) : Wd{Writable.Wd} {} + + const support::ulittle32_t &Wd; +}; + Error makeUnexpectedOpcodeError(const LinkGraph &G, const ThumbRelocation &R, Edge::Kind Kind) { return make_error( @@ -164,12 +199,24 @@ G.getEdgeKindName(Kind))); } +Error makeUnexpectedOpcodeError(const LinkGraph &G, const ArmRelocation &R, + Edge::Kind Kind) { + return make_error( + formatv("Invalid opcode [ 0x{0:x8} ] for relocation: {1}", + static_cast(R.Wd), G.getEdgeKindName(Kind))); +} + template bool checkOpcode(const ThumbRelocation &R) { uint16_t Hi = R.Hi & FixupInfo::OpcodeMask.Hi; uint16_t Lo = R.Lo & FixupInfo::OpcodeMask.Lo; return Hi == FixupInfo::Opcode.Hi && Lo == FixupInfo::Opcode.Lo; } +template bool checkOpcode(const ArmRelocation &R) { + uint32_t Wd = R.Wd & FixupInfo::OpcodeMask; + return Wd == FixupInfo::Opcode; +} + template bool checkRegister(const ThumbRelocation &R, HalfWords Reg) { uint16_t Hi = R.Hi & FixupInfo::RegMask.Hi; @@ -177,6 +224,12 @@ return Hi == Reg.Hi && Lo == Reg.Lo; } +template +bool checkRegister(const ArmRelocation &R, uint32_t Reg) { + uint32_t Wd = R.Wd & FixupInfo::RegMask; + return Wd == Reg; +} + template void writeRegister(WritableThumbRelocation &R, HalfWords Reg) { static constexpr HalfWords Mask = FixupInfo::RegMask; @@ -186,6 +239,13 @@ R.Lo = (R.Lo & ~Mask.Lo) | Reg.Lo; } +template +void writeRegister(WritableArmRelocation &R, uint32_t Reg) { + static constexpr uint32_t Mask = FixupInfo::RegMask; + assert((Mask & Reg) == Reg && "Value bits exceed bit range of given mask"); + R.Wd = (R.Wd & ~Mask) | Reg; +} + template void writeImmediate(WritableThumbRelocation &R, HalfWords Imm) { static constexpr HalfWords Mask = FixupInfo::ImmMask; @@ -195,6 +255,13 @@ R.Lo = (R.Lo & ~Mask.Lo) | Imm.Lo; } +template +void writeImmediate(WritableArmRelocation &R, uint32_t Imm) { + static constexpr uint32_t Mask = FixupInfo::ImmMask; + assert((Mask & Imm) == Imm && "Value bits exceed bit range of given mask"); + R.Wd = (R.Wd & ~Mask) | Imm; +} + Expected readAddendData(LinkGraph &G, Block &B, const Edge &E) { support::endianness Endian = G.getEndianness(); assert(Endian != support::native && "Declare as little or big explicitly"); @@ -216,13 +283,15 @@ } Expected readAddendArm(LinkGraph &G, Block &B, const Edge &E) { + ArmRelocation R(B.getContent().data() + E.getOffset()); Edge::Kind Kind = E.getKind(); switch (Kind) { case Arm_Call: - return make_error( - "Addend extraction for relocation type not yet implemented: " + - StringRef(G.getEdgeKindName(Kind))); + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + return decodeImmBA1BlA1BlxA2(R.Wd); + default: return make_error( "In graph " + G.getName() + ", section " + B.getSection().getName() + @@ -292,7 +361,6 @@ int64_t Addend = E.getAddend(); Symbol &TargetSymbol = E.getTarget(); uint64_t TargetAddress = TargetSymbol.getAddress().getValue(); - assert(!hasTargetFlags(TargetSymbol, ThumbSymbol)); // Regular data relocations have size 4, alignment 1 and write the full 32-bit // result to the place; no need for overflow checking. There are three @@ -321,13 +389,52 @@ } Error applyFixupArm(LinkGraph &G, Block &B, const Edge &E) { + WritableArmRelocation R(B.getAlreadyMutableContent().data() + E.getOffset()); Edge::Kind Kind = E.getKind(); + uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue(); + int64_t Addend = E.getAddend(); + Symbol &TargetSymbol = E.getTarget(); + uint64_t TargetAddress = TargetSymbol.getAddress().getValue(); + if (hasTargetFlags(TargetSymbol, ThumbSymbol)) + TargetAddress |= 0x01; switch (Kind) { - case Arm_Call: - return make_error( - "Fix-up for relocation type not yet implemented: " + - StringRef(G.getEdgeKindName(Kind))); + case Arm_Call: { + if (!checkOpcode(R)) + return makeUnexpectedOpcodeError(G, R, Kind); + + if ((R.Wd & FixupInfo::CondMask) != + FixupInfo::Unconditional) + return make_error("Relocation expects an unconditional " + "BL/BLX branch instruction: " + + StringRef(G.getEdgeKindName(Kind))); + + int64_t Value = TargetAddress - FixupAddress + Addend; + + // The call instruction itself is Arm. The call destination can either be + // Thumb or Arm. We use BL to stay in Arm and BLX to change to Thumb. + bool TargetIsThumb = hasTargetFlags(TargetSymbol, ThumbSymbol); + bool InstrIsBlx = (~R.Wd & FixupInfo::BitBlx) == 0; + if (TargetIsThumb != InstrIsBlx) { + if (LLVM_LIKELY(TargetIsThumb)) { + // Change opcode BL -> BLX and fix range value + R.Wd = R.Wd | FixupInfo::BitBlx; + R.Wd = R.Wd & ~FixupInfo::BitH; + // Set Thumb bit + Value |= 0x01; + } else { + // Change opcode BLX -> BL + R.Wd = R.Wd & ~FixupInfo::BitBlx; + } + } + + if (!isInt<26>(Value)) + return makeTargetOutOfRangeError(G, B, E); + writeImmediate(R, encodeImmBA1BlA1BlxA2(Value)); + + return Error::success(); + } + default: return make_error( "In graph " + G.getName() + ", section " + B.getSection().getName() + diff --git a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_static_arm_reloc.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_static_arm_reloc.s new file mode 100644 --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_static_arm_reloc.s @@ -0,0 +1,37 @@ +# RUN: llvm-mc -triple=armv7-linux-gnueabi -arm-add-build-attributes -filetype=obj -o %t.o %s +# RUN: llvm-objdump -r %t.o | FileCheck --check-prefix=CHECK-TYPE %s +# RUN: llvm-objdump --disassemble %t.o | FileCheck --check-prefix=CHECK-INSTR %s +# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 -slab-allocate 10Kb \ +# RUN: -slab-page-size 4096 -show-entry-es -check %s %t.o + + + .text + .syntax unified + +# CHECK-TYPE: {{[0-9a-f]+}} R_ARM_CALL call_target +# CHECK-INSTR: 00000000 : +# CHECK-INSTR: 0: ebfffffe bl 0x0 +# CHECK-INSTR: 00000004 : +# CHECK-INSTR: 4: e12fff1e bx lr +# jitlink-check: decode_operand(call_site, 0) = call_target - next_pc(call_site) - 4 + .globl call_site + .type call_site,%function + .p2align 2 +call_site: + bl call_target + .size call_site, .-call_site + + .globl call_target + .type call_target,%function + .p2align 2 +call_target: + bx lr + .size call_target, .-call_target + +# Empty main function for jitlink to be happy + .globl main + .type main,%function + .p2align 2 +main: + bx lr + .size main, .-main diff --git a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp --- a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp +++ b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp @@ -29,6 +29,13 @@ 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 { @@ -66,11 +73,13 @@ 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); 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); @@ -159,6 +168,41 @@ } } +/// 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");