[llvm] [JITLink] Support AArch64 big-endian (BE8) ELF objects (PR #225120)

dong jianqiang via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 08:49:47 PDT 2026


https://github.com/dongjianqiang2 created https://github.com/llvm/llvm-project/pull/225120

The AArch64 ELF JITLink backend hard-coded ELF64LE, so aarch64_be objects failed to load ("Only AArch64 (little endian) is supported for now").

- Dispatch aarch64_be to link_ELF_aarch64 and replace the ELF64LE cast in createLinkGraphFromELFObject_aarch64 with dyn_cast-based endianness detection to handle both ELF64LE and ELF64BE.
- Apply target endianness to data fixups (Pointer64, Pointer32, Delta32/64, NegDelta32/64) in aarch64::applyFixup. Instructions are always little-endian encoded (the A64 ISA is word-invariant), so instruction fixups keep writing LE in BE8 mode.

>From 9d286fdebbd24ecb2dcccc12c911c034c98b685e Mon Sep 17 00:00:00 2001
From: Dong JianQiang <dongjianqiang2 at huawei.com>
Date: Tue, 16 Jun 2026 15:36:03 +0800
Subject: [PATCH] [JITLink] Support AArch64 big-endian (BE8) ELF objects

The AArch64 ELF JITLink backend hard-coded ELF64LE, so aarch64_be
objects failed to load ("Only AArch64 (little endian) is supported
for now").

- Dispatch aarch64_be to link_ELF_aarch64 and replace the ELF64LE cast
  in createLinkGraphFromELFObject_aarch64 with dyn_cast-based
  endianness detection to handle both ELF64LE and ELF64BE.
- Apply target endianness to data fixups (Pointer64, Pointer32,
  Delta32/64, NegDelta32/64) in aarch64::applyFixup. Instructions are
  always little-endian encoded (the A64 ISA is word-invariant), so
  instruction fixups keep writing LE in BE8 mode.
---
 .../llvm/ExecutionEngine/JITLink/aarch64.h    | 37 +++++++++++++--
 llvm/lib/ExecutionEngine/JITLink/ELF.cpp      |  1 +
 .../ExecutionEngine/JITLink/ELF_aarch64.cpp   | 26 +++++++---
 .../JITLink/AArch64/ELF_aarch64_be.s          | 47 +++++++++++++++++++
 4 files changed, 100 insertions(+), 11 deletions(-)
 create mode 100644 llvm/test/ExecutionEngine/JITLink/AArch64/ELF_aarch64_be.s

diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch64.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch64.h
index 12bcdd1a79d80a..4b737f0828e207 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch64.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch64.h
@@ -501,17 +501,46 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
   char *FixupPtr = BlockWorkingMem + E.getOffset();
   orc::ExecutorAddr FixupAddress = B.getAddress() + E.getOffset();
 
+  // AArch64 BE8: data values follow target endianness, but instructions are
+  // always encoded in LE byte order (the A64 ISA is word-invariant).
+  bool IsBE = !G.getTargetTriple().isLittleEndian();
+
+  auto writeData64 = [IsBE](char *Ptr, uint64_t Val) {
+    if (IsBE)
+      *(ubig64_t *)Ptr = Val;
+    else
+      *(ulittle64_t *)Ptr = Val;
+  };
+  auto writeData32 = [IsBE](char *Ptr, uint32_t Val) {
+    if (IsBE)
+      *(ubig32_t *)Ptr = Val;
+    else
+      *(ulittle32_t *)Ptr = Val;
+  };
+  auto writeDataS32 = [IsBE](char *Ptr, int32_t Val) {
+    if (IsBE)
+      *(big32_t *)Ptr = Val;
+    else
+      *(little32_t *)Ptr = Val;
+  };
+  auto writeDataS64 = [IsBE](char *Ptr, int64_t Val) {
+    if (IsBE)
+      *(big64_t *)Ptr = Val;
+    else
+      *(little64_t *)Ptr = Val;
+  };
+
   switch (E.getKind()) {
   case Pointer64: {
     uint64_t Value = E.getTarget().getAddress().getValue() + E.getAddend();
-    *(ulittle64_t *)FixupPtr = Value;
+    writeData64(FixupPtr, Value);
     break;
   }
   case Pointer32: {
     uint64_t Value = E.getTarget().getAddress().getValue() + E.getAddend();
     if (Value > std::numeric_limits<uint32_t>::max())
       return makeTargetOutOfRangeError(G, B, E);
-    *(ulittle32_t *)FixupPtr = Value;
+    writeData32(FixupPtr, Value);
     break;
   }
   case Delta32:
@@ -528,9 +557,9 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
       if (Value < std::numeric_limits<int32_t>::min() ||
           Value > std::numeric_limits<int32_t>::max())
         return makeTargetOutOfRangeError(G, B, E);
-      *(little32_t *)FixupPtr = Value;
+      writeDataS32(FixupPtr, Value);
     } else
-      *(little64_t *)FixupPtr = Value;
+      writeDataS64(FixupPtr, Value);
     break;
   }
   case Branch26PCRel: {
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp
index 494466297aefe4..d568fbc71632b1 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF.cpp
@@ -119,6 +119,7 @@ void link_ELF(std::unique_ptr<LinkGraph> G,
               std::unique_ptr<JITLinkContext> Ctx) {
   switch (G->getTargetTriple().getArch()) {
   case Triple::aarch64:
+  case Triple::aarch64_be:
     link_ELF_aarch64(std::move(G), std::move(Ctx));
     return;
   case Triple::arm:
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp
index cd2eed748acdef..b4e2a6773dde55 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch64.cpp
@@ -237,6 +237,8 @@ class ELFLinkGraphBuilder_aarch64 : public ELFLinkGraphBuilder<ELFT> {
   Error addSingleRelocation(const typename ELFT::Rela &Rel,
                             const typename ELFT::Shdr &FixupSect,
                             Block &BlockToFix) {
+    // AArch64 BE8: instructions are always LE-encoded regardless of the ELF
+    // data endianness, so instruction words are read as ulittle32_t below.
     using support::ulittle32_t;
     using Base = ELFLinkGraphBuilder<ELFT>;
 
@@ -687,14 +689,24 @@ Expected<std::unique_ptr<LinkGraph>> createLinkGraphFromELFObject_aarch64(
   if (!Features)
     return Features.takeError();
 
-  assert((*ELFObj)->getArch() == Triple::aarch64 &&
-         "Only AArch64 (little endian) is supported for now");
+  assert(((*ELFObj)->getArch() == Triple::aarch64 ||
+          (*ELFObj)->getArch() == Triple::aarch64_be) &&
+         "Object is not an AArch64 ELF file");
 
-  auto &ELFObjFile = cast<object::ELFObjectFile<object::ELF64LE>>(**ELFObj);
-  return ELFLinkGraphBuilder_aarch64<object::ELF64LE>(
-             (*ELFObj)->getFileName(), ELFObjFile.getELFFile(), std::move(SSP),
-             (*ELFObj)->makeTriple(), std::move(*Features))
-      .buildGraph();
+  if (auto *E = dyn_cast<object::ELFObjectFile<object::ELF64LE>>(&**ELFObj))
+    return ELFLinkGraphBuilder_aarch64<object::ELF64LE>(
+               (*ELFObj)->getFileName(), E->getELFFile(), std::move(SSP),
+               (*ELFObj)->makeTriple(), std::move(*Features))
+        .buildGraph();
+
+  if (auto *E = dyn_cast<object::ELFObjectFile<object::ELF64BE>>(&**ELFObj))
+    return ELFLinkGraphBuilder_aarch64<object::ELF64BE>(
+               (*ELFObj)->getFileName(), E->getELFFile(), std::move(SSP),
+               (*ELFObj)->makeTriple(), std::move(*Features))
+        .buildGraph();
+
+  return make_error<JITLinkError>("Unsupported AArch64 ELF format in " +
+                                  ObjectBuffer.getBufferIdentifier());
 }
 
 void link_ELF_aarch64(std::unique_ptr<LinkGraph> G,
diff --git a/llvm/test/ExecutionEngine/JITLink/AArch64/ELF_aarch64_be.s b/llvm/test/ExecutionEngine/JITLink/AArch64/ELF_aarch64_be.s
new file mode 100644
index 00000000000000..bd3401cb0c45a3
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/AArch64/ELF_aarch64_be.s
@@ -0,0 +1,47 @@
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=aarch64_be-unknown-linux-gnu -filetype=obj \
+# RUN:   -o %t/elf_aarch64_be.o %s
+# RUN: llvm-jitlink -noexec -abs external_data=0xdeadbeef \
+# RUN:   -check %s %t/elf_aarch64_be.o
+
+# Test that AArch64 big-endian (BE8) objects link: data fixups are written
+# in target (big) endianness while instruction fixups stay little-endian
+# (the A64 ISA is word-invariant, so instructions are always LE-encoded).
+
+	.text
+	.globl	main
+	.p2align	2
+	.type	main, at function
+main:
+	ret
+	.size	main, .-main
+
+# Instruction fixup (R_AARCH64_CALL26): the BL immediate is patched into an
+# LE-encoded instruction word, even in a big-endian object.
+# jitlink-check: decode_operand(local_func_call26, 0)[25:0] = (local_func - local_func_call26)[27:2]
+	.globl	local_func
+	.p2align	2
+	.type	local_func, at function
+local_func:
+	ret
+	.size	local_func, .-local_func
+
+	.globl	local_func_call26
+	.p2align	2
+local_func_call26:
+	bl	local_func
+	.size	local_func_call26, .-local_func_call26
+
+# Data fixups: absolute addresses are stored big-endian.
+# jitlink-check: *{8}(local_func_addr_quad) = local_func
+# jitlink-check: *{4}(external_data_addr_word) = external_data
+	.data
+	.globl	local_func_addr_quad
+	.p2align	3
+local_func_addr_quad:
+	.quad	local_func
+
+	.globl	external_data_addr_word
+	.p2align	2
+external_data_addr_word:
+	.word	external_data



More information about the llvm-commits mailing list