Skip to content

Commit 2614db4

Browse files
committed
[BOLT][Linux] Skip some functions
Skip some functions to simplify things, making BOLT for Linux more reliable and saving development efforts. We use "-bolt-function-list-file" to gather a list of C function when building Linux kernel, then BOLT can choose to optimize them only. BOLT can not handle some functions defined in assembly code reliably, since they may have extra semantics/enforcements BOLT can never know. For example, irq_entries_start defined in arch/x86/include/asm/idtentry.h is actually an “array” but BOLT views it as an ordinary function. If BOLT applies basic block reordering, instrumentation, etc to it, run time errors would happen. We could explicitly specify those functions to skip, but to find all of them we usually need to test & debug many times. That is a lot of work and we may still miss some. In my own experience, when adapting BOLT for Linux to a new architecture or Linux version, we may spend lots of time on fixing runtime errors related to functions defined in assembly code. Only handling C functions makes BOLT for Linux more reliable, and may save lots of development efforts. "-bolt-function-list-file" can be used as follows: * put "-mllvm -bolt-function-list-file=filename" in KCFLAGS when buildling Linux * specify "-funcs-file-no-regex=filename" when invoking llvm-bolt Some functions's address are used in add/sub/comparision. To make things simple, they should keep their address (after relocation mode is supported). See __bpf_call_base in kernel/bpf/core.c for an example. Currently, we just skip them. "bolt-keep-address-function-list-file" can be used as follows: * put "-mllvm -bolt-keep-address-function-list-file=filename" in KCFLAGS when buildling Linux * specify "-keep-address-funcs-file-no-regex=filename" when invoking llvm-bolt Functions in sections other than .text (e.g. .head.text, .init.text, .exit.text) are (mostly) run during initialization and shutdown, and not (much) relevant to application performance. Skipping them also helps to avoid runtime errors, especially those even before the first message is printed out, which are not easy to debug. Only handling functions in .text also make sure no function is moved to a different section (after relocation mode is supported). Linux kernel code may compare function pointers with section boundary symbols, and if we move functions to another section, runtime errors may happen.
1 parent 01cc1d1 commit 2614db4

File tree

5 files changed

+112
-2
lines changed

5 files changed

+112
-2
lines changed

bolt/include/bolt/Core/BinaryContext.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ class BinaryContext {
433433
Address);
434434
}
435435

