1//===- bolt/tools/driver/llvm-bolt.cpp - Feedback-directed optimizer ------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This is a binary optimizer that will take 'perf' output and change
10// basic block layout for better performance (a.k.a. branch straightening),
11// plus some other optimizations that are better performed on a binary.
12//
13//===----------------------------------------------------------------------===//
14
15#include "bolt/Profile/DataAggregator.h"
16#include "bolt/Rewrite/MachORewriteInstance.h"
17#include "bolt/Rewrite/RewriteInstance.h"
18#include "bolt/Utils/CommandLineOpts.h"
19#include "llvm/MC/TargetRegistry.h"
20#include "llvm/Object/Binary.h"
21#include "llvm/Support/CommandLine.h"
22#include "llvm/Support/Errc.h"
23#include "llvm/Support/Error.h"
24#include "llvm/Support/ManagedStatic.h"
25#include "llvm/Support/Path.h"
26#include "llvm/Support/PrettyStackTrace.h"
27#include "llvm/Support/Signals.h"
28#include "llvm/Support/TargetSelect.h"
29
30#define DEBUG_TYPE "bolt"
31
32using namespace llvm;
33using namespace object;
34using namespace bolt;
35
36namespace opts {
37
38static cl::OptionCategory *BoltCategories[] = {&BoltCategory,
39 &BoltOptCategory,
40 &BoltRelocCategory,
41 &BoltInstrCategory,
42 &BoltOutputCategory};
43
44static cl::OptionCategory *BoltDiffCategories[] = {&BoltDiffCategory};
45
46static cl::OptionCategory *Perf2BoltCategories[] = {&AggregatorCategory,
47 &BoltOutputCategory};
48
49static cl::opt<std::string> InputFilename(cl::Positional,
50 cl::desc("<executable>"),
51 cl::Required, cl::cat(BoltCategory),
52 cl::sub(cl::SubCommand::getAll()));
53
54static cl::opt<std::string>
55InputDataFilename("data",
56 cl::desc("<data file>"),
57 cl::Optional,
58 cl::cat(BoltCategory));
59
60static cl::alias
61BoltProfile("b",
62 cl::desc("alias for -data"),
63 cl::aliasopt(InputDataFilename),
64 cl::cat(BoltCategory));
65
66cl::opt<std::string>
67 LogFile("log-file",
68 cl::desc("redirect journaling to a file instead of stdout/stderr"),
69 cl::Hidden, cl::cat(BoltCategory));
70
71static cl::opt<std::string>
72InputDataFilename2("data2",
73 cl::desc("<data file>"),
74 cl::Optional,
75 cl::cat(BoltCategory));
76
77static cl::opt<std::string>
78InputFilename2(
79 cl::Positional,
80 cl::desc("<executable>"),
81 cl::Optional,
82 cl::cat(BoltDiffCategory));
83
84} // namespace opts
85
86static StringRef ToolName;
87
88static void report_error(StringRef Message, std::error_code EC) {
89 assert(EC);
90 errs() << ToolName << ": '" << Message << "': " << EC.message() << ".\n";
91 exit(status: 1);
92}
93
94static void report_error(StringRef Message, Error E) {
95 assert(E);
96 errs() << ToolName << ": '" << Message << "': " << toString(E: std::move(E))
97 << ".\n";
98 exit(status: 1);
99}
100
101static void printBoltRevision(llvm::raw_ostream &OS) {
102 OS << "BOLT revision " << BoltRevision << "\n";
103}
104
105void perf2boltMode(int argc, char **argv) {
106 cl::HideUnrelatedOptions(Categories: ArrayRef(opts::Perf2BoltCategories));
107 cl::AddExtraVersionPrinter(func: printBoltRevision);
108 cl::ParseCommandLineOptions(
109 argc, argv,
110 Overview: "perf2bolt - BOLT data aggregator\n"
111 "\nEXAMPLE: perf2bolt -p=perf.data executable -o data.fdata\n");
112 if (opts::PerfData.empty()) {
113 errs() << ToolName << ": expected -perfdata=<filename> option.\n";
114 exit(status: 1);
115 }
116 if (!opts::InputDataFilename.empty()) {
117 errs() << ToolName << ": unknown -data option.\n";
118 exit(status: 1);
119 }
120 if (!sys::fs::exists(Path: opts::PerfData))
121 report_error(Message: opts::PerfData, EC: errc::no_such_file_or_directory);
122 if (!DataAggregator::checkPerfDataMagic(FileName: opts::PerfData)) {
123 errs() << ToolName << ": '" << opts::PerfData
124 << "': expected valid perf.data file.\n";
125 exit(status: 1);
126 }
127 if (opts::OutputFilename.empty()) {
128 errs() << ToolName << ": expected -o=<output file> option.\n";
129 exit(status: 1);
130 }
131 opts::AggregateOnly = true;
132}
133
134void boltDiffMode(int argc, char **argv) {
135 cl::HideUnrelatedOptions(Categories: ArrayRef(opts::BoltDiffCategories));
136 cl::AddExtraVersionPrinter(func: printBoltRevision);
137 cl::ParseCommandLineOptions(
138 argc, argv,
139 Overview: "llvm-boltdiff - BOLT binary diff tool\n"
140 "\nEXAMPLE: llvm-boltdiff -data=a.fdata -data2=b.fdata exec1 exec2\n");
141 if (opts::InputDataFilename2.empty()) {
142 errs() << ToolName << ": expected -data2=<filename> option.\n";
143 exit(status: 1);
144 }
145 if (opts::InputDataFilename.empty()) {
146 errs() << ToolName << ": expected -data=<filename> option.\n";
147 exit(status: 1);
148 }
149 if (opts::InputFilename2.empty()) {
150 errs() << ToolName << ": expected second binary name.\n";
151 exit(status: 1);
152 }
153 if (opts::InputFilename.empty()) {
154 errs() << ToolName << ": expected binary.\n";
155 exit(status: 1);
156 }
157 opts::DiffOnly = true;
158}
159
160void boltMode(int argc, char **argv) {
161 cl::HideUnrelatedOptions(Categories: ArrayRef(opts::BoltCategories));
162 // Register the target printer for --version.
163 cl::AddExtraVersionPrinter(func: printBoltRevision);
164 cl::AddExtraVersionPrinter(func: TargetRegistry::printRegisteredTargetsForVersion);
165
166 cl::ParseCommandLineOptions(argc, argv,
167 Overview: "BOLT - Binary Optimization and Layout Tool\n");
168
169 if (opts::OutputFilename.empty()) {
170 errs() << ToolName << ": expected -o=<output file> option.\n";
171 exit(status: 1);
172 }
173}
174
175static std::string GetExecutablePath(const char *Argv0) {
176 SmallString<256> ExecutablePath(Argv0);
177 // Do a PATH lookup if Argv0 isn't a valid path.
178 if (!llvm::sys::fs::exists(Path: ExecutablePath))
179 if (llvm::ErrorOr<std::string> P =
180 llvm::sys::findProgramByName(Name: ExecutablePath))
181 ExecutablePath = *P;
182 return std::string(ExecutablePath);
183}
184
185int main(int argc, char **argv) {
186 // Print a stack trace if we signal out.
187 sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
188 PrettyStackTraceProgram X(argc, argv);
189
190 llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
191
192 std::string ToolPath = GetExecutablePath(Argv0: argv[0]);
193
194 // Initialize targets and assembly printers/parsers.
195 llvm::InitializeAllTargetInfos();
196 llvm::InitializeAllTargetMCs();
197 llvm::InitializeAllAsmParsers();
198 llvm::InitializeAllDisassemblers();
199
200 llvm::InitializeAllTargets();
201 llvm::InitializeAllAsmPrinters();
202
203 ToolName = argv[0];
204
205 if (llvm::sys::path::filename(path: ToolName) == "perf2bolt")
206 perf2boltMode(argc, argv);
207 else if (llvm::sys::path::filename(path: ToolName) == "llvm-boltdiff")
208 boltDiffMode(argc, argv);
209 else
210 boltMode(argc, argv);
211
212 if (!sys::fs::exists(Path: opts::InputFilename))
213 report_error(Message: opts::InputFilename, EC: errc::no_such_file_or_directory);
214
215 // Initialize journaling streams
216 raw_ostream *BOLTJournalOut = &outs();
217 raw_ostream *BOLTJournalErr = &errs();
218 // RAII obj to keep log file open throughout execution
219 std::unique_ptr<raw_fd_ostream> LogFileStream;
220 if (!opts::LogFile.empty()) {
221 std::error_code LogEC;
222 LogFileStream = std::make_unique<raw_fd_ostream>(
223 args&: opts::LogFile, args&: LogEC, args: sys::fs::OpenFlags::OF_None);
224 if (LogEC) {
225 errs() << "BOLT-ERROR: cannot open requested log file for writing: "
226 << LogEC.message() << "\n";
227 exit(status: 1);
228 }
229 BOLTJournalOut = LogFileStream.get();
230 BOLTJournalErr = LogFileStream.get();
231 }
232
233 // Attempt to open the binary.
234 if (!opts::DiffOnly) {
235 Expected<OwningBinary<Binary>> BinaryOrErr =
236 createBinary(Path: opts::InputFilename);
237 if (Error E = BinaryOrErr.takeError())
238 report_error(Message: opts::InputFilename, E: std::move(E));
239 Binary &Binary = *BinaryOrErr.get().getBinary();
240
241 if (auto *e = dyn_cast<ELFObjectFileBase>(Val: &Binary)) {
242 auto RIOrErr = RewriteInstance::create(File: e, Argc: argc, Argv: argv, ToolPath,
243 Stdout&: *BOLTJournalOut, Stderr&: *BOLTJournalErr);
244 if (Error E = RIOrErr.takeError())
245 report_error(Message: opts::InputFilename, E: std::move(E));
246 RewriteInstance &RI = *RIOrErr.get();
247 if (!opts::PerfData.empty()) {
248 if (!opts::AggregateOnly) {
249 errs() << ToolName
250 << ": WARNING: reading perf data directly is unsupported, "
251 "please use "
252 "-aggregate-only or perf2bolt.\n!!! Proceed on your own "
253 "risk. !!!\n";
254 }
255 if (Error E = RI.setProfile(opts::PerfData))
256 report_error(Message: opts::PerfData, E: std::move(E));
257 }
258 if (!opts::InputDataFilename.empty()) {
259 if (Error E = RI.setProfile(opts::InputDataFilename))
260 report_error(Message: opts::InputDataFilename, E: std::move(E));
261 }
262 if (opts::AggregateOnly && opts::PerfData.empty()) {
263 errs() << ToolName << ": missing required -perfdata option.\n";
264 exit(status: 1);
265 }
266
267 if (Error E = RI.run())
268 report_error(Message: opts::InputFilename, E: std::move(E));
269 } else if (auto *O = dyn_cast<MachOObjectFile>(Val: &Binary)) {
270 auto MachORIOrErr = MachORewriteInstance::create(InputFile: O, ToolPath);
271 if (Error E = MachORIOrErr.takeError())
272 report_error(Message: opts::InputFilename, E: std::move(E));
273 MachORewriteInstance &MachORI = *MachORIOrErr.get();
274
275 if (!opts::InputDataFilename.empty())
276 if (Error E = MachORI.setProfile(opts::InputDataFilename))
277 report_error(Message: opts::InputDataFilename, E: std::move(E));
278
279 MachORI.run();
280 } else {
281 report_error(Message: opts::InputFilename, EC: object_error::invalid_file_type);
282 }
283
284 return EXIT_SUCCESS;
285 }
286
287 // Bolt-diff
288 Expected<OwningBinary<Binary>> BinaryOrErr1 =
289 createBinary(Path: opts::InputFilename);
290 Expected<OwningBinary<Binary>> BinaryOrErr2 =
291 createBinary(Path: opts::InputFilename2);
292 if (Error E = BinaryOrErr1.takeError())
293 report_error(Message: opts::InputFilename, E: std::move(E));
294 if (Error E = BinaryOrErr2.takeError())
295 report_error(Message: opts::InputFilename2, E: std::move(E));
296 Binary &Binary1 = *BinaryOrErr1.get().getBinary();
297 Binary &Binary2 = *BinaryOrErr2.get().getBinary();
298 if (auto *ELFObj1 = dyn_cast<ELFObjectFileBase>(Val: &Binary1)) {
299 if (auto *ELFObj2 = dyn_cast<ELFObjectFileBase>(Val: &Binary2)) {
300 auto RI1OrErr = RewriteInstance::create(File: ELFObj1, Argc: argc, Argv: argv, ToolPath);
301 if (Error E = RI1OrErr.takeError())
302 report_error(Message: opts::InputFilename, E: std::move(E));
303 RewriteInstance &RI1 = *RI1OrErr.get();
304 if (Error E = RI1.setProfile(opts::InputDataFilename))
305 report_error(Message: opts::InputDataFilename, E: std::move(E));
306 auto RI2OrErr = RewriteInstance::create(File: ELFObj2, Argc: argc, Argv: argv, ToolPath);
307 if (Error E = RI2OrErr.takeError())
308 report_error(Message: opts::InputFilename2, E: std::move(E));
309 RewriteInstance &RI2 = *RI2OrErr.get();
310 if (Error E = RI2.setProfile(opts::InputDataFilename2))
311 report_error(Message: opts::InputDataFilename2, E: std::move(E));
312 outs() << "BOLT-DIFF: *** Analyzing binary 1: " << opts::InputFilename
313 << "\n";
314 outs() << "BOLT-DIFF: *** Binary 1 fdata: " << opts::InputDataFilename
315 << "\n";
316 if (Error E = RI1.run())
317 report_error(Message: opts::InputFilename, E: std::move(E));
318 outs() << "BOLT-DIFF: *** Analyzing binary 2: " << opts::InputFilename2
319 << "\n";
320 outs() << "BOLT-DIFF: *** Binary 2 fdata: "
321 << opts::InputDataFilename2 << "\n";
322 if (Error E = RI2.run())
323 report_error(Message: opts::InputFilename2, E: std::move(E));
324 RI1.compare(RI2);
325 } else {
326 report_error(Message: opts::InputFilename2, EC: object_error::invalid_file_type);
327 }
328 } else {
329 report_error(Message: opts::InputFilename, EC: object_error::invalid_file_type);
330 }
331
332 return EXIT_SUCCESS;
333}
334

source code of bolt/tools/driver/llvm-bolt.cpp