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 | static 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 | opts::ShowDensity = true; |
133 | } |
134 | |
135 | void boltDiffMode(int argc, char **argv) { |
136 | cl::HideUnrelatedOptions(Categories: ArrayRef(opts::BoltDiffCategories)); |
137 | cl::AddExtraVersionPrinter(func: printBoltRevision); |
138 | cl::ParseCommandLineOptions( |
139 | argc, argv, |
140 | Overview: "llvm-boltdiff - BOLT binary diff tool\n" |
141 | "\nEXAMPLE: llvm-boltdiff -data=a.fdata -data2=b.fdata exec1 exec2\n" ); |
142 | if (opts::InputDataFilename2.empty()) { |
143 | errs() << ToolName << ": expected -data2=<filename> option.\n" ; |
144 | exit(status: 1); |
145 | } |
146 | if (opts::InputDataFilename.empty()) { |
147 | errs() << ToolName << ": expected -data=<filename> option.\n" ; |
148 | exit(status: 1); |
149 | } |
150 | if (opts::InputFilename2.empty()) { |
151 | errs() << ToolName << ": expected second binary name.\n" ; |
152 | exit(status: 1); |
153 | } |
154 | if (opts::InputFilename.empty()) { |
155 | errs() << ToolName << ": expected binary.\n" ; |
156 | exit(status: 1); |
157 | } |
158 | opts::DiffOnly = true; |
159 | } |
160 | |
161 | void boltMode(int argc, char **argv) { |
162 | cl::HideUnrelatedOptions(Categories: ArrayRef(opts::BoltCategories)); |
163 | // Register the target printer for --version. |
164 | cl::AddExtraVersionPrinter(func: printBoltRevision); |
165 | cl::AddExtraVersionPrinter(func: TargetRegistry::printRegisteredTargetsForVersion); |
166 | |
167 | cl::ParseCommandLineOptions(argc, argv, |
168 | Overview: "BOLT - Binary Optimization and Layout Tool\n" ); |
169 | |
170 | if (opts::OutputFilename.empty()) { |
171 | errs() << ToolName << ": expected -o=<output file> option.\n" ; |
172 | exit(status: 1); |
173 | } |
174 | } |
175 | |
176 | int main(int argc, char **argv) { |
177 | // Print a stack trace if we signal out. |
178 | sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
179 | PrettyStackTraceProgram X(argc, argv); |
180 | |
181 | llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. |
182 | |
183 | std::string ToolPath = llvm::sys::fs::getMainExecutable(argv0: argv[0], MainExecAddr: nullptr); |
184 | |
185 | // Initialize targets and assembly printers/parsers. |
186 | #define BOLT_TARGET(target) \ |
187 | LLVMInitialize##target##TargetInfo(); \ |
188 | LLVMInitialize##target##TargetMC(); \ |
189 | LLVMInitialize##target##AsmParser(); \ |
190 | LLVMInitialize##target##Disassembler(); \ |
191 | LLVMInitialize##target##Target(); \ |
192 | LLVMInitialize##target##AsmPrinter(); |
193 | |
194 | #include "bolt/Core/TargetConfig.def" |
195 | |
196 | ToolName = argv[0]; |
197 | |
198 | if (llvm::sys::path::filename(path: ToolName).starts_with(Prefix: "perf2bolt" )) |
199 | perf2boltMode(argc, argv); |
200 | else if (llvm::sys::path::filename(path: ToolName).starts_with(Prefix: "llvm-boltdiff" )) |
201 | boltDiffMode(argc, argv); |
202 | else |
203 | boltMode(argc, argv); |
204 | |
205 | if (!sys::fs::exists(Path: opts::InputFilename)) |
206 | report_error(Message: opts::InputFilename, EC: errc::no_such_file_or_directory); |
207 | |
208 | // Initialize journaling streams |
209 | raw_ostream *BOLTJournalOut = &outs(); |
210 | raw_ostream *BOLTJournalErr = &errs(); |
211 | // RAII obj to keep log file open throughout execution |
212 | std::unique_ptr<raw_fd_ostream> LogFileStream; |
213 | if (!opts::LogFile.empty()) { |
214 | std::error_code LogEC; |
215 | LogFileStream = std::make_unique<raw_fd_ostream>( |
216 | args&: opts::LogFile, args&: LogEC, args: sys::fs::OpenFlags::OF_None); |
217 | if (LogEC) { |
218 | errs() << "BOLT-ERROR: cannot open requested log file for writing: " |
219 | << LogEC.message() << "\n" ; |
220 | exit(status: 1); |
221 | } |
222 | BOLTJournalOut = LogFileStream.get(); |
223 | BOLTJournalErr = LogFileStream.get(); |
224 | } |
225 | |
226 | // Attempt to open the binary. |
227 | if (!opts::DiffOnly) { |
228 | Expected<OwningBinary<Binary>> BinaryOrErr = |
229 | createBinary(Path: opts::InputFilename); |
230 | if (Error E = BinaryOrErr.takeError()) |
231 | report_error(Message: opts::InputFilename, E: std::move(E)); |
232 | Binary &Binary = *BinaryOrErr.get().getBinary(); |
233 | |
234 | if (auto *e = dyn_cast<ELFObjectFileBase>(Val: &Binary)) { |
235 | auto RIOrErr = RewriteInstance::create(File: e, Argc: argc, Argv: argv, ToolPath, |
236 | Stdout&: *BOLTJournalOut, Stderr&: *BOLTJournalErr); |
237 | if (Error E = RIOrErr.takeError()) |
238 | report_error(Message: opts::InputFilename, E: std::move(E)); |
239 | RewriteInstance &RI = *RIOrErr.get(); |
240 | if (!opts::PerfData.empty()) { |
241 | if (!opts::AggregateOnly) { |
242 | errs() << ToolName |
243 | << ": WARNING: reading perf data directly is unsupported, " |
244 | "please use " |
245 | "-aggregate-only or perf2bolt.\n!!! Proceed on your own " |
246 | "risk. !!!\n" ; |
247 | } |
248 | if (Error E = RI.setProfile(opts::PerfData)) |
249 | report_error(Message: opts::PerfData, E: std::move(E)); |
250 | } |
251 | if (!opts::InputDataFilename.empty()) { |
252 | if (Error E = RI.setProfile(opts::InputDataFilename)) |
253 | report_error(Message: opts::InputDataFilename, E: std::move(E)); |
254 | } |
255 | if (opts::AggregateOnly && opts::PerfData.empty()) { |
256 | errs() << ToolName << ": missing required -perfdata option.\n" ; |
257 | exit(status: 1); |
258 | } |
259 | |
260 | if (Error E = RI.run()) |
261 | report_error(Message: opts::InputFilename, E: std::move(E)); |
262 | } else if (auto *O = dyn_cast<MachOObjectFile>(Val: &Binary)) { |
263 | auto MachORIOrErr = MachORewriteInstance::create(InputFile: O, ToolPath); |
264 | if (Error E = MachORIOrErr.takeError()) |
265 | report_error(Message: opts::InputFilename, E: std::move(E)); |
266 | MachORewriteInstance &MachORI = *MachORIOrErr.get(); |
267 | |
268 | if (!opts::InputDataFilename.empty()) |
269 | if (Error E = MachORI.setProfile(opts::InputDataFilename)) |
270 | report_error(Message: opts::InputDataFilename, E: std::move(E)); |
271 | |
272 | MachORI.run(); |
273 | } else { |
274 | report_error(Message: opts::InputFilename, EC: object_error::invalid_file_type); |
275 | } |
276 | |
277 | return EXIT_SUCCESS; |
278 | } |
279 | |
280 | // Bolt-diff |
281 | Expected<OwningBinary<Binary>> BinaryOrErr1 = |
282 | createBinary(Path: opts::InputFilename); |
283 | Expected<OwningBinary<Binary>> BinaryOrErr2 = |
284 | createBinary(Path: opts::InputFilename2); |
285 | if (Error E = BinaryOrErr1.takeError()) |
286 | report_error(Message: opts::InputFilename, E: std::move(E)); |
287 | if (Error E = BinaryOrErr2.takeError()) |
288 | report_error(Message: opts::InputFilename2, E: std::move(E)); |
289 | Binary &Binary1 = *BinaryOrErr1.get().getBinary(); |
290 | Binary &Binary2 = *BinaryOrErr2.get().getBinary(); |
291 | if (auto *ELFObj1 = dyn_cast<ELFObjectFileBase>(Val: &Binary1)) { |
292 | if (auto *ELFObj2 = dyn_cast<ELFObjectFileBase>(Val: &Binary2)) { |
293 | auto RI1OrErr = RewriteInstance::create(File: ELFObj1, Argc: argc, Argv: argv, ToolPath); |
294 | if (Error E = RI1OrErr.takeError()) |
295 | report_error(Message: opts::InputFilename, E: std::move(E)); |
296 | RewriteInstance &RI1 = *RI1OrErr.get(); |
297 | if (Error E = RI1.setProfile(opts::InputDataFilename)) |
298 | report_error(Message: opts::InputDataFilename, E: std::move(E)); |
299 | auto RI2OrErr = RewriteInstance::create(File: ELFObj2, Argc: argc, Argv: argv, ToolPath); |
300 | if (Error E = RI2OrErr.takeError()) |
301 | report_error(Message: opts::InputFilename2, E: std::move(E)); |
302 | RewriteInstance &RI2 = *RI2OrErr.get(); |
303 | if (Error E = RI2.setProfile(opts::InputDataFilename2)) |
304 | report_error(Message: opts::InputDataFilename2, E: std::move(E)); |
305 | outs() << "BOLT-DIFF: *** Analyzing binary 1: " << opts::InputFilename |
306 | << "\n" ; |
307 | outs() << "BOLT-DIFF: *** Binary 1 fdata: " << opts::InputDataFilename |
308 | << "\n" ; |
309 | if (Error E = RI1.run()) |
310 | report_error(Message: opts::InputFilename, E: std::move(E)); |
311 | outs() << "BOLT-DIFF: *** Analyzing binary 2: " << opts::InputFilename2 |
312 | << "\n" ; |
313 | outs() << "BOLT-DIFF: *** Binary 2 fdata: " |
314 | << opts::InputDataFilename2 << "\n" ; |
315 | if (Error E = RI2.run()) |
316 | report_error(Message: opts::InputFilename2, E: std::move(E)); |
317 | RI1.compare(RI2); |
318 | } else { |
319 | report_error(Message: opts::InputFilename2, EC: object_error::invalid_file_type); |
320 | } |
321 | } else { |
322 | report_error(Message: opts::InputFilename, EC: object_error::invalid_file_type); |
323 | } |
324 | |
325 | return EXIT_SUCCESS; |
326 | } |
327 | |