Skip to content

Commit b49b7a9

Browse files
committed
[BOLT] Add split function support for the Linux kernel
While rewriting the Linux kernel, we try to fit optimized functions into their original boundaries. When a function becomes larger, we skip it during the rewrite and end up with less than optimal code layout. To overcome that issue, add support for --split-function option so that hot part of the function could be fit into the original space. The cold part should go to reserved space in the binary.
1 parent c665e49 commit b49b7a9

File tree

4 files changed

+73
-26
lines changed

4 files changed

+73
-26
lines changed

bolt/include/bolt/Core/BinaryContext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "bolt/Core/JumpTable.h"
2121
#include "bolt/Core/MCPlusBuilder.h"
2222
#include "bolt/RuntimeLibs/RuntimeLibrary.h"
23+
#include "llvm/ADT/AddressRanges.h"
2324
#include "llvm/ADT/ArrayRef.h"
2425
#include "llvm/ADT/StringMap.h"
2526
#include "llvm/ADT/iterator.h"
@@ -726,6 +727,9 @@ class BinaryContext {
726727
uint64_t OldTextSectionOffset{0};
727728
uint64_t OldTextSectionSize{0};
728729

730+
/// Area in the input binary reserved for BOLT.
731+
AddressRange BOLTReserved;
732+
729733
/// Address of the code/function that is executed before any other code in
730734
/// the binary.
731735
std::optional<uint64_t> StartFunctionAddress;

bolt/lib/Passes/SplitFunctions.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,12 @@ Error SplitFunctions::runOnFunctions(BinaryContext &BC) {
715715
if (!opts::SplitFunctions)
716716
return Error::success();
717717

718+
if (BC.IsLinuxKernel && BC.BOLTReserved.empty()) {
719+
BC.errs() << "BOLT-ERROR: split functions require reserved space in the "
720+
"Linux kernel binary\n";
721+
exit(1);
722+
}
723+
718724
// If split strategy is not CDSplit, then a second run of the pass is not
719725
// needed after function reordering.
720726
if (BC.HasFinalizedFunctionOrder &&
@@ -829,6 +835,13 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
829835
}
830836
}
831837
}
838+
839+
// Outlining blocks with dynamic branches is not supported yet.
840+
if (BC.IsLinuxKernel) {
841+
if (llvm::any_of(
842+
*BB, [&](MCInst &Inst) { return BC.MIB->isDynamicBranch(Inst); }))
843+
BB->setCanOutline(false);
844+
}
832845
}
833846

834847
BF.getLayout().updateLayoutIndices();

bolt/lib/Rewrite/LinuxKernelRewriter.cpp

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -783,11 +783,9 @@ Error LinuxKernelRewriter::rewriteORCTables() {
783783
};
784784