436+
bool isInRange(StringRef NameStart, StringRef NameEnd,
437+
uint64_t Address) const {
438+
ErrorOr<uint64_t> Start = getSymbolValue(NameStart);
439+
ErrorOr<uint64_t> End = getSymbolValue(NameEnd);
440+
return Start && End && *Start <= Address && Address < *End;
441+
}
442+
436443
/// Return size of an entry for the given jump table \p Type.
437444
uint64_t getJumpTableEntrySize(JumpTable::JumpTableType Type) const {
438445
return Type == JumpTable::JTT_PIC ? 4 : AsmInfo->getCodePointerSize();
@@ -911,7 +918,11 @@ class BinaryContext {
911918
/// Return a value of the global \p Symbol or an error if the value
912919
/// was not set.
913920
ErrorOr<uint64_t> getSymbolValue(const MCSymbol &Symbol) const {
914-
const BinaryData *BD = getBinaryDataByName(Symbol.getName());
921+
return getSymbolValue(Symbol.getName());
922+
}
923+
924+
ErrorOr<uint64_t> getSymbolValue(StringRef Name) const {
925+
const BinaryData *BD = getBinaryDataByName(Name);
915926
if (!BD)
916927
return std::make_error_code(std::errc::bad_address);
917928
return BD->getAddress();

bolt/include/bolt/Core/BinaryFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ class BinaryFunction {
294294
/// Pseudo functions should not be disassembled or emitted.
295295
bool IsPseudo{false};
296296

297+
// True if address of this function can not be changed
298+
bool KeepAddress{false};
299+
297300
/// True if the original function code has all necessary relocations to track
298301
/// addresses of functions emitted to new locations. Typically set for
299302
/// functions that we are not going to emit.
@@ -1318,6 +1321,9 @@ class BinaryFunction {
13181321
/// otherwise processed.
13191322
bool isPseudo() const { return IsPseudo; }
13201323

1324+
/// Return true if address of this function can not be changed
1325+
bool mustKeepAddress() const { return KeepAddress; }
1326+
13211327
/// Return true if the function contains explicit or implicit indirect branch
13221328
/// to its split fragments, e.g., split jump table, landing pad in split
13231329
/// fragment.

bolt/lib/Rewrite/LinuxKernelRewriter.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,33 @@ class LinuxKernelRewriter final : public MetadataRewriter {
351351
if (Error E = detectLinuxKernelVersion())
352352
return E;
353353

354+
auto ShouldIgnore = [this](const BinaryFunction &Function) {
355+
std::optional<StringRef> SectionName = Function.getOriginSectionName();
356+
if (!SectionName || *SectionName != ".text")
357+
return true;
358+
359+
uint64_t Address = Function.getAddress();
360+
361+
if (BC.isX86()) {
362+
BinaryData *BDStart = BC.getBinaryDataByName("irq_entries_start");
363+
if (BDStart && BDStart->containsAddress(Address))
364+
return true;
365+
366+
if (BC.isInRange("__static_call_text_start", "__static_call_text_end",
367+
Address))
368+
return true;
369+
}
370+
371+
if (BC.isInRange("__noinstr_text_start", "__noinstr_text_end", Address))
372+
return true;
373+
374+
return false;
375+
};
376+
377+
for (BinaryFunction *Function : BC.getAllBinaryFunctions())
378+
if (ShouldIgnore(*Function))
379+
Function->setIgnored();
380+
354381
processLKSections();
355382

356383
if (Error E = processSMPLocks())

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ static cl::opt<std::string> FunctionNamesFileNR(
137137
cl::desc("file with list of functions to optimize (non-regex)"), cl::Hidden,
138138
cl::cat(BoltCategory));
139139

140+
static cl::list<std::string> KeepAddressFunctionNamesNR(
141+
"keep-address-funcs-no-regex", cl::CommaSeparated,
142+
cl::desc("KeepAddress functions from the list (non-regex)"),
143+
cl::value_desc("func1,func2,func3,..."), cl::Hidden, cl::cat(BoltCategory));
144+
145+
static cl::opt<std::string> KeepAddressFunctionNamesFileNR(
146+
"keep-address-funcs-file-no-regex",
147+
cl::desc("file with list of KeepAddress functions to optimize (non-regex)"),
148+
cl::Hidden, cl::cat(BoltCategory));
149+
140150
cl::opt<bool>
141151
KeepTmp("keep-tmp",
142152
cl::desc("preserve intermediate .o file"),
@@ -2982,6 +2992,8 @@ void RewriteInstance::selectFunctionsToProcess() {
29822992
populateFunctionNames(opts::FunctionNamesFile, opts::ForceFunctionNames);
29832993
populateFunctionNames(opts::SkipFunctionNamesFile, opts::SkipFunctionNames);
29842994
populateFunctionNames(opts::FunctionNamesFileNR, opts::ForceFunctionNamesNR);
2995+
populateFunctionNames(opts::KeepAddressFunctionNamesFileNR,
2996+
opts::KeepAddressFunctionNamesNR);
29852997

29862998
// Make a set of functions to process to speed up lookups.
29872999
std::unordered_set<std::string> ForceFunctionsNR(
@@ -2996,6 +3008,10 @@ void RewriteInstance::selectFunctionsToProcess() {
29963008
exit(1);
29973009
}
29983010

3011+
std::unordered_set<std::string> KeepAddressFunctionsNR(
3012+
opts::KeepAddressFunctionNamesNR.begin(),
3013+
opts::KeepAddressFunctionNamesNR.end());
3014+
29993015
uint64_t LiteThresholdExecCount = 0;
30003016
if (opts::LiteThresholdPct) {
30013017
if (opts::LiteThresholdPct > 100)
@@ -3043,7 +3059,8 @@ void RewriteInstance::selectFunctionsToProcess() {
30433059
for (std::string &Name : opts::SkipFunctionNames)
30443060
if (Function.hasNameRegex(Name))
30453061
return true;
3046-
3062+
if (Function.mustKeepAddress())
3063+
return true;
30473064
return false;
30483065
};
30493066

@@ -3094,6 +3111,10 @@ void RewriteInstance::selectFunctionsToProcess() {
30943111
for (auto &BFI : BC->getBinaryFunctions()) {
30953112
BinaryFunction &Function = BFI.second;
30963113

3114+
for (const StringRef Name : Function.getNames())
3115+
if (KeepAddressFunctionsNR.count(Name.str()))
3116+
Function.KeepAddress = true;
3117+
30973118
// Pseudo functions are explicitly marked by us not to be processed.
30983119
if (Function.isPseudo()) {
30993120
Function.IsIgnored = true;

llvm/lib/CodeGen/CodeGenPrepare.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
#include "llvm/Support/Compiler.h"
8686
#include "llvm/Support/Debug.h"
8787
#include "llvm/Support/ErrorHandling.h"
88+
#include "llvm/Support/FileSystem.h"
8889
#include "llvm/Support/raw_ostream.h"
8990
#include "llvm/Target/TargetMachine.h"
9091
#include "llvm/Target/TargetOptions.h"
@@ -274,6 +275,14 @@ static cl::opt<bool>
274275
DisableDeletePHIs("disable-cgp-delete-phis", cl::Hidden, cl::init(false),
275276
cl::desc("Disable elimination of dead PHI nodes."));
276277

278+
cl::opt<std::string>
279+
BoltFunctionListFile("bolt-function-list-file", cl::Hidden,
280+
cl::desc("Specify BOLT function list file"));
281+
282+
cl::opt<std::string> BoltKeepAddressFunctionListFile(
283+
"bolt-keep-address-function-list-file", cl::Hidden,
284+
cl::desc("Specify BOLT KeepAddress function list file"));
285+
277286
namespace {
278287

279288
enum ExtType {
@@ -505,7 +514,43 @@ class CodeGenPrepareLegacyPass : public FunctionPass {
505514

506515
char CodeGenPrepareLegacyPass::ID = 0;
507516

517+
template <typename T> void GatherForBoltKA(raw_fd_ostream &OS, T &I) {
518+
switch (I.getOpcode()) {
519+
case Instruction::ICmp:
520+
case Instruction::PtrToInt:
521+
for (Use &U : I.operands())
522+
if (auto *FF = dyn_cast<Function>(U.get()))
523+
OS << FF->getName() << "\n";
524+
break;
525+
default:;
526+
}
527+
for (Use &U : I.operands())
528+
if (auto *CE = dyn_cast<ConstantExpr>(U.get()))
529+
GatherForBoltKA(OS, *CE);
530+
}
531+
508532
bool CodeGenPrepareLegacyPass::runOnFunction(Function &F) {
533+
if (!BoltFunctionListFile.empty()) {
534+
std::error_code EC;
535+
raw_fd_ostream OS(BoltFunctionListFile, EC, sys::fs::OpenFlags::OF_Append);
536+
if (EC)
537+
report_fatal_error(Twine(BoltFunctionListFile) + ": " + EC.message());
538+
OS << F.getName() << "\n";
539+
}
540+
541+
if (!BoltKeepAddressFunctionListFile.empty()) {
542+
std::error_code EC;
543+
raw_fd_ostream OS(BoltKeepAddressFunctionListFile, EC,
544+
sys::fs::OpenFlags::OF_Append);
545+
if (EC)
546+
report_fatal_error(Twine(BoltKeepAddressFunctionListFile) + ": " +
547+
EC.message());
548+
549+
for (BasicBlock &BB : F)
550+
for (Instruction &I : BB)
551+
GatherForBoltKA(OS, I);
552+
}
553+
509554
if (skipFunction(F))
510555
return false;
511556
auto TM = &getAnalysis<TargetPassConfig>().getTM<TargetMachine>();

0 commit comments

Comments
 (0)