| 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 | |