785785
// Emit new ORC entries for the emitted function.
786-
auto emitORC = [&](const BinaryFunction &BF) -> Error {
787-
assert(!BF.isSplit() && "Split functions not supported by ORC writer yet.");
788-
786+
auto emitORC = [&](const FunctionFragment &FF) -> Error {
789787
ORCState CurrentState = NullORC;
790-
for (BinaryBasicBlock *BB : BF.getLayout().blocks()) {
788+
for (BinaryBasicBlock *BB : FF) {
791789
for (MCInst &Inst : *BB) {
792790
ErrorOr<ORCState> ErrorOrState =
793791
BC.MIB->tryGetAnnotationAs<ORCState>(Inst, "ORC");
@@ -808,7 +806,36 @@ Error LinuxKernelRewriter::rewriteORCTables() {
808806
return Error::success();
809807
};
810808

809+
// Emit ORC entries for cold fragments. We assume that these fragments are
810+
// emitted contiguously in memory using reserved space in the kernel. This
811+
// assumption is validated in post-emit pass validateORCTables() where we
812+
// check that ORC entries are sorted by their addresses.
813+
auto emitColdORC = [&]() -> Error {
814+
for (BinaryFunction &BF :
815+
llvm::make_second_range(BC.getBinaryFunctions())) {
816+
if (!BC.shouldEmit(BF))
817+
continue;
818+
for (FunctionFragment &FF : BF.getLayout().getSplitFragments())
819+
if (Error E = emitORC(FF))
820+
return E;
821+
}
822+
823+
return Error::success();
824+
};
825+
826+
bool ShouldEmitCold = !BC.BOLTReserved.empty();
811827
for (ORCListEntry &Entry : ORCEntries) {
828+
if (ShouldEmitCold && Entry.IP > BC.BOLTReserved.start()) {
829+
if (Error E = emitColdORC())
830+
return E;
831+
832+
// Emit terminator entry at the end of the reserved region.
833+
if (Error E = emitORCEntry(BC.BOLTReserved.end(), NullORC))
834+
return E;
835+
836+
ShouldEmitCold = false;
837+
}
838+
812839
// Emit original entries for functions that we haven't modified.
813840
if (!Entry.BF || !BC.shouldEmit(*Entry.BF)) {
814841
// Emit terminator only if it marks the start of a function.
@@ -822,7 +849,7 @@ Error LinuxKernelRewriter::rewriteORCTables() {
822849
// Emit all ORC entries for a function referenced by an entry and skip over
823850
// the rest of entries for this function by resetting its ORC attribute.
824851
if (Entry.BF->hasORC()) {
825-
if (Error E = emitORC(*Entry.BF))
852+
if (Error E = emitORC(Entry.BF->getLayout().getMainFragment()))
826853
return E;
827854
Entry.BF->setHasORC(false);
828855
}
@@ -831,10 +858,9 @@ Error LinuxKernelRewriter::rewriteORCTables() {
831858
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: emitted " << NumEmitted
832859
<< " ORC entries\n");
833860

834-
// Replicate terminator entry at the end of sections to match the original
835-
// table sizes.
836-
const BinaryFunction &LastBF = BC.getBinaryFunctions().rbegin()->second;
837-
const uint64_t LastIP = LastBF.getAddress() + LastBF.getMaxSize();
861+
// Populate ORC tables with a terminator entry with max address to match the
862+
// original table sizes.
863+
const uint64_t LastIP = std::numeric_limits<uint64_t>::max();
838864
while (UnwindWriter.bytesRemaining()) {
839865
if (Error E = emitORCEntry(LastIP, NullORC, nullptr, /*Force*/ true))
840866
return E;

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,13 +3628,19 @@ void RewriteInstance::mapFileSections(BOLTLinker::SectionMapper MapSection) {
36283628
}
36293629

36303630
if (StartBD) {
3631+
if (StartBD->getAddress() >= EndBD->getAddress()) {
3632+
BC->errs() << "BOLT-ERROR: invalid reserved space boundaries\n";
3633+
exit(1);
3634+
}
3635+
BC->BOLTReserved = AddressRange(StartBD->getAddress(), EndBD->getAddress());
3636+
BC->outs()
3637+
<< "BOLT-INFO: using reserved space for allocating new sections\n";
3638+
36313639
PHDRTableOffset = 0;
36323640
PHDRTableAddress = 0;
36333641
NewTextSegmentAddress = 0;
36343642
NewTextSegmentOffset = 0;
3635-
NextAvailableAddress = StartBD->getAddress();
3636-
BC->outs()
3637-
<< "BOLT-INFO: using reserved space for allocating new sections\n";
3643+
NextAvailableAddress = BC->BOLTReserved.start();
36383644
}
36393645

36403646
// If no new .eh_frame was written, remove relocated original .eh_frame.
@@ -3657,12 +3663,12 @@ void RewriteInstance::mapFileSections(BOLTLinker::SectionMapper MapSection) {
36573663
// Map the rest of the sections.
36583664
mapAllocatableSections(MapSection);
36593665

3660-
if (StartBD) {
3661-
const uint64_t ReservedSpace = EndBD->getAddress() - StartBD->getAddress();
3662-
const uint64_t AllocatedSize = NextAvailableAddress - StartBD->getAddress();
3663-
if (ReservedSpace < AllocatedSize) {
3664-
BC->errs() << "BOLT-ERROR: reserved space (" << ReservedSpace << " byte"
3665-
<< (ReservedSpace == 1 ? "" : "s")
3666+
if (!BC->BOLTReserved.empty()) {
3667+
const uint64_t AllocatedSize =
3668+
NextAvailableAddress - BC->BOLTReserved.start();
3669+
if (BC->BOLTReserved.size() < AllocatedSize) {
3670+
BC->errs() << "BOLT-ERROR: reserved space (" << BC->BOLTReserved.size()
3671+
<< " byte" << (BC->BOLTReserved.size() == 1 ? "" : "s")
36663672
<< ") is smaller than required for new allocations ("
36673673
<< AllocatedSize << " bytes)\n";
36683674
exit(1);
@@ -5852,13 +5858,11 @@ void RewriteInstance::writeEHFrameHeader() {
58525858

58535859
NextAvailableAddress += EHFrameHdrSec.getOutputSize();
58545860

5855-
if (const BinaryData *ReservedEnd =
5856-
BC->getBinaryDataByName(getBOLTReservedEnd())) {
5857-
if (NextAvailableAddress > ReservedEnd->getAddress()) {
5858-
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
5859-
<< " into reserved space\n";
5860-
exit(1);
5861-
}
5861+
if (!BC->BOLTReserved.empty() &&
5862+
(NextAvailableAddress > BC->BOLTReserved.end())) {
5863+
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
5864+
<< " into reserved space\n";
5865+
exit(1);
58625866
}
58635867

58645868
// Merge new .eh_frame with the relocated original so that gdb can locate all
@@ -5892,7 +5896,7 @@ uint64_t RewriteInstance::getNewValueForSymbol(const StringRef Name) {
58925896

58935897
uint64_t RewriteInstance::getFileOffsetForAddress(uint64_t Address) const {
58945898
// Check if it's possibly part of the new segment.
5895-
if (Address >= NewTextSegmentAddress)
5899+
if (NewTextSegmentAddress && Address >= NewTextSegmentAddress)
58965900
return Address - NewTextSegmentAddress + NewTextSegmentOffset;
58975901

58985902
// Find an existing segment that matches the address.

0 commit comments

Comments
 (0)