[clang] [llvm] [Draft] Basic JITLink AArch32 support for clang-repl (PR #77313)
Stefan Gränitz via llvm-commits
llvm-commits at lists.llvm.org
Sat Jan 20 10:34:47 PST 2024
https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/77313
>From ab1fa3773766072882666a16682e985bbd9cd72d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Thu, 18 Jan 2024 17:26:33 +0100
Subject: [PATCH 1/9] [JITLink][AArch32] Add GOT builder and implement
R_ARM_GOT_PREL relocations for ELF
---
.../llvm/ExecutionEngine/JITLink/aarch32.h | 17 ++++-
.../ExecutionEngine/JITLink/ELF_aarch32.cpp | 7 ++
llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 65 +++++++++++++++----
.../JITLink/AArch32/ELF_relocations_data.s | 50 +++++++++++---
4 files changed, 118 insertions(+), 21 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index 7765208b5e3dfec..0968a093279bfbf 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -41,7 +41,10 @@ enum EdgeKind_aarch32 : Edge::Kind {
/// Absolute 32-bit value relocation
Data_Pointer32,
- LastDataRelocation = Data_Pointer32,
+ /// Create GOT entry and store offset
+ Data_RequestGOTAndTransformToDelta32,
+
+ LastDataRelocation = Data_RequestGOTAndTransformToDelta32,
///
/// Relocations of class Arm (covers fixed-width 4-byte instruction subset)
@@ -318,6 +321,18 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
llvm_unreachable("Relocation must be of class Data, Arm or Thumb");
}
+/// Populate a Global Offset Table from edges that request it.
+class GOTBuilder : public TableManager<GOTBuilder> {
+public:
+ static StringRef getSectionName() { return "$__GOT"; }
+
+ bool visitEdge(LinkGraph &G, Block *B, Edge &E);
+ Symbol &createEntry(LinkGraph &G, Symbol &Target);
+
+private:
+ Section *GOTSection = nullptr;
+};
+
/// Stubs builder for v7 emits non-position-independent Thumb stubs.
///
/// Right now we only have one default stub kind, but we want to extend this
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
index b862a7ba2acc922..2553cd70a576912 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
@@ -35,6 +35,8 @@ Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType) {
switch (ELFType) {
case ELF::R_ARM_ABS32:
return aarch32::Data_Pointer32;
+ case ELF::R_ARM_GOT_PREL:
+ return aarch32::Data_RequestGOTAndTransformToDelta32;
case ELF::R_ARM_REL32:
return aarch32::Data_Delta32;
case ELF::R_ARM_CALL:
@@ -71,6 +73,8 @@ Expected<uint32_t> getELFRelocationType(Edge::Kind Kind) {
return ELF::R_ARM_REL32;
case aarch32::Data_Pointer32:
return ELF::R_ARM_ABS32;
+ case aarch32::Data_RequestGOTAndTransformToDelta32:
+ return ELF::R_ARM_GOT_PREL;
case aarch32::Arm_Call:
return ELF::R_ARM_CALL;
case aarch32::Arm_Jump24:
@@ -222,6 +226,9 @@ Error buildTables_ELF_aarch32(LinkGraph &G) {
StubsManagerType StubsManager;
visitExistingEdges(G, StubsManager);
+ aarch32::GOTBuilder GOT;
+ visitExistingEdges(G, GOT);
+
return Error::success();
}
diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
index 8153c97deff6289..eb51a4896ddcc66 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -395,6 +395,7 @@ Expected<int64_t> readAddendData(LinkGraph &G, Block &B, Edge::OffsetT Offset,
switch (Kind) {
case Data_Delta32:
case Data_Pointer32:
+ case Data_RequestGOTAndTransformToDelta32:
return SignExtend64<32>(support::endian::read32(FixupPtr, Endian));
default:
return make_error<JITLinkError>(
@@ -464,15 +465,6 @@ Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
char *BlockWorkingMem = B.getAlreadyMutableContent().data();
char *FixupPtr = BlockWorkingMem + E.getOffset();
- auto Write32 = [FixupPtr, Endian = G.getEndianness()](int64_t Value) {
- assert(isInt<32>(Value) && "Must be in signed 32-bit range");
- uint32_t Imm = static_cast<int32_t>(Value);
- if (LLVM_LIKELY(Endian == endianness::little))
- endian::write32<endianness::little>(FixupPtr, Imm);
- else
- endian::write32<endianness::big>(FixupPtr, Imm);
- };
-
Edge::Kind Kind = E.getKind();
uint64_t FixupAddress = (B.getAddress() + E.getOffset()).getValue();
int64_t Addend = E.getAddend();
@@ -487,16 +479,24 @@ Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
int64_t Value = TargetAddress - FixupAddress + Addend;
if (!isInt<32>(Value))
return makeTargetOutOfRangeError(G, B, E);
- Write32(Value);
+ if (LLVM_LIKELY(G.getEndianness() == endianness::little))
+ endian::write32le(FixupPtr, Value);
+ else
+ endian::write32be(FixupPtr, Value);
return Error::success();
}
case Data_Pointer32: {
int64_t Value = TargetAddress + Addend;
- if (!isInt<32>(Value))
+ if (!isUInt<32>(Value))
return makeTargetOutOfRangeError(G, B, E);
- Write32(Value);
+ if (LLVM_LIKELY(G.getEndianness() == endianness::little))
+ endian::write32le(FixupPtr, Value);
+ else
+ endian::write32be(FixupPtr, Value);
return Error::success();
}
+ case Data_RequestGOTAndTransformToDelta32:
+ llvm_unreachable("Should be transformed");
default:
return make_error<JITLinkError>(
"In graph " + G.getName() + ", section " + B.getSection().getName() +
@@ -678,6 +678,46 @@ Error applyFixupThumb(LinkGraph &G, Block &B, const Edge &E,
}
}
+const uint8_t GOTEntryInit[] = {
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/// Create a new node in the link-graph for the given pointer value.
+template <size_t Size>
+static Block &allocPointer(LinkGraph &G, Section &S, const uint8_t (&Content)[Size]) {
+ static_assert(Size == 4, "Pointers are 32-bit");
+ constexpr uint64_t Alignment = 4;
+ ArrayRef<char> Init(reinterpret_cast<const char *>(Content), Size);
+ return G.createContentBlock(S, Init, orc::ExecutorAddr(), Alignment, 0);
+}
+
+Symbol &GOTBuilder::createEntry(LinkGraph &G, Symbol &Target) {
+ if (!GOTSection)
+ GOTSection = &G.createSection(getSectionName(), orc::MemProt::Read);
+ Block &B = allocPointer(G, *GOTSection, GOTEntryInit);
+ constexpr int64_t GOTEntryAddend = 0;
+ B.addEdge(Data_Pointer32, 0, Target, GOTEntryAddend);
+ return G.addAnonymousSymbol(B, 0, B.getSize(), false, false);
+}
+
+bool GOTBuilder::visitEdge(LinkGraph &G, Block *B, Edge &E) {
+ Edge::Kind KindToSet = Edge::Invalid;
+ switch (E.getKind()) {
+ case aarch32::Data_RequestGOTAndTransformToDelta32: {
+ KindToSet = aarch32::Data_Delta32;
+ break;
+ }
+ default:
+ return false;
+ }
+ LLVM_DEBUG(dbgs() << " Transforming " << G.getEdgeKindName(E.getKind()) << " edge at "
+ << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
+ << formatv("{0:x}", E.getOffset()) << ") into " << G.getEdgeKindName(KindToSet) << "\n");
+ E.setKind(KindToSet);
+ E.setTarget(getEntryForTarget(G, E.getTarget()));
+ return true;
+}
+
const uint8_t Thumbv7ABS[] = {
0x40, 0xf2, 0x00, 0x0c, // movw r12, #0x0000 ; lower 16-bit
0xc0, 0xf2, 0x00, 0x0c, // movt r12, #0x0000 ; upper 16-bit
@@ -709,6 +749,7 @@ const char *getEdgeKindName(Edge::Kind K) {
switch (K) {
KIND_NAME_CASE(Data_Delta32)
KIND_NAME_CASE(Data_Pointer32)
+ KIND_NAME_CASE(Data_RequestGOTAndTransformToDelta32)
KIND_NAME_CASE(Arm_Call)
KIND_NAME_CASE(Arm_Jump24)
KIND_NAME_CASE(Arm_MovwAbsNC)
diff --git a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
index 379d35fe4902cb0..f91a4733c40eee0 100644
--- a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
+++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
@@ -1,12 +1,13 @@
-# RUN: llvm-mc -triple=armv7-none-linux-gnueabi -arm-add-build-attributes -filetype=obj -o %t_armv7.o %s
-# RUN: llvm-objdump -r %t_armv7.o | FileCheck --check-prefix=CHECK-TYPE %s
+# RUN: rm -rf %t && mkdir -p %t/armv7 && mkdir -p %t/thumbv7
+# RUN: llvm-mc -triple=armv7-none-linux-gnueabi -arm-add-build-attributes -filetype=obj -o %t/armv7/out.o %s
+# RUN: llvm-objdump -r %t/armv7/out.o | FileCheck --check-prefix=CHECK-TYPE %s
# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 -slab-allocate 10Kb -slab-page-size 4096 \
-# RUN: -abs target=0x76bbe88f -check %s %t_armv7.o
+# RUN: -abs target=0x76bbe88f -check %s %t/armv7/out.o
-# RUN: llvm-mc -triple=thumbv7-none-linux-gnueabi -arm-add-build-attributes -filetype=obj -o %t_thumbv7.o %s
-# RUN: llvm-objdump -r %t_thumbv7.o | FileCheck --check-prefix=CHECK-TYPE %s
+# RUN: llvm-mc -triple=thumbv7-none-linux-gnueabi -arm-add-build-attributes -filetype=obj -o %t/thumbv7/out.o %s
+# RUN: llvm-objdump -r %t/thumbv7/out.o | FileCheck --check-prefix=CHECK-TYPE %s
# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 -slab-allocate 10Kb -slab-page-size 4096 \
-# RUN: -abs target=0x76bbe88f -check %s %t_thumbv7.o
+# RUN: -abs target=0x76bbe88f -check %s %t/thumbv7/out.o
.data
.global target
@@ -28,10 +29,43 @@ rel32:
.word target - .
.size rel32, .-rel32
-# Empty main function for jitlink to be happy
+# CHECK-TYPE: {{[0-9a-f]+}} R_ARM_GOT_PREL target
+#
+# The GOT entry contains the absolute address of the external:
+# jitlink-check: *{4}(got_addr(out.o, target)) = target
+#
+# The embedded offset value contains the offset to the GOT entry relative to pc.
+# The +12 accounts for the ARM branch offset (8) and the .LPC offset (4), which
+# is stored as initial addend inline.
+# FIXME: We shouldn't need to substract the 64-bit sign-extension manually.
+# jitlink-check: *{4}got_prel_offset = got_addr(out.o, target) - (got_prel + 12) - 0xffffffff00000000
+ .globl got_prel
+ .type got_prel,%function
+ .p2align 2
+ .code 32
+got_prel:
+ ldr r0, .LCPI
+.LPC:
+ ldr r0, [pc, r0]
+ ldr r0, [r0]
+ bx lr
+# Actual relocation site is on the embedded offset value:
+ .globl got_prel_offset
+got_prel_offset:
+.LCPI:
+ .long target(GOT_PREL)-((.LPC+8)-.LCPI)
+ .size got_prel_offset, .-got_prel_offset
+ .size got_prel, .-got_prel
+
+# This test is executable with any 4-byte external target:
+# > echo "unsigned target = 42;" | clang -target armv7-linux-gnueabihf -o target.o -c -xc -
+# > llvm-jitlink target.o armv7/out.o
+#
.globl main
.type main, %function
.p2align 2
main:
- bx lr
+ push {lr}
+ bl got_prel
+ pop {pc}
.size main, .-main
>From 8b607588a28b54c70609be0d7bc160f732797db8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Fri, 19 Jan 2024 19:17:36 +0100
Subject: [PATCH 2/9] fixup! [JITLink][AArch32] Add GOT builder and implement
R_ARM_GOT_PREL relocations for ELF
---
llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
index eb51a4896ddcc66..111527a39e06ecd 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -679,12 +679,16 @@ Error applyFixupThumb(LinkGraph &G, Block &B, const Edge &E,
}
const uint8_t GOTEntryInit[] = {
- 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
};
/// Create a new node in the link-graph for the given pointer value.
template <size_t Size>
-static Block &allocPointer(LinkGraph &G, Section &S, const uint8_t (&Content)[Size]) {
+static Block &allocPointer(LinkGraph &G, Section &S,
+ const uint8_t (&Content)[Size]) {
static_assert(Size == 4, "Pointers are 32-bit");
constexpr uint64_t Alignment = 4;
ArrayRef<char> Init(reinterpret_cast<const char *>(Content), Size);
@@ -710,9 +714,11 @@ bool GOTBuilder::visitEdge(LinkGraph &G, Block *B, Edge &E) {
default:
return false;
}
- LLVM_DEBUG(dbgs() << " Transforming " << G.getEdgeKindName(E.getKind()) << " edge at "
- << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
- << formatv("{0:x}", E.getOffset()) << ") into " << G.getEdgeKindName(KindToSet) << "\n");
+ LLVM_DEBUG(dbgs() << " Transforming " << G.getEdgeKindName(E.getKind())
+ << " edge at " << B->getFixupAddress(E) << " ("
+ << B->getAddress() << " + "
+ << formatv("{0:x}", E.getOffset()) << ") into "
+ << G.getEdgeKindName(KindToSet) << "\n");
E.setKind(KindToSet);
E.setTarget(getEntryForTarget(G, E.getTarget()));
return true;
>From e19bf04dd167179833093878d66779df683ed994 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 13 Jan 2024 23:35:54 +0100
Subject: [PATCH 3/9] [llvm-jitlink] Allow optional stub-kind filter in
stub_addr() expressions
---
.../llvm/ExecutionEngine/RuntimeDyldChecker.h | 2 +-
.../RuntimeDyld/RuntimeDyldChecker.cpp | 27 ++++--
.../RuntimeDyld/RuntimeDyldCheckerImpl.h | 3 +-
llvm/tools/llvm-jitlink/llvm-jitlink-elf.cpp | 2 +-
llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 86 +++++++++++++++++--
llvm/tools/llvm-jitlink/llvm-jitlink.h | 7 +-
llvm/tools/llvm-rtdyld/llvm-rtdyld.cpp | 11 ++-
7 files changed, 114 insertions(+), 24 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/RuntimeDyldChecker.h b/llvm/include/llvm/ExecutionEngine/RuntimeDyldChecker.h
index 80e4bbf494339cf..034c134a13731d3 100644
--- a/llvm/include/llvm/ExecutionEngine/RuntimeDyldChecker.h
+++ b/llvm/include/llvm/ExecutionEngine/RuntimeDyldChecker.h
@@ -154,7 +154,7 @@ class RuntimeDyldChecker {
using GetSectionInfoFunction = std::function<Expected<MemoryRegionInfo>(
StringRef FileName, StringRef SectionName)>;
using GetStubInfoFunction = std::function<Expected<MemoryRegionInfo>(
- StringRef StubContainer, StringRef TargetName)>;
+ StringRef StubContainer, StringRef TargetName, StringRef StubKindFilter)>;
using GetGOTInfoFunction = std::function<Expected<MemoryRegionInfo>(
StringRef GOTContainer, StringRef TargetName)>;
diff --git a/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldChecker.cpp b/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldChecker.cpp
index 7fadbdd6a1fff2b..11fb21a9c1c0a70 100644
--- a/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldChecker.cpp
+++ b/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldChecker.cpp
@@ -400,6 +400,15 @@ class RuntimeDyldCheckerExprEval {
StringRef Symbol;
std::tie(Symbol, RemainingExpr) = parseSymbol(RemainingExpr);
+ // Parse optional parameter to filter by stub kind
+ StringRef KindNameFilter;
+ if (RemainingExpr.starts_with(",")) {
+ RemainingExpr = RemainingExpr.substr(1).ltrim();
+ size_t ClosingBracket = RemainingExpr.find(")");
+ KindNameFilter = RemainingExpr.substr(0, ClosingBracket);
+ RemainingExpr = RemainingExpr.substr(ClosingBracket);
+ }
+
if (!RemainingExpr.starts_with(")"))
return std::make_pair(
unexpectedToken(RemainingExpr, Expr, "expected ')'"), "");
@@ -407,8 +416,9 @@ class RuntimeDyldCheckerExprEval {
uint64_t StubAddr;
std::string ErrorMsg;
- std::tie(StubAddr, ErrorMsg) = Checker.getStubOrGOTAddrFor(
- StubContainerName, Symbol, PCtx.IsInsideLoad, IsStubAddr);
+ std::tie(StubAddr, ErrorMsg) =
+ Checker.getStubOrGOTAddrFor(StubContainerName, Symbol, KindNameFilter,
+ PCtx.IsInsideLoad, IsStubAddr);
if (ErrorMsg != "")
return std::make_pair(EvalResult(ErrorMsg), "");
@@ -985,11 +995,14 @@ std::pair<uint64_t, std::string> RuntimeDyldCheckerImpl::getSectionAddr(
}
std::pair<uint64_t, std::string> RuntimeDyldCheckerImpl::getStubOrGOTAddrFor(
- StringRef StubContainerName, StringRef SymbolName, bool IsInsideLoad,
- bool IsStubAddr) const {
-
- auto StubInfo = IsStubAddr ? GetStubInfo(StubContainerName, SymbolName)
- : GetGOTInfo(StubContainerName, SymbolName);
+ StringRef StubContainerName, StringRef SymbolName, StringRef StubKindFilter,
+ bool IsInsideLoad, bool IsStubAddr) const {
+
+ assert((StubKindFilter.empty() || IsStubAddr) &&
+ "Kind name filter only supported for stubs");
+ auto StubInfo =
+ IsStubAddr ? GetStubInfo(StubContainerName, SymbolName, StubKindFilter)
+ : GetGOTInfo(StubContainerName, SymbolName);
if (!StubInfo) {
std::string ErrMsg;
diff --git a/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldCheckerImpl.h b/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldCheckerImpl.h
index 9f44a9389f47344..bda554e9e5b673c 100644
--- a/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldCheckerImpl.h
+++ b/llvm/lib/ExecutionEngine/RuntimeDyld/RuntimeDyldCheckerImpl.h
@@ -64,7 +64,8 @@ class RuntimeDyldCheckerImpl {
std::pair<uint64_t, std::string>
getStubOrGOTAddrFor(StringRef StubContainerName, StringRef Symbol,
- bool IsInsideLoad, bool IsStubAddr) const;
+ StringRef StubKindFilter, bool IsInsideLoad,
+ bool IsStubAddr) const;
std::optional<uint64_t> getSectionLoadAddress(void *LocalAddr) const;
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink-elf.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink-elf.cpp
index c6b4218aad7af86..1f0fca2202a0ef7 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink-elf.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink-elf.cpp
@@ -91,7 +91,7 @@ static Error registerSymbol(LinkGraph &G, Symbol &Sym, Session::FileInfo &FI,
case Stubs:
return FI.registerStubEntry(G, Sym, getELFStubTarget);
case AArch32Stubs:
- return FI.registerStubEntry(G, Sym, getELFAArch32StubTarget);
+ return FI.registerMultiStubEntry(G, Sym, getELFAArch32StubTarget);
case Other:
return Error::success();
}
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index 8c18610313ce8f4..d233ebdb5a3a8dc 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -331,8 +331,12 @@ operator<<(raw_ostream &OS, const Session::FileInfo &FI) {
OS << " Section \"" << SIKV.first() << "\": " << SIKV.second << "\n";
for (auto &GOTKV : FI.GOTEntryInfos)
OS << " GOT \"" << GOTKV.first() << "\": " << GOTKV.second << "\n";
- for (auto &StubKV : FI.StubInfos)
- OS << " Stub \"" << StubKV.first() << "\": " << StubKV.second << "\n";
+ for (auto &StubKVs : FI.StubInfos) {
+ OS << " Stubs \"" << StubKVs.first() << "\":";
+ for (auto MemRegion : StubKVs.second)
+ OS << " " << MemRegion;
+ OS << "\n";
+ }
return OS;
}
@@ -1207,9 +1211,35 @@ Error Session::FileInfo::registerStubEntry(
auto TS = GetSymbolTarget(G, Sym.getBlock());
if (!TS)
return TS.takeError();
- StubInfos[TS->getName()] = {Sym.getSymbolContent(),
- Sym.getAddress().getValue(),
- Sym.getTargetFlags()};
+
+ SmallVector<MemoryRegionInfo> &Entry = StubInfos[TS->getName()];
+ Entry.insert(Entry.begin(),
+ {Sym.getSymbolContent(), Sym.getAddress().getValue(),
+ Sym.getTargetFlags()});
+ return Error::success();
+}
+
+Error Session::FileInfo::registerMultiStubEntry(
+ LinkGraph &G, Symbol &Sym, GetSymbolTargetFunction GetSymbolTarget) {
+ if (Sym.isSymbolZeroFill())
+ return make_error<StringError>("Unexpected zero-fill symbol in section " +
+ Sym.getBlock().getSection().getName(),
+ inconvertibleErrorCode());
+
+ auto Target = GetSymbolTarget(G, Sym.getBlock());
+ if (!Target)
+ return Target.takeError();
+
+ SmallVector<MemoryRegionInfo> &Entry = StubInfos[Target->getName()];
+ Entry.emplace_back(Sym.getSymbolContent(), Sym.getAddress().getValue(),
+ Sym.getTargetFlags());
+
+ // Let's keep stubs ordered by ascending address.
+ std::sort(Entry.begin(), Entry.end(),
+ [](const MemoryRegionInfo &L, const MemoryRegionInfo &R) {
+ return L.getTargetAddress() < R.getTargetAddress();
+ });
+
return Error::success();
}
@@ -1235,8 +1265,14 @@ Session::findSectionInfo(StringRef FileName, StringRef SectionName) {
return SecInfoItr->second;
}
+static StringRef detectStubKind(const Session::MemoryRegionInfo &Stub) {
+ // Implement acutal stub kind detection
+ return "";
+}
+
Expected<Session::MemoryRegionInfo &>
-Session::findStubInfo(StringRef FileName, StringRef TargetName) {
+Session::findStubInfo(StringRef FileName, StringRef TargetName,
+ StringRef KindNameFilter) {
auto FI = findFileInfo(FileName);
if (!FI)
return FI.takeError();
@@ -1246,7 +1282,38 @@ Session::findStubInfo(StringRef FileName, StringRef TargetName) {
"\" registered for file \"" + FileName +
"\"",
inconvertibleErrorCode());
- return StubInfoItr->second;
+ auto &StubsForTarget = StubInfoItr->second;
+ assert(!StubsForTarget.empty() && "At least 1 stub in each entry");
+ if (KindNameFilter.empty() && StubsForTarget.size() == 1)
+ return StubsForTarget[0]; // Regular single-stub match
+
+ std::string KindsStr;
+ SmallVector<MemoryRegionInfo *, 1> Matches;
+ Regex KindNameMatcher(KindNameFilter.empty() ? ".*" : KindNameFilter);
+ for (MemoryRegionInfo &Stub : StubsForTarget) {
+ StringRef Kind = detectStubKind(Stub);
+ if (KindNameMatcher.match(Kind))
+ Matches.push_back(&Stub);
+ KindsStr += "\"" + (Kind.empty() ? "<unknown>" : Kind.str()) + "\", ";
+ }
+ if (Matches.empty())
+ return make_error<StringError>(
+ "\"" + TargetName + "\" has " + Twine(StubsForTarget.size()) +
+ " stubs in file \"" + FileName +
+ "\", but none of them matches the stub-kind filter \"" +
+ KindNameFilter + "\" (all encountered kinds are " +
+ StringRef(KindsStr.data(), KindsStr.size() - 2) + ").",
+ inconvertibleErrorCode());
+ if (Matches.size() > 1)
+ return make_error<StringError>(
+ "\"" + TargetName + "\" has " + Twine(Matches.size()) +
+ " candidate stubs in file \"" + FileName +
+ "\". Please refine stub-kind filter \"" + KindNameFilter +
+ "\" for disambiguation (encountered kinds are " +
+ StringRef(KindsStr.data(), KindsStr.size() - 2) + ").",
+ inconvertibleErrorCode());
+
+ return *Matches[0];
}
Expected<Session::MemoryRegionInfo &>
@@ -2015,8 +2082,9 @@ static Error runChecks(Session &S, Triple TT, SubtargetFeatures Features) {
return S.findSectionInfo(FileName, SectionName);
};
- auto GetStubInfo = [&S](StringRef FileName, StringRef SectionName) {
- return S.findStubInfo(FileName, SectionName);
+ auto GetStubInfo = [&S](StringRef FileName, StringRef SectionName,
+ StringRef KindNameFilter) {
+ return S.findStubInfo(FileName, SectionName, KindNameFilter);
};
auto GetGOTInfo = [&S](StringRef FileName, StringRef SectionName) {
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.h b/llvm/tools/llvm-jitlink/llvm-jitlink.h
index 93a00266b150436..e09c15adace20ec 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.h
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.h
@@ -49,7 +49,7 @@ struct Session {
struct FileInfo {
StringMap<MemoryRegionInfo> SectionInfos;
- StringMap<MemoryRegionInfo> StubInfos;
+ StringMap<SmallVector<MemoryRegionInfo, 1>> StubInfos;
StringMap<MemoryRegionInfo> GOTEntryInfos;
using Symbol = jitlink::Symbol;
@@ -61,6 +61,8 @@ struct Session {
GetSymbolTargetFunction GetSymbolTarget);
Error registerStubEntry(LinkGraph &G, Symbol &Sym,
GetSymbolTargetFunction GetSymbolTarget);
+ Error registerMultiStubEntry(LinkGraph &G, Symbol &Sym,
+ GetSymbolTargetFunction GetSymbolTarget);
};
using DynLibJDMap = std::map<std::string, orc::JITDylib *>;
@@ -74,7 +76,8 @@ struct Session {
Expected<MemoryRegionInfo &> findSectionInfo(StringRef FileName,
StringRef SectionName);
Expected<MemoryRegionInfo &> findStubInfo(StringRef FileName,
- StringRef TargetName);
+ StringRef TargetName,
+ StringRef KindNameFilter);
Expected<MemoryRegionInfo &> findGOTEntryInfo(StringRef FileName,
StringRef TargetName);
diff --git a/llvm/tools/llvm-rtdyld/llvm-rtdyld.cpp b/llvm/tools/llvm-rtdyld/llvm-rtdyld.cpp
index 107b555a99faa40..4cb76f4347422b6 100644
--- a/llvm/tools/llvm-rtdyld/llvm-rtdyld.cpp
+++ b/llvm/tools/llvm-rtdyld/llvm-rtdyld.cpp
@@ -926,7 +926,8 @@ static int linkAndVerify() {
};
auto GetStubInfo = [&Dyld, &StubMap](StringRef StubContainer,
- StringRef SymbolName)
+ StringRef SymbolName,
+ StringRef KindNameFilter)
-> Expected<RuntimeDyldChecker::MemoryRegionInfo> {
if (!StubMap.count(StubContainer))
return make_error<StringError>("Stub container not found: " +
@@ -947,6 +948,11 @@ static int linkAndVerify() {
return StubMemInfo;
};
+ auto GetGOTInfo = [&GetStubInfo](StringRef StubContainer,
+ StringRef SymbolName) {
+ return GetStubInfo(StubContainer, SymbolName, "");
+ };
+
// We will initialize this below once we have the first object file and can
// know the endianness.
std::unique_ptr<RuntimeDyldChecker> Checker;
@@ -977,8 +983,7 @@ static int linkAndVerify() {
if (!Checker)
Checker = std::make_unique<RuntimeDyldChecker>(
- IsSymbolValid, GetSymbolInfo, GetSectionInfo, GetStubInfo,
- GetStubInfo,
+ IsSymbolValid, GetSymbolInfo, GetSectionInfo, GetStubInfo, GetGOTInfo,
Obj.isLittleEndian() ? llvm::endianness::little
: llvm::endianness::big,
TheTriple, MCPU, SubtargetFeatures(), dbgs());
>From 559cf1218c87e304dcb474b7f1de5bc13d537247 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 13 Jan 2024 23:58:36 +0100
Subject: [PATCH 4/9] [JITLink][AArch32] Multi-stub support for armv7/thumbv7
---
.../llvm/ExecutionEngine/JITLink/aarch32.h | 59 ++-------
llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 125 ++++++++++++++++--
.../JITLink/AArch32/ELF_stubs_arm.s | 53 ++++++++
.../JITLink/AArch32/ELF_stubs_multi.s | 50 +++++++
llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 46 ++++++-
5 files changed, 274 insertions(+), 59 deletions(-)
create mode 100644 llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_arm.s
create mode 100644 llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_multi.s
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index 0968a093279bfbf..725f9a7eeb88896 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -333,64 +333,31 @@ class GOTBuilder : public TableManager<GOTBuilder> {
Section *GOTSection = nullptr;
};
-/// Stubs builder for v7 emits non-position-independent Thumb stubs.
-///
-/// Right now we only have one default stub kind, but we want to extend this
-/// and allow creation of specific kinds in the future (e.g. branch range
-/// extension or interworking).
-///
-/// Let's keep it simple for the moment and not wire this through a GOT.
-///
-class StubsManager_v7 : public TableManager<StubsManager_v7> {
+/// Stubs builder for v7 emits non-position-independent Arm and Thumb stubs.
+class StubsManager_v7 {
public:
StubsManager_v7() = default;
/// Name of the object file section that will contain all our stubs.
static StringRef getSectionName() {
- return "__llvm_jitlink_aarch32_STUBS_Thumbv7";
+ return "__llvm_jitlink_aarch32_STUBS_v7";
}
/// Implements link-graph traversal via visitExistingEdges().
- bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
- if (E.getTarget().isDefined())
- return false;
-
- switch (E.getKind()) {
- case Thumb_Call:
- case Thumb_Jump24: {
- DEBUG_WITH_TYPE("jitlink", {
- dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
- << B->getFixupAddress(E) << " (" << B->getAddress() << " + "
- << formatv("{0:x}", E.getOffset()) << ")\n";
- });
- E.setTarget(this->getEntryForTarget(G, E.getTarget()));
- return true;
- }
- }
- return false;
- }
-
- /// Create a branch range extension stub with Thumb encoding for v7 CPUs.
- Symbol &createEntry(LinkGraph &G, Symbol &Target);
+ bool visitEdge(LinkGraph &G, Block *B, Edge &E);
private:
- /// Create a new node in the link-graph for the given stub template.
- template <size_t Size>
- Block &addStub(LinkGraph &G, const uint8_t (&Code)[Size],
- uint64_t Alignment) {
- ArrayRef<char> Template(reinterpret_cast<const char *>(Code), Size);
- return G.createContentBlock(getStubsSection(G), Template,
- orc::ExecutorAddr(), Alignment, 0);
- }
-
- /// Get or create the object file section that will contain all our stubs.
- Section &getStubsSection(LinkGraph &G) {
- if (!StubsSection)
- StubsSection = &G.createSection(getSectionName(),
- orc::MemProt::Read | orc::MemProt::Exec);
- return *StubsSection;
+ // Two slots per external: Arm and Thumb
+ using StubMapEntry = std::tuple<Symbol *, Symbol *>;
+
+ Symbol *&getStubSymbolSlot(StringRef Name, bool Thumb) {
+ StubMapEntry &Stubs = StubMap.try_emplace(Name).first->second;
+ if (Thumb)
+ return std::get<1>(Stubs);
+ return std::get<0>(Stubs);
}
+ DenseMap<StringRef, StubMapEntry> StubMap;
Section *StubsSection = nullptr;
};
diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
index 111527a39e06ecd..7f7b460b4b36968 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -15,6 +15,7 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/ManagedStatic.h"
@@ -724,27 +725,127 @@ bool GOTBuilder::visitEdge(LinkGraph &G, Block *B, Edge &E) {
return true;
}
+const uint8_t Armv7ABS[] = {
+ 0x00, 0xc0, 0x00, 0xe3, // movw r12, #0x0000 ; lower 16-bit
+ 0x00, 0xc0, 0x40, 0xe3, // movt r12, #0x0000 ; upper 16-bit
+ 0x1c, 0xff, 0x2f, 0xe1 // bx r12
+};
+
const uint8_t Thumbv7ABS[] = {
0x40, 0xf2, 0x00, 0x0c, // movw r12, #0x0000 ; lower 16-bit
0xc0, 0xf2, 0x00, 0x0c, // movt r12, #0x0000 ; upper 16-bit
0x60, 0x47 // bx r12
};
-Symbol &StubsManager_v7::createEntry(LinkGraph &G, Symbol &Target) {
+/// Create a new node in the link-graph for the given stub template.
+template <size_t Size>
+static Block &allocStub(LinkGraph &G, Section &S, const uint8_t (&Code)[Size]) {
constexpr uint64_t Alignment = 4;
- Block &B = addStub(G, Thumbv7ABS, Alignment);
- LLVM_DEBUG({
- const char *StubPtr = B.getContent().data();
- HalfWords Reg12 = encodeRegMovtT1MovwT3(12);
- assert(checkRegister<Thumb_MovwAbsNC>(StubPtr, Reg12) &&
- checkRegister<Thumb_MovtAbs>(StubPtr + 4, Reg12) &&
- "Linker generated stubs may only corrupt register r12 (IP)");
- });
+ ArrayRef<char> Template(reinterpret_cast<const char *>(Code), Size);
+ return G.createContentBlock(S, Template, orc::ExecutorAddr(), Alignment, 0);
+}
+
+static Block &createStubThumbv7(LinkGraph &G, Section &S, Symbol &Target) {
+ Block &B = allocStub(G, S, Thumbv7ABS);
B.addEdge(Thumb_MovwAbsNC, 0, Target, 0);
B.addEdge(Thumb_MovtAbs, 4, Target, 0);
- Symbol &Stub = G.addAnonymousSymbol(B, 0, B.getSize(), true, false);
- Stub.setTargetFlags(ThumbSymbol);
- return Stub;
+
+ [[maybe_unused]] const char *StubPtr = B.getContent().data();
+ [[maybe_unused]] HalfWords Reg12 = encodeRegMovtT1MovwT3(12);
+ assert(checkRegister<Thumb_MovwAbsNC>(StubPtr, Reg12) &&
+ checkRegister<Thumb_MovtAbs>(StubPtr + 4, Reg12) &&
+ "Linker generated stubs may only corrupt register r12 (IP)");
+ return B;
+}
+
+static Block &createStubArmv7(LinkGraph &G, Section &S, Symbol &Target) {
+ Block &B = allocStub(G, S, Armv7ABS);
+ B.addEdge(Arm_MovwAbsNC, 0, Target, 0);
+ B.addEdge(Arm_MovtAbs, 4, Target, 0);
+
+ [[maybe_unused]] const char *StubPtr = B.getContent().data();
+ [[maybe_unused]] uint32_t Reg12 = encodeRegMovtA1MovwA2(12);
+ assert(checkRegister<Arm_MovwAbsNC>(StubPtr, Reg12) &&
+ checkRegister<Arm_MovtAbs>(StubPtr + 4, Reg12) &&
+ "Linker generated stubs may only corrupt register r12 (IP)");
+ return B;
+}
+
+static bool needsStub(const Edge &E) {
+ Symbol &Target = E.getTarget();
+
+ // Create stubs for external branch targets.
+ if (!Target.isDefined()) {
+ switch (E.getKind()) {
+ case Arm_Call:
+ case Arm_Jump24:
+ case Thumb_Call:
+ case Thumb_Jump24:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ // For local targets, create interworking stubs if we switch Arm/Thumb with an
+ // instruction that cannot switch the instruction set state natively.
+ bool TargetIsThumb = Target.getTargetFlags() & ThumbSymbol;
+ switch (E.getKind()) {
+ case Arm_Jump24:
+ return TargetIsThumb; // Branch to Thumb needs interworking stub
+ case Thumb_Jump24:
+ return !TargetIsThumb; // Branch to Arm needs interworking stub
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool StubsManager_v7::visitEdge(LinkGraph &G, Block *B, Edge &E) {
+ if (!needsStub(E))
+ return false;
+
+ // Stub Arm/Thumb follows instruction set state at relocation site.
+ // TODO: We may reduce them at relaxation time and reuse freed slots.
+ bool MakeThumb = (E.getKind() > LastArmRelocation);
+ LLVM_DEBUG(dbgs() << " Preparing " << (MakeThumb ? "Thumb" : "Arm")
+ << " stub for " << G.getEdgeKindName(E.getKind())
+ << " edge at " << B->getFixupAddress(E) << " ("
+ << B->getAddress() << " + "
+ << formatv("{0:x}", E.getOffset()) << ")\n");
+
+ Symbol &Target = E.getTarget();
+ assert(Target.hasName() && "Edge cannot point to anonymous target");
+ Symbol *&StubSymbol = getStubSymbolSlot(Target.getName(), MakeThumb);
+
+ if (!StubSymbol) {
+ if (!StubsSection)
+ StubsSection = &G.createSection(getSectionName(),
+ orc::MemProt::Read | orc::MemProt::Exec);
+ Block &B = MakeThumb ? createStubThumbv7(G, *StubsSection, Target)
+ : createStubArmv7(G, *StubsSection, Target);
+ StubSymbol = &G.addAnonymousSymbol(B, 0, B.getSize(), true, false);
+ if (MakeThumb)
+ StubSymbol->setTargetFlags(ThumbSymbol);
+
+ LLVM_DEBUG({
+ dbgs() << " Created " << (MakeThumb ? "Thumb" : "Arm") << " entry for "
+ << Target.getName() << " in " << StubsSection->getName() << ": "
+ << *StubSymbol << "\n";
+ });
+ }
+
+ assert(MakeThumb == (StubSymbol->getTargetFlags() & ThumbSymbol) &&
+ "Instruction set states of stub and relocation site should be equal");
+ LLVM_DEBUG({
+ dbgs() << " Using " << (MakeThumb ? "Thumb" : "Arm") << " entry "
+ << *StubSymbol << " in "
+ << StubSymbol->getBlock().getSection().getName() << "\n";
+ });
+
+ E.setTarget(*StubSymbol);
+ return true;
}
const char *getEdgeKindName(Edge::Kind K) {
diff --git a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_arm.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_arm.s
new file mode 100644
index 000000000000000..fb2e0eb2c0bf249
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_arm.s
@@ -0,0 +1,53 @@
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=armv7-linux-gnueabi -arm-add-build-attributes \
+# RUN: -filetype=obj -o %t/out.o %s
+# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 \
+# RUN: -slab-allocate 10Kb -slab-page-size 4096 \
+# RUN: -abs ext=0x76bbe880 \
+# RUN: -check %s %t/out.o
+
+ .text
+ .syntax unified
+
+# Check that calls/jumps to external functions trigger the generation of
+# branch-range extension stubs. These stubs don't follow the default PLT model
+# where the branch-target address is loaded from a GOT entry. Instead, they
+# hard-code it in the immediate field.
+
+# The external function ext will return to the caller directly.
+# jitlink-check: decode_operand(test_arm_jump, 0) = stub_addr(out.o, ext) - (test_arm_jump + 8)
+ .globl test_arm_jump
+ .type test_arm_jump,%function
+ .p2align 2
+test_arm_jump:
+ b ext
+ .size test_arm_jump, .-test_arm_jump
+
+# The branch-with-link sets the LR register so that the external function ext
+# returns to us. We have to save the register (push) and return to main manually
+# (pop). This adds the +4 offset for the bl instruction we decode:
+# jitlink-check: decode_operand(test_arm_call + 4, 0) = stub_addr(out.o, ext) - (test_arm_call + 8) - 4
+ .globl test_arm_call
+ .type test_arm_call,%function
+ .p2align 2
+test_arm_call:
+ push {lr}
+ bl ext
+ pop {pc}
+ .size test_arm_call, .-test_arm_call
+
+# This test is executable with both, Arm and Thumb `ext` functions. It only has
+# to return with `bx lr`. For example:
+# > echo "void ext() {}" | clang -target armv7-linux-gnueabihf -o ext-arm.o -c -xc -
+# > llvm-jitlink ext-arm.o out.o
+#
+ .globl main
+ .type main,%function
+ .p2align 2
+main:
+ push {lr}
+ bl test_arm_call
+ bl test_arm_jump
+ movw r0, #0
+ pop {pc}
+ .size main, .-main
diff --git a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_multi.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_multi.s
new file mode 100644
index 000000000000000..d575f114dcba1e7
--- /dev/null
+++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_stubs_multi.s
@@ -0,0 +1,50 @@
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: llvm-mc -triple=armv7-linux-gnueabi -arm-add-build-attributes \
+# RUN: -filetype=obj -o %t/out.o %s
+# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 \
+# RUN: -slab-allocate=10Kb -slab-page-size=4096 \
+# RUN: -abs ext=0x76bbe880 -check %s %t/out.o
+
+ .text
+ .syntax unified
+
+# Check that a single external symbol can have multiple stubs. We access them
+# with the extra stub-index argument to stub_addr(). Stubs are sorted by
+# ascending size (because the default memory manager lays out blocks by size).
+
+# Thumb relocation site emits thumb stub
+# jitlink-check: decode_operand(test_stub_thumb, 0) = stub_addr(out.o, ext, thumb) - (test_stub_thumb + 4)
+ .globl test_stub_thumb
+ .type test_stub_thumb,%function
+ .p2align 1
+ .code 16
+ .thumb_func
+test_stub_thumb:
+ b ext
+ .size test_stub_thumb, .-test_stub_thumb
+
+# Arm relocation site emits arm stub
+# jitlink-check: decode_operand(test_stub_arm, 0) = stub_addr(out.o, ext, arm) - (test_stub_arm + 8)
+ .globl test_stub_arm
+ .type test_stub_arm,%function
+ .p2align 2
+ .code 32
+test_stub_arm:
+ b ext
+ .size test_stub_arm, .-test_stub_arm
+
+# This test is executable with both, Arm and Thumb `ext` functions. It only has
+# to return (directly to main) with `bx lr`. For example:
+# > echo "void ext() {}" | clang -target armv7-linux-gnueabihf -o ext-arm.o -c -xc -
+# > llvm-jitlink ext-arm.o out.o
+#
+ .globl main
+ .type main,%function
+ .p2align 2
+main:
+ push {lr}
+ bl test_stub_arm
+ bl test_stub_thumb
+ movw r0, #0
+ pop {pc}
+ .size main, .-main
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index d233ebdb5a3a8dc..c0554108818f68b 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -1265,8 +1265,52 @@ Session::findSectionInfo(StringRef FileName, StringRef SectionName) {
return SecInfoItr->second;
}
+class MemoryMatcher {
+public:
+ MemoryMatcher(ArrayRef<char> Content)
+ : Pos(Content.data()), End(Pos + Content.size()) {}
+
+ template <typename MaskType> bool matchMask(MaskType Mask) {
+ if (Mask == (Mask & *reinterpret_cast<const MaskType *>(Pos))) {
+ Pos += sizeof(MaskType);
+ return true;
+ }
+ return false;
+ }
+
+ template <typename ValueType> bool matchEqual(ValueType Value) {
+ if (Value == *reinterpret_cast<const ValueType *>(Pos)) {
+ Pos += sizeof(ValueType);
+ return true;
+ }
+ return false;
+ }
+
+ bool done() const { return Pos == End; }
+
+private:
+ const char *Pos;
+ const char *End;
+};
+
static StringRef detectStubKind(const Session::MemoryRegionInfo &Stub) {
- // Implement acutal stub kind detection
+ constexpr uint32_t Armv7MovWTle = 0xe300c000;
+ constexpr uint32_t Armv7BxR12le = 0xe12fff1c;
+ constexpr uint32_t Thumbv7MovWTle = 0x0c00f240;
+ constexpr uint16_t Thumbv7BxR12le = 0x4760;
+
+ MemoryMatcher M(Stub.getContent());
+ if (M.matchMask(Thumbv7MovWTle)) {
+ if (M.matchMask(Thumbv7MovWTle))
+ if (M.matchEqual(Thumbv7BxR12le))
+ if (M.done())
+ return "thumbv7_abs_le";
+ } else if (M.matchMask(Armv7MovWTle)) {
+ if (M.matchMask(Armv7MovWTle))
+ if (M.matchEqual(Armv7BxR12le))
+ if (M.done())
+ return "armv7_abs_le";
+ }
return "";
}
>From 7215c81f693ec1caaac5006e1d269623a2068bc6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 20 Jan 2024 11:13:45 +0100
Subject: [PATCH 5/9] [clang-repl] Fix linker error on ARM
Error in gold linker is: PLT offset too large, try linking with --long-plt
---
clang/tools/clang-repl/CMakeLists.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang/tools/clang-repl/CMakeLists.txt b/clang/tools/clang-repl/CMakeLists.txt
index 2ccbe292fd49e09..8589e37418354e7 100644
--- a/clang/tools/clang-repl/CMakeLists.txt
+++ b/clang/tools/clang-repl/CMakeLists.txt
@@ -22,3 +22,7 @@ clang_target_link_libraries(clang-repl PRIVATE
if(CLANG_PLUGIN_SUPPORT)
export_executable_symbols_for_plugins(clang-repl)
endif()
+
+if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "ARM")
+ target_link_options(clang-repl PRIVATE LINKER:--long-plt)
+endif()
>From e1ce5b6c12f8c85c641d99ecc94a7ae02e4ce4f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 20 Jan 2024 16:12:46 +0100
Subject: [PATCH 6/9] [JITLink][AArch32] Implement R_ARM_TARGET1
Prepare a configuration switch anddefault to R_ARM_ABS32
---
llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h | 2 ++
llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp | 8 ++++++--
llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp | 7 ++++---
3 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index 725f9a7eeb88896..98525b9f69b97e2 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -135,6 +135,8 @@ enum class StubsFlavor {
struct ArmConfig {
bool J1J2BranchEncoding = false;
StubsFlavor Stubs = StubsFlavor::Unsupported;
+ // We might want a --target1-rel linker switch in the long term.
+ bool Target1Rel = false;
};
/// Obtain the sub-arch configuration for a given Arm CPU model.
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
index 2553cd70a576912..ed7d58da2dc111e 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
@@ -31,7 +31,8 @@ namespace llvm {
namespace jitlink {
/// Translate from ELF relocation type to JITLink-internal edge kind.
-Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType) {
+Expected<aarch32::EdgeKind_aarch32>
+getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &ArmCfg) {
switch (ELFType) {
case ELF::R_ARM_ABS32:
return aarch32::Data_Pointer32;
@@ -47,6 +48,9 @@ Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType) {
return aarch32::Arm_MovwAbsNC;
case ELF::R_ARM_MOVT_ABS:
return aarch32::Arm_MovtAbs;
+ case ELF::R_ARM_TARGET1:
+ return (ArmCfg.Target1Rel) ? aarch32::Data_Delta32
+ : aarch32::Data_Pointer32;
case ELF::R_ARM_THM_CALL:
return aarch32::Thumb_Call;
case ELF::R_ARM_THM_JUMP24:
@@ -171,7 +175,7 @@ class ELFLinkGraphBuilder_aarch32
inconvertibleErrorCode());
uint32_t Type = Rel.getType(false);
- Expected<aarch32::EdgeKind_aarch32> Kind = getJITLinkEdgeKind(Type);
+ Expected<aarch32::EdgeKind_aarch32> Kind = getJITLinkEdgeKind(Type, ArmCfg);
if (!Kind)
return Kind.takeError();
diff --git a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp
index 26c773b8dc3a715..46792e9e397b6ae 100644
--- a/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp
+++ b/llvm/unittests/ExecutionEngine/JITLink/AArch32Tests.cpp
@@ -39,7 +39,7 @@ struct MutableWord {
namespace llvm {
namespace jitlink {
-Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType);
+Expected<aarch32::EdgeKind_aarch32> getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &Cfg);
Expected<uint32_t> getELFRelocationType(Edge::Kind Kind);
} // namespace jitlink
@@ -47,7 +47,8 @@ Expected<uint32_t> getELFRelocationType(Edge::Kind Kind);
TEST(AArch32_ELF, EdgeKinds) {
// Fails: Invalid ELF type -> JITLink kind
- Expected<uint32_t> ErrKind = getJITLinkEdgeKind(ELF::R_ARM_NONE);
+ aarch32::ArmConfig Cfg;
+ Expected<uint32_t> ErrKind = getJITLinkEdgeKind(ELF::R_ARM_NONE, Cfg);
EXPECT_TRUE(errorToBool(ErrKind.takeError()));
// Fails: Invalid JITLink kind -> ELF type
@@ -59,7 +60,7 @@ TEST(AArch32_ELF, EdgeKinds) {
EXPECT_FALSE(errorToBool(ELFType.takeError()))
<< "Failed to translate JITLink kind -> ELF type";
- Expected<Edge::Kind> JITLinkKind = getJITLinkEdgeKind(*ELFType);
+ Expected<Edge::Kind> JITLinkKind = getJITLinkEdgeKind(*ELFType, Cfg);
EXPECT_FALSE(errorToBool(JITLinkKind.takeError()))
<< "Failed to translate ELF type -> JITLink kind";
>From 14ec373340abce8655f59e85cc1dfe3c37b13a61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 20 Jan 2024 18:06:14 +0100
Subject: [PATCH 7/9] [JITLink][AArch32] Implement R_ARM_NONE
---
llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h | 12 +++++++++---
llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp | 4 ++++
llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 1 +
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index 98525b9f69b97e2..7be0380d4a7c3e7 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -101,7 +101,11 @@ enum EdgeKind_aarch32 : Edge::Kind {
Thumb_MovtPrel,
LastThumbRelocation = Thumb_MovtPrel,
- LastRelocation = LastThumbRelocation,
+
+ /// No-op relocation
+ None,
+
+ LastRelocation = None,
};
/// Flags enum for AArch32-specific symbol properties
@@ -293,7 +297,8 @@ inline Expected<int64_t> readAddend(LinkGraph &G, Block &B,
if (Kind <= LastThumbRelocation)
return readAddendThumb(G, B, Offset, Kind, ArmCfg);
- llvm_unreachable("Relocation must be of class Data, Arm or Thumb");
+ assert(Kind == None && "Not associated with a relocation class");
+ return 0;
}
/// Helper function to apply the fixup for Data-class relocations.
@@ -320,7 +325,8 @@ inline Error applyFixup(LinkGraph &G, Block &B, const Edge &E,
if (Kind <= LastThumbRelocation)
return applyFixupThumb(G, B, E, ArmCfg);
- llvm_unreachable("Relocation must be of class Data, Arm or Thumb");
+ assert(Kind == None && "Not associated with a relocation class");
+ return Error::success();
}
/// Populate a Global Offset Table from edges that request it.
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
index ed7d58da2dc111e..15c209e1ebe5bf6 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
@@ -48,6 +48,8 @@ getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &ArmCfg) {
return aarch32::Arm_MovwAbsNC;
case ELF::R_ARM_MOVT_ABS:
return aarch32::Arm_MovtAbs;
+ case ELF::R_ARM_NONE:
+ return aarch32::None;
case ELF::R_ARM_TARGET1:
return (ArmCfg.Target1Rel) ? aarch32::Data_Delta32
: aarch32::Data_Pointer32;
@@ -99,6 +101,8 @@ Expected<uint32_t> getELFRelocationType(Edge::Kind Kind) {
return ELF::R_ARM_THM_MOVW_PREL_NC;
case aarch32::Thumb_MovtPrel:
return ELF::R_ARM_THM_MOVT_PREL;
+ case aarch32::None:
+ return ELF::R_ARM_NONE;
}
return make_error<JITLinkError>(formatv("Invalid aarch32 edge {0:d}: ",
diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
index 7f7b460b4b36968..9508cde07b42a65 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -867,6 +867,7 @@ const char *getEdgeKindName(Edge::Kind K) {
KIND_NAME_CASE(Thumb_MovtAbs)
KIND_NAME_CASE(Thumb_MovwPrelNC)
KIND_NAME_CASE(Thumb_MovtPrel)
+ KIND_NAME_CASE(None)
default:
return getGenericEdgeKindName(K);
}
>From 1fc8d948dd7728810d1ff579a4e0ef087a1b3519 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Sat, 20 Jan 2024 16:00:51 +0100
Subject: [PATCH 8/9] [JITLink][AArch32] Implement R_ARM_PREL31 and stop
skipping .ARM.exidx sections (wip)
---
.../llvm/ExecutionEngine/JITLink/aarch32.h | 2 ++
.../ExecutionEngine/JITLink/ELF_aarch32.cpp | 13 ++++++++
llvm/lib/ExecutionEngine/JITLink/aarch32.cpp | 21 +++++++++++--
.../JITLink/AArch32/ELF_relocations_data.s | 30 +++++++++++++++++++
4 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
index 7be0380d4a7c3e7..dc75394c7283672 100644
--- a/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
+++ b/llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h
@@ -41,6 +41,8 @@ enum EdgeKind_aarch32 : Edge::Kind {
/// Absolute 32-bit value relocation
Data_Pointer32,
+ Data_PRel31,
+
/// Create GOT entry and store offset
Data_RequestGOTAndTransformToDelta32,
diff --git a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
index 15c209e1ebe5bf6..563dc498d7737eb 100644
--- a/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/ELF_aarch32.cpp
@@ -50,6 +50,8 @@ getJITLinkEdgeKind(uint32_t ELFType, const aarch32::ArmConfig &ArmCfg) {
return aarch32::Arm_MovtAbs;
case ELF::R_ARM_NONE:
return aarch32::None;
+ case ELF::R_ARM_PREL31:
+ return aarch32::Data_PRel31;
case ELF::R_ARM_TARGET1:
return (ArmCfg.Target1Rel) ? aarch32::Data_Delta32
: aarch32::Data_Pointer32;
@@ -79,6 +81,8 @@ Expected<uint32_t> getELFRelocationType(Edge::Kind Kind) {
return ELF::R_ARM_REL32;
case aarch32::Data_Pointer32:
return ELF::R_ARM_ABS32;
+ case aarch32::Data_PRel31:
+ return ELF::R_ARM_PREL31;
case aarch32::Data_RequestGOTAndTransformToDelta32:
return ELF::R_ARM_GOT_PREL;
case aarch32::Arm_Call:
@@ -147,6 +151,15 @@ class ELFLinkGraphBuilder_aarch32
// Handling ABI for the Arm® Architecture -> Index table entries
if (Sect.sh_type == ELF::SHT_ARM_EXIDX)
return true;
+ // Skip .ARM.extab sections. They are only ever referenced from .ARM.exidx,
+ // i.e. when unwind instructions don't fit into 4 bytes.
+ if (Sect.sh_type == ELF::SHT_PROGBITS && (Sect.sh_flags & ELF::SHF_ALLOC)) {
+ if (Sect.sh_name && Sect.sh_name < Base::SectionStringTab.size()) {
+ StringRef SectionName = Base::SectionStringTab.data() + Sect.sh_name;
+ if (SectionName.starts_with(".ARM.extab"))
+ return true;
+ }
+ }
return false;
}
diff --git a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
index 9508cde07b42a65..96dff7656f17d63 100644
--- a/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/aarch32.cpp
@@ -398,6 +398,8 @@ Expected<int64_t> readAddendData(LinkGraph &G, Block &B, Edge::OffsetT Offset,
case Data_Pointer32:
case Data_RequestGOTAndTransformToDelta32:
return SignExtend64<32>(support::endian::read32(FixupPtr, Endian));
+ case Data_PRel31:
+ return SignExtend64<31>(support::endian::read32(FixupPtr, Endian));
default:
return make_error<JITLinkError>(
"In graph " + G.getName() + ", section " + B.getSection().getName() +
@@ -472,9 +474,8 @@ Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
Symbol &TargetSymbol = E.getTarget();
uint64_t TargetAddress = TargetSymbol.getAddress().getValue();
- // 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
- // exceptions: R_ARM_ABS8, R_ARM_ABS16, R_ARM_PREL31
+ // Data relocations have alignment 1, size 4 (except R_ARM_ABS8 and
+ // R_ARM_ABS16) and write the full 32-bit result (except R_ARM_PREL31).
switch (Kind) {
case Data_Delta32: {
int64_t Value = TargetAddress - FixupAddress + Addend;
@@ -496,6 +497,19 @@ Error applyFixupData(LinkGraph &G, Block &B, const Edge &E) {
endian::write32be(FixupPtr, Value);
return Error::success();
}
+ case Data_PRel31: {
+ int64_t Value = TargetAddress - FixupAddress + Addend;
+ if (!isInt<31>(Value))
+ return makeTargetOutOfRangeError(G, B, E);
+ if (LLVM_LIKELY(G.getEndianness() == endianness::little)) {
+ uint32_t MSB = endian::read32le(FixupPtr) & 0x80000000;
+ endian::write32le(FixupPtr, MSB | (Value & ~0x80000000));
+ } else {
+ uint32_t MSB = endian::read32be(FixupPtr) & 0x80000000;
+ endian::write32be(FixupPtr, MSB | (Value & ~0x80000000));
+ }
+ return Error::success();
+ }
case Data_RequestGOTAndTransformToDelta32:
llvm_unreachable("Should be transformed");
default:
@@ -856,6 +870,7 @@ const char *getEdgeKindName(Edge::Kind K) {
switch (K) {
KIND_NAME_CASE(Data_Delta32)
KIND_NAME_CASE(Data_Pointer32)
+ KIND_NAME_CASE(Data_PRel31)
KIND_NAME_CASE(Data_RequestGOTAndTransformToDelta32)
KIND_NAME_CASE(Arm_Call)
KIND_NAME_CASE(Arm_Jump24)
diff --git a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
index f91a4733c40eee0..82092965d5bb307 100644
--- a/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
+++ b/llvm/test/ExecutionEngine/JITLink/AArch32/ELF_relocations_data.s
@@ -57,6 +57,35 @@ got_prel_offset:
.size got_prel_offset, .-got_prel_offset
.size got_prel, .-got_prel
+# Dummy personality routine. It will be referenced from .ARM.exidx
+# CHECK-TYPE: {{[0-9a-f]+}} R_ARM_NONE __aeabi_unwind_cpp_pr0
+ .globl __aeabi_unwind_cpp_pr0
+ .type __aeabi_unwind_cpp_pr0,%function
+ .align 2
+__aeabi_unwind_cpp_pr0:
+ bx lr
+
+# CHECK-TYPE: {{[0-9a-f]+}} R_ARM_PREL31 .text
+#
+# Used in the .ARM.exidx exception tables. The linker must preserve the most-
+# significant bit. It denotes whether the field is an inline entry (1) or a
+# relocation (0).
+#
+# jitlink-check: *{4}(section_addr(out.o, .ARM.exidx)) = (prel31 - (section_addr(out.o, .ARM.exidx))) & 0x7fffffff
+ .globl prel31
+ .type prel31,%function
+ .align 2
+prel31:
+ .fnstart
+ .save {r11, lr}
+ push {r11, lr}
+ .setfp r11, sp
+ mov r11, sp
+ pop {r11, lr}
+ mov pc, lr
+ .size prel31,.-prel31
+ .fnend
+
# This test is executable with any 4-byte external target:
# > echo "unsigned target = 42;" | clang -target armv7-linux-gnueabihf -o target.o -c -xc -
# > llvm-jitlink target.o armv7/out.o
@@ -67,5 +96,6 @@ got_prel_offset:
main:
push {lr}
bl got_prel
+ bl prel31
pop {pc}
.size main, .-main
>From b7431fc5f62147e48261e5d0fcfa7c390efb855c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 8 Jan 2024 15:06:33 +0100
Subject: [PATCH 9/9] [Orc] Make JITLink default in LLJIT for ELF-based ARM
targets
---
llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp
index e259c393d07e031..833dcb9d5bf2e72 100644
--- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp
@@ -734,6 +734,12 @@ Error LLJITBuilderState::prepareForConstruction() {
case Triple::aarch64:
UseJITLink = !TT.isOSBinFormatCOFF();
break;
+ case Triple::arm:
+ case Triple::armeb:
+ case Triple::thumb:
+ case Triple::thumbeb:
+ UseJITLink = TT.isOSBinFormatELF();
+ break;
case Triple::x86_64:
UseJITLink = !TT.isOSBinFormatCOFF();
break;
More information about the llvm-commits
mailing list