[llvm] 142c89c - [JITLink][AArch32] Implement ELF::R_ARM_CALL relocation

Stefan Gränitz via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 13 08:17:28 PDT 2023


Author: Eymen Ünay
Date: 2023-09-13T17:16:29+02:00
New Revision: 142c89c381bccba1c688a90d7e9df91eaf78d4e3

URL: https://github.com/llvm/llvm-project/commit/142c89c381bccba1c688a90d7e9df91eaf78d4e3
DIFF: https://github.com/llvm/llvm-project/commit/142c89c381bccba1c688a90d7e9df91eaf78d4e3.diff

LOG: [JITLink][AArch32] Implement ELF::R_ARM_CALL relocation

- Added WritableArmRelocation and ArmRelocation Structs
- Encode/Decode funcs for B/BL A1 and BLX A2 encodings
- Add ARM helper functions, consistent with the existing Thumb helper functions
- Add Test for ELF::R_ARM_CALL

Reviewed By: sgraenitz

Differential Revision: https://reviews.llvm.org/D157533

Added: 
    llvm/test/ExecutionEngine/JITLink/AArch32/ELF_static_arm_reloc.s

Modified: 
    llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
    llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
    llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index d1d54c0ca633362..d33b422745c2d9d 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -48,7 +48,8 @@ enum EdgeKind_aarch32 : Edge::Kind {
   ///
   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 @@ struct HalfWords {
 ///
 template <EdgeKind_aarch32 Kind> struct FixupInfo {};
 
+template <> struct FixupInfo<Arm_Call> {
+  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<Thumb_Jump24> {
   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
index 01c9e8b4fd9d18e..ac91d0bd6e6724f 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -83,6 +83,24 @@ int64_t decodeImmBT4BlT1BlxT2_J1J2(uint32_t Hi, uint32_t Lo) {
   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).
+///
+///   Imm24:00 ->  00000000: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).
+///
+///   00000000:Imm24 ->  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 @@ struct ThumbRelocation {
   const support::ulittle16_t &Lo; // Second halfword
 };
 
+struct WritableArmRelocation {
+  WritableArmRelocation(char *FixupPtr)
+      : Wd{*reinterpret_cast<support::ulittle32_t *>(FixupPtr)} {}
+
+  support::ulittle32_t &Wd;
+};
+
+struct ArmRelocation {
+
+  ArmRelocation(const char *FixupPtr)
+      : Wd{*reinterpret_cast<const support::ulittle32_t *>(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<JITLinkError>(
@@ -164,12 +199,24 @@ Error makeUnexpectedOpcodeError(const LinkGraph &G, const ThumbRelocation &R,
               G.getEdgeKindName(Kind)));
 }
 
+Error makeUnexpectedOpcodeError(const LinkGraph &G, const ArmRelocation &R,
+                                Edge::Kind Kind) {
+  return make_error<JITLinkError>(
+      formatv("Invalid opcode [ 0x{0:x8} ] for relocation: {1}",
+              static_cast<uint32_t>(R.Wd), G.getEdgeKindName(Kind)));
+}
+
 template <EdgeKind_aarch32 Kind> bool checkOpcode(const ThumbRelocation &R) {
   uint16_t Hi = R.Hi & FixupInfo<Kind>::OpcodeMask.Hi;
   uint16_t Lo = R.Lo & FixupInfo<Kind>::OpcodeMask.Lo;
   return Hi == FixupInfo<Kind>::Opcode.Hi && Lo == FixupInfo<Kind>::Opcode.Lo;
 }
 
+template <EdgeKind_aarch32 Kind> bool checkOpcode(const ArmRelocation &R) {
+  uint32_t Wd = R.Wd & FixupInfo<Kind>::OpcodeMask;
+  return Wd == FixupInfo<Kind>::Opcode;
+}
+
 template <EdgeKind_aarch32 Kind>
 bool checkRegister(const ThumbRelocation &R, HalfWords Reg) {
   uint16_t Hi = R.Hi & FixupInfo<Kind>::RegMask.Hi;
@@ -177,6 +224,12 @@ bool checkRegister(const ThumbRelocation &R, HalfWords Reg) {
   return Hi == Reg.Hi && Lo == Reg.Lo;
 }
 
+template <EdgeKind_aarch32 Kind>
+bool checkRegister(const ArmRelocation &R, uint32_t Reg) {
+  uint32_t Wd = R.Wd & FixupInfo<Kind>::RegMask;
+  return Wd == Reg;
+}
+
 template <EdgeKind_aarch32 Kind>
 void writeRegister(WritableThumbRelocation &R, HalfWords Reg) {
   static constexpr HalfWords Mask = FixupInfo<Kind>::RegMask;
@@ -186,6 +239,13 @@ void writeRegister(WritableThumbRelocation &R, HalfWords Reg) {
   R.Lo = (R.Lo & ~Mask.Lo) | Reg.Lo;
 }
 
+template <EdgeKind_aarch32 Kind>
+void writeRegister(WritableArmRelocation &R, uint32_t Reg) {
+  static constexpr uint32_t Mask = FixupInfo<Kind>::RegMask;
+  assert((Mask & Reg) == Reg && "Value bits exceed bit range of given mask");
+  R.Wd = (R.Wd & ~Mask) | Reg;
+}
+
 template <EdgeKind_aarch32 Kind>
 void writeImmediate(WritableThumbRelocation &R, HalfWords Imm) {
   static constexpr HalfWords Mask = FixupInfo<Kind>::ImmMask;
@@ -195,6 +255,13 @@ void writeImmediate(WritableThumbRelocation &R, HalfWords Imm) {
   R.Lo = (R.Lo & ~Mask.Lo) | Imm.Lo;
 }
 
+template <EdgeKind_aarch32 Kind>
+void writeImmediate(WritableArmRelocation &R, uint32_t Imm) {
+  static constexpr uint32_t Mask = FixupInfo<Kind>::ImmMask;
+  assert((Mask & Imm) == Imm && "Value bits exceed bit range of given mask");
+  R.Wd = (R.Wd & ~Mask) | Imm;
+}
+
 Expected<int64_t> 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<int64_t> readAddendData(LinkGraph &G, Block &B, const Edge &E) {
 }
 
 Expected<int64_t> 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<JITLinkError>(
-        "Addend extraction for relocation type not yet implemented: " +
-        StringRef(G.getEdgeKindName(Kind)));
+    if (!checkOpcode<Arm_Call>(R))
+      return makeUnexpectedOpcodeError(G, R, Kind);
+    return decodeImmBA1BlA1BlxA2(R.Wd);
+
   default:
     return make_error<JITLinkError>(
         "In graph " + G.getName() + ", section " + B.getSection().getName() +
@@ -292,7 +361,6 @@ Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
   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 applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
 }
 
 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<JITLinkError>(
-        "Fix-up for relocation type not yet implemented: " +
-        StringRef(G.getEdgeKindName(Kind)));
+  case Arm_Call: {
+    if (!checkOpcode<Arm_Call>(R))
+      return makeUnexpectedOpcodeError(G, R, Kind);
+
+    if ((R.Wd & FixupInfo<Arm_Call>::CondMask) !=
+        FixupInfo<Arm_Call>::Unconditional)
+      return make_error<JITLinkError>("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<Arm_Call>::BitBlx) == 0;
+    if (TargetIsThumb != InstrIsBlx) {
+      if (LLVM_LIKELY(TargetIsThumb)) {
+        // Change opcode BL -> BLX and fix range value
+        R.Wd = R.Wd | FixupInfo<Arm_Call>::BitBlx;
+        R.Wd = R.Wd & ~FixupInfo<Arm_Call>::BitH;
+        // Set Thumb bit
+        Value |= 0x01;
+      } else {
+        // Change opcode BLX -> BL
+        R.Wd = R.Wd & ~FixupInfo<Arm_Call>::BitBlx;
+      }
+    }
+
+    if (!isInt<26>(Value))
+      return makeTargetOutOfRangeError(G, B, E);
+    writeImmediate<Arm_Call>(R, encodeImmBA1BlA1BlxA2(Value));
+
+    return Error::success();
+  }
+
   default:
     return make_error<JITLinkError>(
         "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
index 000000000000000..30bba4c09e75046
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_static_arm_reloc.s
@@ -0,0 +1,39 @@
+# 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 <call_site>:
+# CHECK-INSTR: 	       0: ebfffffe     bl      0x0 <call_site>
+# CHECK-INSTR: 	00000004 <call_target>:
+# CHECK-INSTR: 	       4: e12fff1e     bx      lr
+# ARM branch offset is 8, because it accounts for an additional prefetch
+# instruction that increments PC even though it is implicit
+# jitlink-check: decode_operand(call_site, 0) = call_target - (call_site + 8)
+	.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
index 0e41174040b684b..2f9e44daea7a1db 100644
--- a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp
+++ b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp
@@ -29,6 +29,13 @@ struct MutableHalfWords {
   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 @@ 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);
 
 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 @@ TEST(AArch32_Relocations, Thumb_Call_Bare) {
   }
 }
 
+/// 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<Arm_Call>::ImmMask;
+
+  static std::array<uint32_t, 3> 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");


        


More information about the llvm-commits mailing list