Skip to content

Commit e5ca202

Browse files
[JITLink][AArch32] Multi-stub support for armv7/thumbv7 (#78371)
We want to emit stubs that match the instruction set state of the relocation site. This is important for branches that have no built-in switch for the instruction set state. It's the case for Jump24 relocations. Relocations on instructions that support switching on the fly will be rewritten in a relaxation step in the future. This affects Call relocations on `BL`/`BLX` instructions. In this patch, the StubManager gains a second stub symbol slot for each target and selects which one to use based on the relocation type. For testing, we select the appropriate slot with a stub-kind filter, i.e. `arm` or `thumb`. With that we can implement Armv7 stubs and test that we can have both kinds of stubs for a single external symbol.
1 parent b8e708b commit e5ca202

File tree

5 files changed

+274
-59
lines changed

5 files changed

+274
-59
lines changed

llvm/include/llvm/ExecutionEngine/JITLink/aarch32.h

+13-46
Original file line numberDiff line numberDiff line change
@@ -341,64 +341,31 @@ class GOTBuilder : public TableManager<GOTBuilder> {
341341
Section *GOTSection = nullptr;
342342
};
343343

344-
/// Stubs builder for v7 emits non-position-independent Thumb stubs.
345-
///
346-
/// Right now we only have one default stub kind, but we want to extend this
347-
/// and allow creation of specific kinds in the future (e.g. branch range
348-
/// extension or interworking).
349-
///
350-
/// Let's keep it simple for the moment and not wire this through a GOT.
351-
///
352-
class StubsManager_v7 : public TableManager<StubsManager_v7> {
344+
/// Stubs builder for v7 emits non-position-independent Arm and Thumb stubs.
345+
class StubsManager_v7 {
353346
public:
354347
StubsManager_v7() = default;
355348

356349
/// Name of the object file section that will contain all our stubs.
357350
static StringRef getSectionName() {
358-
return "__llvm_jitlink_aarch32_STUBS_Thumbv7";
351+
return "__llvm_jitlink_aarch32_STUBS_v7";
359352
}
360353

361354
/// Implements link-graph traversal via visitExistingEdges().
362-
bool visitEdge(LinkGraph &G, Block *B, Edge &E) {
363-
if (E.getTarget().isDefined())
364-
return false;
365-
366-
switch (E.getKind()) {
367-
case Thumb_Call:
368-
case Thumb_Jump24: {
369-
DEBUG_WITH_TYPE("jitlink", {
370-
dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at "
371-
<< B->getFixupAddress(E) << " (" << B->getAddress() << " + "
372-
<< formatv("{0:x}", E.getOffset()) << ")\n";
373-
});
374-
E.setTarget(this->getEntryForTarget(G, E.getTarget()));
375-
return true;
376-
}
377-
}
378-
return false;
379-
}
380-
381-
/// Create a branch range extension stub with Thumb encoding for v7 CPUs.
382-
Symbol &createEntry(LinkGraph &G, Symbol &Target);
355+
bool visitEdge(LinkGraph &G, Block *B, Edge &E);
383356

384357
private:
385-
/// Create a new node in the link-graph for the given stub template.
386-
template <size_t Size>
387-
Block &addStub(LinkGraph &G, const uint8_t (&Code)[Size],
388-
uint64_t Alignment) {
389-
ArrayRef<char> Template(reinterpret_cast<const char *>(Code), Size);
390-
return G.createContentBlock(getStubsSection(G), Template,
391-
orc::ExecutorAddr(), Alignment, 0);
392-
}
393-
394-
/// Get or create the object file section that will contain all our stubs.
395-
Section &getStubsSection(LinkGraph &G) {
396-
if (!StubsSection)
397-
StubsSection = &G.createSection(getSectionName(),
398-
orc::MemProt::Read | orc::MemProt::Exec);
399-
return *StubsSection;
358+
// Two slots per external: Arm and Thumb
359+
using StubMapEntry = std::tuple<Symbol *, Symbol *>;
360+
361+
Symbol *&getStubSymbolSlot(StringRef Name, bool Thumb) {
362+
StubMapEntry &Stubs = StubMap.try_emplace(Name).first->second;
363+
if (Thumb)
364+
return std::get<1>(Stubs);
365+
return std::get<0>(Stubs);
400366
}
401367

368+
DenseMap<StringRef, StubMapEntry> StubMap;
402369
Section *StubsSection = nullptr;
403370
};
404371

llvm/lib/ExecutionEngine/JITLink/aarch32.cpp

+113-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "llvm/ADT/StringExtras.h"
1616
#include "llvm/BinaryFormat/ELF.h"
1717
#include "llvm/ExecutionEngine/JITLink/JITLink.h"
18+
#include "llvm/ExecutionEngine/Orc/Shared/MemoryFlags.h"
1819
#include "llvm/Object/ELFObjectFile.h"
1920
#include "llvm/Support/Endian.h"
2021
#include "llvm/Support/ManagedStatic.h"
@@ -724,27 +725,127 @@ bool GOTBuilder::visitEdge(LinkGraph &G, Block *B, Edge &E) {
724725
return true;
725726
}
726727

728+
const uint8_t Armv7ABS[] = {
729+
0x00, 0xc0, 0x00, 0xe3, // movw r12, #0x0000 ; lower 16-bit
730+
0x00, 0xc0, 0x40, 0xe3, // movt r12, #0x0000 ; upper 16-bit
731+
0x1c, 0xff, 0x2f, 0xe1 // bx r12
732+
};
733+
727734
const uint8_t Thumbv7ABS[] = {
728735
0x40, 0xf2, 0x00, 0x0c, // movw r12, #0x0000 ; lower 16-bit
729736
0xc0, 0xf2, 0x00, 0x0c, // movt r12, #0x0000 ; upper 16-bit
730737
0x60, 0x47 // bx r12
731738
};
732739

733-
Symbol &StubsManager_v7::createEntry(LinkGraph &G, Symbol &Target) {
740+
/// Create a new node in the link-graph for the given stub template.
741+
template <size_t Size>
742+
static Block &allocStub(LinkGraph &G, Section &S, const uint8_t (&Code)[Size]) {
734743
constexpr uint64_t Alignment = 4;
735-
Block &B = addStub(G, Thumbv7ABS, Alignment);
736-
LLVM_DEBUG({
737-
const char *StubPtr = B.getContent().data();
738-
HalfWords Reg12 = encodeRegMovtT1MovwT3(12);
739-
assert(checkRegister<Thumb_MovwAbsNC>(StubPtr, Reg12) &&
740-
checkRegister<Thumb_MovtAbs>(StubPtr + 4, Reg12) &&
741-
"Linker generated stubs may only corrupt register r12 (IP)");
742-
});
744+
ArrayRef<char> Template(reinterpret_cast<const char *>(Code), Size);
745+
return G.createContentBlock(S, Template, orc::ExecutorAddr(), Alignment, 0);
746+
}
747+
748+
static Block &createStubThumbv7(LinkGraph &G, Section &S, Symbol &Target) {
749+
Block &B = allocStub(G, S, Thumbv7ABS);
743750
B.addEdge(Thumb_MovwAbsNC, 0, Target, 0);
744751
B.addEdge(Thumb_MovtAbs, 4, Target, 0);
745-
Symbol &Stub = G.addAnonymousSymbol(B, 0, B.getSize(), true, false);
746-
Stub.setTargetFlags(ThumbSymbol);
747-
return Stub;
752+
753+
[[maybe_unused]] const char *StubPtr = B.getContent().data();
754+
[[maybe_unused]] HalfWords Reg12 = encodeRegMovtT1MovwT3(12);
755+
assert(checkRegister<Thumb_MovwAbsNC>(StubPtr, Reg12) &&
756+
checkRegister<Thumb_MovtAbs>(StubPtr + 4, Reg12) &&
757+
"Linker generated stubs may only corrupt register r12 (IP)");
758+
return B;
759+
}
760+
761+
static Block &createStubArmv7(LinkGraph &G, Section &S, Symbol &Target) {
762+
Block &B = allocStub(G, S, Armv7ABS);
763+
B.addEdge(Arm_MovwAbsNC, 0, Target, 0);
764+
B.addEdge(Arm_MovtAbs, 4, Target, 0);
765+
766+
[[maybe_unused]] const char *StubPtr = B.getContent().data();
767+
[[maybe_unused]] uint32_t Reg12 = encodeRegMovtA1MovwA2(12);
768+
assert(checkRegister<Arm_MovwAbsNC>(StubPtr, Reg12) &&
769+
checkRegister<Arm_MovtAbs>(StubPtr + 4, Reg12) &&
770+
"Linker generated stubs may only corrupt register r12 (IP)");
771+
return B;
772+
}
773+
774+
static bool needsStub(const Edge &E) {
775+
Symbol &Target = E.getTarget();
776+
777+
// Create stubs for external branch targets.
778+
if (!Target.isDefined()) {
779+
switch (E.getKind()) {
780+
case Arm_Call:
781+
case Arm_Jump24:
782+
case Thumb_Call:
783+
case Thumb_Jump24:
784+
return true;
785+
default:
786+
return false;
787+
}
788+
}
789+
790+
// For local targets, create interworking stubs if we switch Arm/Thumb with an
791+
// instruction that cannot switch the instruction set state natively.
792+
bool TargetIsThumb = Target.getTargetFlags() & ThumbSymbol;
793+
switch (E.getKind()) {
794+
case Arm_Jump24:
795+
return TargetIsThumb; // Branch to Thumb needs interworking stub
796+
case Thumb_Jump24:
797+
return !TargetIsThumb; // Branch to Arm needs interworking stub
798+
default:
799+
break;
800+
}
801+
802+
return false;
803+
}
804+
805+
bool StubsManager_v7::visitEdge(LinkGraph &G, Block *B, Edge &E) {
806+
if (!needsStub(E))
807+
return false;
808+
809+
// Stub Arm/Thumb follows instruction set state at relocation site.
810+
// TODO: We may reduce them at relaxation time and reuse freed slots.
811+
bool MakeThumb = (E.getKind() > LastArmRelocation);
812+
LLVM_DEBUG(dbgs() << " Preparing " << (MakeThumb ? "Thumb" : "Arm")
813+
<< " stub for " << G.getEdgeKindName(E.getKind())
814+
<< " edge at " << B->getFixupAddress(E) << " ("
815+
<< B->getAddress() << " + "
816+
<< formatv("{0:x}", E.getOffset()) << ")\n");
817+
818+
Symbol &Target = E.getTarget();
819+
assert(Target.hasName() && "Edge cannot point to anonymous target");
820+
Symbol *&StubSymbol = getStubSymbolSlot(Target.getName(), MakeThumb);
821+
822+
if (!StubSymbol) {
823+
if (!StubsSection)
824+
StubsSection = &G.createSection(getSectionName(),
825+
orc::MemProt::Read | orc::MemProt::Exec);
826+
Block &B = MakeThumb ? createStubThumbv7(G, *StubsSection, Target)
827+
: createStubArmv7(G, *StubsSection, Target);
828+
StubSymbol = &G.addAnonymousSymbol(B, 0, B.getSize(), true, false);
829+
if (MakeThumb)
830+
StubSymbol->setTargetFlags(ThumbSymbol);
831+
832+
LLVM_DEBUG({
833+
dbgs() << " Created " << (MakeThumb ? "Thumb" : "Arm") << " entry for "
834+
<< Target.getName() << " in " << StubsSection->getName() << ": "
835+
<< *StubSymbol << "\n";
836+
});
837+
}
838+
839+
assert(MakeThumb == (StubSymbol->getTargetFlags() & ThumbSymbol) &&
840+
"Instruction set states of stub and relocation site should be equal");
841+
LLVM_DEBUG({
842+
dbgs() << " Using " << (MakeThumb ? "Thumb" : "Arm") << " entry "
843+
<< *StubSymbol << " in "
844+
<< StubSymbol->getBlock().getSection().getName() << "\n";
845+
});
846+
847+
E.setTarget(*StubSymbol);
848+
return true;
748849
}
749850

750851
const char *getEdgeKindName(Edge::Kind K) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# RUN: rm -rf %t && mkdir -p %t
2+
# RUN: llvm-mc -triple=armv7-linux-gnueabi -arm-add-build-attributes \
3+
# RUN: -filetype=obj -o %t/out.o %s
4+
# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 \
5+
# RUN: -slab-allocate 10Kb -slab-page-size 4096 \
6+
# RUN: -abs ext=0x76bbe880 \
7+
# RUN: -check %s %t/out.o
8+
9+
.text
10+
.syntax unified
11+
12+
# Check that calls/jumps to external functions trigger the generation of
13+
# branch-range extension stubs. These stubs don't follow the default PLT model
14+
# where the branch-target address is loaded from a GOT entry. Instead, they
15+
# hard-code it in the immediate field.
16+
17+
# The external function ext will return to the caller directly.
18+
# jitlink-check: decode_operand(test_arm_jump, 0) = stub_addr(out.o, ext) - (test_arm_jump + 8)
19+
.globl test_arm_jump
20+
.type test_arm_jump,%function
21+
.p2align 2
22+
test_arm_jump:
23+
b ext
24+
.size test_arm_jump, .-test_arm_jump
25+
26+
# The branch-with-link sets the LR register so that the external function ext
27+
# returns to us. We have to save the register (push) and return to main manually
28+
# (pop). This adds the +4 offset for the bl instruction we decode:
29+
# jitlink-check: decode_operand(test_arm_call + 4, 0) = stub_addr(out.o, ext) - (test_arm_call + 8) - 4
30+
.globl test_arm_call
31+
.type test_arm_call,%function
32+
.p2align 2
33+
test_arm_call:
34+
push {lr}
35+
bl ext
36+
pop {pc}
37+
.size test_arm_call, .-test_arm_call
38+
39+
# This test is executable with both, Arm and Thumb `ext` functions. It only has
40+
# to return with `bx lr`. For example:
41+
# > echo "void ext() {}" | clang -target armv7-linux-gnueabihf -o ext-arm.o -c -xc -
42+
# > llvm-jitlink ext-arm.o out.o
43+
#
44+
.globl main
45+
.type main,%function
46+
.p2align 2
47+
main:
48+
push {lr}
49+
bl test_arm_call
50+
bl test_arm_jump
51+
movw r0, #0
52+
pop {pc}
53+
.size main, .-main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# RUN: rm -rf %t && mkdir -p %t
2+
# RUN: llvm-mc -triple=armv7-linux-gnueabi -arm-add-build-attributes \
3+
# RUN: -filetype=obj -o %t/out.o %s
4+
# RUN: llvm-jitlink -noexec -slab-address 0x76ff0000 \
5+
# RUN: -slab-allocate=10Kb -slab-page-size=4096 \
6+
# RUN: -abs ext=0x76bbe880 -check %s %t/out.o
7+
8+
.text
9+
.syntax unified
10+
11+
# Check that a single external symbol can have multiple stubs. We access them
12+
# with the extra stub-index argument to stub_addr(). Stubs are sorted by
13+
# ascending size (because the default memory manager lays out blocks by size).
14+
15+
# Thumb relocation site emits thumb stub
16+
# jitlink-check: decode_operand(test_stub_thumb, 0) = stub_addr(out.o, ext, thumb) - (test_stub_thumb + 4)
17+
.globl test_stub_thumb
18+
.type test_stub_thumb,%function
19+
.p2align 1
20+
.code 16
21+
.thumb_func
22+
test_stub_thumb:
23+
b ext
24+
.size test_stub_thumb, .-test_stub_thumb
25+
26+
# Arm relocation site emits arm stub
27+
# jitlink-check: decode_operand(test_stub_arm, 0) = stub_addr(out.o, ext, arm) - (test_stub_arm + 8)
28+
.globl test_stub_arm
29+
.type test_stub_arm,%function
30+
.p2align 2
31+
.code 32
32+
test_stub_arm:
33+
b ext
34+
.size test_stub_arm, .-test_stub_arm
35+
36+
# This test is executable with both, Arm and Thumb `ext` functions. It only has
37+
# to return (directly to main) with `bx lr`. For example:
38+
# > echo "void ext() {}" | clang -target armv7-linux-gnueabihf -o ext-arm.o -c -xc -
39+
# > llvm-jitlink ext-arm.o out.o
40+
#
41+
.globl main
42+
.type main,%function
43+
.p2align 2
44+
main:
45+
push {lr}
46+
bl test_stub_arm
47+
bl test_stub_thumb
48+
movw r0, #0
49+
pop {pc}
50+
.size main, .-main

llvm/tools/llvm-jitlink/llvm-jitlink.cpp

+45-1
Original file line numberDiff line numberDiff line change
@@ -1265,8 +1265,52 @@ Session::findSectionInfo(StringRef FileName, StringRef SectionName) {
12651265
return SecInfoItr->second;
12661266
}
12671267

1268+
class MemoryMatcher {
1269+
public:
1270+
MemoryMatcher(ArrayRef<char> Content)
1271+
: Pos(Content.data()), End(Pos + Content.size()) {}
1272+
1273+
template <typename MaskType> bool matchMask(MaskType Mask) {
1274+
if (Mask == (Mask & *reinterpret_cast<const MaskType *>(Pos))) {
1275+
Pos += sizeof(MaskType);
1276+
return true;
1277+
}
1278+
return false;
1279+
}
1280+
1281+
template <typename ValueType> bool matchEqual(ValueType Value) {
1282+
if (Value == *reinterpret_cast<const ValueType *>(Pos)) {
1283+
Pos += sizeof(ValueType);
1284+
return true;
1285+
}
1286+
return false;
1287+
}
1288+
1289+
bool done() const { return Pos == End; }
1290+
1291+
private:
1292+
const char *Pos;
1293+
const char *End;
1294+
};
1295+
12681296
static StringRef detectStubKind(const Session::MemoryRegionInfo &Stub) {
1269-
// Implement acutal stub kind detection
1297+
constexpr uint32_t Armv7MovWTle = 0xe300c000;
1298+
constexpr uint32_t Armv7BxR12le = 0xe12fff1c;
1299+
constexpr uint32_t Thumbv7MovWTle = 0x0c00f240;
1300+
constexpr uint16_t Thumbv7BxR12le = 0x4760;
1301+
1302+
MemoryMatcher M(Stub.getContent());
1303+
if (M.matchMask(Thumbv7MovWTle)) {
1304+
if (M.matchMask(Thumbv7MovWTle))
1305+
if (M.matchEqual(Thumbv7BxR12le))
1306+
if (M.done())
1307+
return "thumbv7_abs_le";
1308+
} else if (M.matchMask(Armv7MovWTle)) {
1309+
if (M.matchMask(Armv7MovWTle))
1310+
if (M.matchEqual(Armv7BxR12le))
1311+
if (M.done())
1312+
return "armv7_abs_le";
1313+
}
12701314
return "";
12711315
}
12721316

0 commit comments

Comments
 (0)