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 | |
32 | using namespace llvm; |
33 | using namespace object; |
34 | using namespace bolt; |
35 | |
36 | namespace opts { |
37 | |
38 | static cl::OptionCategory *BoltCategories[] = {&BoltCategory, |
39 | &BoltOptCategory, |
40 | &BoltRelocCategory, |
41 | &BoltInstrCategory, |
42 | &BoltOutputCategory}; |
43 | |
44 | static cl::OptionCategory *BoltDiffCategories[] = {&BoltDiffCategory}; |
45 | |
46 | static cl::OptionCategory *Perf2BoltCategories[] = {&AggregatorCategory, |
47 | &BoltOutputCategory}; |
48 | |
49 | static cl::opt<std::string> InputFilename(cl::Positional, |
50 | cl::desc("<executable>" ), |
51 | cl::Required, cl::cat(BoltCategory), |
52 | cl::sub(cl::SubCommand::getAll())); |
53 | |
54 | static cl::opt<std::string> |
55 | InputDataFilename("data" , |
56 | cl::desc("<data file>" ), |
57 | cl::Optional, |
58 | cl::cat(BoltCategory)); |
59 | |
60 | static cl::alias |
61 | BoltProfile("b" , |
62 | cl::desc("alias for -data" ), |
63 | cl::aliasopt(InputDataFilename), |
64 | cl::cat(BoltCategory)); |
65 | |
66 | cl::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 | |
71 | static cl::opt<std::string> |
72 | InputDataFilename2("data2" , |
73 | cl::desc("<data file>" ), |
74 | cl::Optional, |
75 | cl::cat(BoltCategory)); |
76 | |
77 | static cl::opt<std::string> |
78 | InputFilename2( |
79 | cl::Positional, |
80 | cl::desc("<executable>" ), |
81 | cl::Optional, |
82 | cl::cat(BoltDiffCategory)); |
83 | |
84 | } // namespace opts |
85 | |
86 | static StringRef ToolName; |
87 | |
88 | static 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 | |
94 | static 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 | |
101 | static void printBoltRevision(llvm::raw_ostream &OS) { |
102 | OS << "BOLT revision " << BoltRevision << "\n" ; |
103 | } |
104 | |
105 | void 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 | |
134 | void 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 | |
160 | void 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 | |
175 | static 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 | |
185 | int 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 | |