388 lines · cpp
1//===-- llvm-cgdata.cpp - LLVM CodeGen Data Tool --------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8//9// llvm-cgdata parses raw codegen data embedded in compiled binary files, and10// merges them into a single .cgdata file. It can also inspect and maninuplate11// a .cgdata file. This .cgdata can contain various codegen data like outlining12// information, and it can be used to optimize the code in the subsequent build.13//14//===----------------------------------------------------------------------===//15#include "llvm/ADT/StringRef.h"16#include "llvm/CGData/CodeGenDataReader.h"17#include "llvm/CGData/CodeGenDataWriter.h"18#include "llvm/IR/LLVMContext.h"19#include "llvm/Object/Archive.h"20#include "llvm/Object/Binary.h"21#include "llvm/Option/ArgList.h"22#include "llvm/Option/Option.h"23#include "llvm/Support/CommandLine.h"24#include "llvm/Support/LLVMDriver.h"25#include "llvm/Support/Path.h"26#include "llvm/Support/VirtualFileSystem.h"27#include "llvm/Support/WithColor.h"28#include "llvm/Support/raw_ostream.h"29 30using namespace llvm;31using namespace llvm::object;32 33enum CGDataFormat {34 Invalid,35 Text,36 Binary,37};38 39enum CGDataAction {40 Convert,41 Merge,42 Show,43};44 45// Command-line option boilerplate.46namespace {47enum ID {48 OPT_INVALID = 0, // This is not an option ID.49#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),50#include "Opts.inc"51#undef OPTION52};53 54#define OPTTABLE_STR_TABLE_CODE55#include "Opts.inc"56#undef OPTTABLE_STR_TABLE_CODE57 58#define OPTTABLE_PREFIXES_TABLE_CODE59#include "Opts.inc"60#undef OPTTABLE_PREFIXES_TABLE_CODE61 62using namespace llvm::opt;63static constexpr opt::OptTable::Info InfoTable[] = {64#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),65#include "Opts.inc"66#undef OPTION67};68 69class CGDataOptTable : public opt::GenericOptTable {70public:71 CGDataOptTable()72 : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}73};74} // end anonymous namespace75 76// Options77static StringRef ToolName;78static std::string OutputFilename = "-";79static std::string Filename;80static bool ShowCGDataVersion;81static bool SkipTrim;82static CGDataAction Action;83static std::optional<CGDataFormat> OutputFormat;84static std::vector<std::string> InputFilenames;85 86namespace llvm {87extern cl::opt<bool> IndexedCodeGenDataLazyLoading;88} // end namespace llvm89 90static void exitWithError(Twine Message, StringRef Whence = "",91 StringRef Hint = "") {92 WithColor::error();93 if (!Whence.empty())94 errs() << Whence << ": ";95 errs() << Message << "\n";96 if (!Hint.empty())97 WithColor::note() << Hint << "\n";98 ::exit(1);99}100 101static void exitWithError(Error E, StringRef Whence = "") {102 if (E.isA<CGDataError>()) {103 handleAllErrors(std::move(E), [&](const CGDataError &IPE) {104 exitWithError(IPE.message(), Whence);105 });106 return;107 }108 109 exitWithError(toString(std::move(E)), Whence);110}111 112static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") {113 exitWithError(EC.message(), Whence);114}115 116static int convert_main(int argc, const char *argv[]) {117 std::error_code EC;118 raw_fd_ostream OS(OutputFilename, EC,119 OutputFormat == CGDataFormat::Text120 ? sys::fs::OF_TextWithCRLF121 : sys::fs::OF_None);122 if (EC)123 exitWithErrorCode(EC, OutputFilename);124 125 auto FS = vfs::getRealFileSystem();126 auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);127 if (Error E = ReaderOrErr.takeError())128 exitWithError(std::move(E), Filename);129 130 CodeGenDataWriter Writer;131 auto Reader = ReaderOrErr->get();132 if (Reader->hasOutlinedHashTree()) {133 OutlinedHashTreeRecord Record(Reader->releaseOutlinedHashTree());134 Writer.addRecord(Record);135 }136 if (Reader->hasStableFunctionMap()) {137 StableFunctionMapRecord Record(Reader->releaseStableFunctionMap());138 Writer.addRecord(Record);139 }140 141 if (OutputFormat == CGDataFormat::Text) {142 if (Error E = Writer.writeText(OS))143 exitWithError(std::move(E));144 } else {145 if (Error E = Writer.write(OS))146 exitWithError(std::move(E));147 }148 149 return 0;150}151 152static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,153 OutlinedHashTreeRecord &GlobalOutlineRecord,154 StableFunctionMapRecord &GlobalFunctionMapRecord);155 156static bool handleArchive(StringRef Filename, Archive &Arch,157 OutlinedHashTreeRecord &GlobalOutlineRecord,158 StableFunctionMapRecord &GlobalFunctionMapRecord) {159 bool Result = true;160 Error Err = Error::success();161 for (const auto &Child : Arch.children(Err)) {162 auto BuffOrErr = Child.getMemoryBufferRef();163 if (Error E = BuffOrErr.takeError())164 exitWithError(std::move(E), Filename);165 auto NameOrErr = Child.getName();166 if (Error E = NameOrErr.takeError())167 exitWithError(std::move(E), Filename);168 std::string Name = (Filename + "(" + NameOrErr.get() + ")").str();169 Result &= handleBuffer(Name, BuffOrErr.get(), GlobalOutlineRecord,170 GlobalFunctionMapRecord);171 }172 if (Err)173 exitWithError(std::move(Err), Filename);174 return Result;175}176 177static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,178 OutlinedHashTreeRecord &GlobalOutlineRecord,179 StableFunctionMapRecord &GlobalFunctionMapRecord) {180 Expected<std::unique_ptr<object::Binary>> BinOrErr =181 object::createBinary(Buffer);182 if (Error E = BinOrErr.takeError())183 exitWithError(std::move(E), Filename);184 185 bool Result = true;186 if (auto *Obj = dyn_cast<ObjectFile>(BinOrErr->get())) {187 if (Error E = CodeGenDataReader::mergeFromObjectFile(188 Obj, GlobalOutlineRecord, GlobalFunctionMapRecord))189 exitWithError(std::move(E), Filename);190 } else if (auto *Arch = dyn_cast<Archive>(BinOrErr->get())) {191 Result &= handleArchive(Filename, *Arch, GlobalOutlineRecord,192 GlobalFunctionMapRecord);193 } else {194 // TODO: Support for the MachO universal binary format.195 errs() << "Error: unsupported binary file: " << Filename << "\n";196 Result = false;197 }198 199 return Result;200}201 202static bool handleFile(StringRef Filename,203 OutlinedHashTreeRecord &GlobalOutlineRecord,204 StableFunctionMapRecord &GlobalFunctionMapRecord) {205 ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =206 MemoryBuffer::getFileOrSTDIN(Filename);207 if (std::error_code EC = BuffOrErr.getError())208 exitWithErrorCode(EC, Filename);209 return handleBuffer(Filename, *BuffOrErr.get(), GlobalOutlineRecord,210 GlobalFunctionMapRecord);211}212 213static int merge_main(int argc, const char *argv[]) {214 bool Result = true;215 OutlinedHashTreeRecord GlobalOutlineRecord;216 StableFunctionMapRecord GlobalFunctionMapRecord;217 for (auto &Filename : InputFilenames)218 Result &=219 handleFile(Filename, GlobalOutlineRecord, GlobalFunctionMapRecord);220 221 if (!Result)222 exitWithError("failed to merge codegen data files.");223 224 GlobalFunctionMapRecord.finalize(SkipTrim);225 226 CodeGenDataWriter Writer;227 if (!GlobalOutlineRecord.empty())228 Writer.addRecord(GlobalOutlineRecord);229 if (!GlobalFunctionMapRecord.empty())230 Writer.addRecord(GlobalFunctionMapRecord);231 232 std::error_code EC;233 raw_fd_ostream OS(OutputFilename, EC,234 OutputFormat == CGDataFormat::Text235 ? sys::fs::OF_TextWithCRLF236 : sys::fs::OF_None);237 if (EC)238 exitWithErrorCode(EC, OutputFilename);239 240 if (OutputFormat == CGDataFormat::Text) {241 if (Error E = Writer.writeText(OS))242 exitWithError(std::move(E));243 } else {244 if (Error E = Writer.write(OS))245 exitWithError(std::move(E));246 }247 248 return 0;249}250 251static int show_main(int argc, const char *argv[]) {252 std::error_code EC;253 raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF);254 if (EC)255 exitWithErrorCode(EC, OutputFilename);256 257 auto FS = vfs::getRealFileSystem();258 auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS);259 if (Error E = ReaderOrErr.takeError())260 exitWithError(std::move(E), Filename);261 262 auto Reader = ReaderOrErr->get();263 if (ShowCGDataVersion)264 OS << "Version: " << Reader->getVersion() << "\n";265 266 if (Reader->hasOutlinedHashTree()) {267 auto Tree = Reader->releaseOutlinedHashTree();268 OS << "Outlined hash tree:\n";269 OS << " Total Node Count: " << Tree->size() << "\n";270 OS << " Terminal Node Count: " << Tree->size(/*GetTerminalCountOnly=*/true)271 << "\n";272 OS << " Depth: " << Tree->depth() << "\n";273 }274 if (Reader->hasStableFunctionMap()) {275 auto Map = Reader->releaseStableFunctionMap();276 OS << "Stable function map:\n";277 OS << " Unique hash Count: " << Map->size() << "\n";278 OS << " Total function Count: "279 << Map->size(StableFunctionMap::TotalFunctionCount) << "\n";280 OS << " Mergeable function Count: "281 << Map->size(StableFunctionMap::MergeableFunctionCount) << "\n";282 }283 284 return 0;285}286 287static void parseArgs(int argc, char **argv) {288 CGDataOptTable Tbl;289 ToolName = argv[0];290 llvm::BumpPtrAllocator A;291 llvm::StringSaver Saver{A};292 llvm::opt::InputArgList Args =293 Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) {294 llvm::errs() << Msg << '\n';295 std::exit(1);296 });297 298 if (Args.hasArg(OPT_help)) {299 Tbl.printHelp(300 llvm::outs(),301 "llvm-cgdata <action> [options] (<binary files>|<.cgdata file>)",302 ToolName.str().c_str());303 std::exit(0);304 }305 if (Args.hasArg(OPT_version)) {306 cl::PrintVersionMessage();307 std::exit(0);308 }309 310 ShowCGDataVersion = Args.hasArg(OPT_cgdata_version);311 SkipTrim = Args.hasArg(OPT_skip_trim);312 313 if (opt::Arg *A = Args.getLastArg(OPT_format)) {314 StringRef OF = A->getValue();315 OutputFormat = StringSwitch<CGDataFormat>(OF)316 .Case("text", CGDataFormat::Text)317 .Case("binary", CGDataFormat::Binary)318 .Default(CGDataFormat::Invalid);319 if (OutputFormat == CGDataFormat::Invalid)320 exitWithError("unsupported format '" + OF + "'");321 }322 323 InputFilenames = Args.getAllArgValues(OPT_INPUT);324 if (InputFilenames.empty())325 exitWithError("No input file is specified.");326 Filename = InputFilenames[0];327 328 if (Args.hasArg(OPT_output)) {329 OutputFilename = Args.getLastArgValue(OPT_output);330 for (auto &Filename : InputFilenames)331 if (Filename == OutputFilename)332 exitWithError(333 "Input file name cannot be the same as the output file name!\n");334 }335 336 opt::Arg *ActionArg = nullptr;337 for (opt::Arg *Arg : Args.filtered(OPT_action_group)) {338 if (ActionArg)339 exitWithError("Only one action is allowed.");340 ActionArg = Arg;341 }342 if (!ActionArg)343 exitWithError("One action is required.");344 345 switch (ActionArg->getOption().getID()) {346 case OPT_show:347 if (InputFilenames.size() != 1)348 exitWithError("only one input file is allowed.");349 Action = CGDataAction::Show;350 break;351 case OPT_convert:352 // The default output format is text for convert.353 if (!OutputFormat)354 OutputFormat = CGDataFormat::Text;355 if (InputFilenames.size() != 1)356 exitWithError("only one input file is allowed.");357 Action = CGDataAction::Convert;358 break;359 case OPT_merge:360 // The default output format is binary for merge.361 if (!OutputFormat)362 OutputFormat = CGDataFormat::Binary;363 Action = CGDataAction::Merge;364 break;365 default:366 llvm_unreachable("unrecognized action");367 }368 369 IndexedCodeGenDataLazyLoading =370 Args.hasArg(OPT_indexed_codegen_data_lazy_loading);371}372 373int llvm_cgdata_main(int argc, char **argvNonConst, const llvm::ToolContext &) {374 const char **argv = const_cast<const char **>(argvNonConst);375 parseArgs(argc, argvNonConst);376 377 switch (Action) {378 case CGDataAction::Convert:379 return convert_main(argc, argv);380 case CGDataAction::Merge:381 return merge_main(argc, argv);382 case CGDataAction::Show:383 return show_main(argc, argv);384 }385 386 llvm_unreachable("unrecognized action");387}388