Skip to content

Commit e69ce41

Browse files
[BOLT] Skip flushing optional out-of-range pending relocs
When a pending relocation is created it is also marked whether it is optional or not. It can be optional when such relocation is added as part of an optimization (i.e., `scanExternalRefs`). When bolt tries to `flushPendingRelocations`, it safely skips any optional relocations that cannot be encoded. Background: BOLT, as part of scanExternalRefs, identifies external references from calls and creates some pending relocations for them. Those when flushed will update references to point to the optimized functions. This optimization can be disabled using `--no-scan`. BOLT can assert if any of these pending relocations cannot be encoded. This patch does not disable this optimization but instead selectively applies it given that a pending relocation is optional.
1 parent a6dbcb8 commit e69ce41

File tree

6 files changed

+73
-17
lines changed

6 files changed

+73
-17
lines changed

bolt/include/bolt/Core/BinarySection.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@ class BinarySection {
6666
// from the original section address.
6767
RelocationSetType DynamicRelocations;
6868

69-
// Pending relocations for this section.
70-
std::vector<Relocation> PendingRelocations;
69+
/// Pending relocations for this section and whether they are optional, i.e.,
70+
/// added as part of an optimization. In that case they can be safely omitted
71+
/// if flushPendingRelocations discovers they cannot be encoded.
72+
std::vector<std::pair<Relocation, bool>> PendingRelocations;
7173

7274
struct BinaryPatch {
7375
uint64_t Offset;
@@ -375,9 +377,10 @@ class BinarySection {
375377
DynamicRelocations.emplace(Reloc);
376378
}
377379

378-
/// Add relocation against the original contents of this section.
379-
void addPendingRelocation(const Relocation &Rel) {
380-
PendingRelocations.push_back(Rel);
380+
/// Add relocation against the original contents of this section. When added
381+
/// as part of an optimization it is marked as \p Optional.
382+
void addPendingRelocation(const Relocation &Rel, bool Optional = false) {
383+
PendingRelocations.push_back({Rel, Optional});
381384
}
382385

383386
/// Add patch to the input contents of this section.

bolt/include/bolt/Core/Relocation.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class Relocation {
8686
/// Adjust value depending on relocation type (make it PC relative or not).
8787
static uint64_t encodeValue(uint32_t Type, uint64_t Value, uint64_t PC);
8888

89+
/// Return true if there are enough bits to encode the relocation value.
90+
static bool canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC);
91+
8992
/// Extract current relocated value from binary contents. This is used for
9093
/// RISC architectures where values are encoded in specific bits depending
9194
/// on the relocation value. For X86, we limit to sign extending the value

bolt/lib/Core/BinaryFunction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1824,7 +1824,7 @@ bool BinaryFunction::scanExternalRefs() {
18241824
// Add relocations unless disassembly failed for this function.
18251825
if (!DisassemblyFailed)
18261826
for (Relocation &Rel : FunctionRelocations)
1827-
getOriginSection()->addPendingRelocation(Rel);
1827+
getOriginSection()->addPendingRelocation(Rel, /*Optional*/ true);
18281828

18291829
// Add patches grouping them together.
18301830
if (!InstructionPatches.empty()) {

bolt/lib/Core/BinarySection.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,19 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
174174
OS.pwrite(Patch.Bytes.data(), Patch.Bytes.size(),
175175
SectionFileOffset + Patch.Offset);
176176

177-
for (Relocation &Reloc : PendingRelocations) {
177+
uint64_t SkippedPendingRelocations = 0;
178+
for (auto &[Reloc, Optional] : PendingRelocations) {
178179
uint64_t Value = Reloc.Addend;
179180
if (Reloc.Symbol)
180181
Value += Resolver(Reloc.Symbol);
181182

183+
// Safely skip any pending relocation that cannot be encoded and was added
184+
// as part of an optimization.
185+
if (Optional && !Relocation::canEncodeValue(
186+
Reloc.Type, Value, SectionAddress + Reloc.Offset)) {
187+
++SkippedPendingRelocations;
188+
continue;
189+
}
182190
Value = Relocation::encodeValue(Reloc.Type, Value,
183191
SectionAddress + Reloc.Offset);
184192

@@ -197,6 +205,10 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
197205
}
198206

199207
clearList(PendingRelocations);
208+
209+
if (SkippedPendingRelocations > 0)
210+
LLVM_DEBUG(dbgs() << "BOLT-INFO: Skipped " << SkippedPendingRelocations
211+
<< " pending relocations as they were out of range\n");
200212
}
201213

202214
BinarySection::~BinarySection() { updateContents(nullptr, 0); }

bolt/lib/Core/Relocation.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,16 @@ static uint64_t encodeValueX86(uint32_t Type, uint64_t Value, uint64_t PC) {
271271
return Value;
272272
}
273273

274+
static bool canEncodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
275+
switch (Type) {
276+
default:
277+
llvm_unreachable("unsupported relocation");
278+
case ELF::R_AARCH64_CALL26:
279+
case ELF::R_AARCH64_JUMP26:
280+
return isInt<28>(Value - PC);
281+
}
282+
}
283+
274284
static uint64_t encodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
275285
switch (Type) {
276286
default:
@@ -303,6 +313,16 @@ static uint64_t encodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
303313
return Value;
304314
}
305315

316+
static uint64_t canEncodeValueRISCV(uint32_t Type, uint64_t Value,
317+
uint64_t PC) {
318+
switch (Type) {
319+
default:
320+
llvm_unreachable("unsupported relocation");
321+
case ELF::R_RISCV_64:
322+
return true;
323+
}
324+
}
325+
306326
static uint64_t encodeValueRISCV(uint32_t Type, uint64_t Value, uint64_t PC) {
307327
switch (Type) {
308328
default:
@@ -739,6 +759,19 @@ uint64_t Relocation::encodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
739759
}
740760
}
741761

762+
bool Relocation::canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
763+
switch (Arch) {
764+
default:
765+
llvm_unreachable("Unsupported architecture");
766+
case Triple::aarch64:
767+
return canEncodeValueAArch64(Type, Value, PC);
768+
case Triple::riscv64:
769+
return canEncodeValueRISCV(Type, Value, PC);
770+
case Triple::x86_64:
771+
return true;
772+
}
773+
}
774+
742775
uint64_t Relocation::extractValue(uint32_t Type, uint64_t Contents,
743776
uint64_t PC) {
744777
switch (Arch) {

bolt/unittests/Core/BinaryContext.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,31 +161,36 @@ TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
161161
<< "Wrong forward branch value\n";
162162
}
163163

164-
// TODO: BOLT currently asserts when a relocation is out of range.
165164
TEST_P(BinaryContextTester, FlushOptionalOutOfRangePendingRelocCALL26) {
166165
if (GetParam() != Triple::aarch64)
167166
GTEST_SKIP();
168167

169168
// This test checks that flushPendingRelocations skips flushing any optional
170169
// pending relocations that cannot be encoded.
171170

171+
bool DebugFlagPrev = ::llvm::DebugFlag;
172+
::llvm::DebugFlag = true;
173+
testing::internal::CaptureStderr();
174+
172175
BinarySection &BS = BC->registerOrUpdateSection(
173176
".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC);
174177
MCSymbol *RelSymbol = BC->getOrCreateGlobalSymbol(4, "Func");
175178
ASSERT_TRUE(RelSymbol);
176-
BS.addPendingRelocation(
177-
Relocation{8, RelSymbol, ELF::R_AARCH64_CALL26, 0, 0});
179+
BS.addPendingRelocation(Relocation{8, RelSymbol, ELF::R_AARCH64_CALL26, 0, 0},
180+
/*Optional*/ true);
178181

179182
SmallVector<char> Vect;
180183
raw_svector_ostream OS(Vect);
181184

182-
// FIXME: bolt must be able to skip pending relocs that are out of range.
183-
EXPECT_DEBUG_DEATH(
184-
// Resolve relocation symbol to a high value so encoding will be out of
185-
// range.
186-
BS.flushPendingRelocations(OS,
187-
[&](const MCSymbol *S) { return 0x800000F; }),
188-
".*only PC \\+/- 128MB is allowed for direct call");
185+
// Resolve relocation symbol to a high value so encoding will be out of range.
186+
BS.flushPendingRelocations(OS, [&](const MCSymbol *S) { return 0x800000F; });
187+
188+
std::string CapturedStdErr = testing::internal::GetCapturedStderr();
189+
EXPECT_TRUE(CapturedStdErr.find("BOLT-INFO: Skipped 1 pending relocations as "
190+
"they were out of range") !=
191+
std::string::npos);
192+
193+
::llvm::DebugFlag = DebugFlagPrev;
189194
}
190195

191196
#endif

0 commit comments

Comments
 (0)