//===-- llvm-cgdata.cpp - LLVM CodeGen Data Tool --------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // llvm-cgdata parses raw codegen data embedded in compiled binary files, and // merges them into a single .cgdata file. It can also inspect and maninuplate // a .cgdata file. This .cgdata can contain various codegen data like outlining // information, and it can be used to optimize the code in the subsequent build. // //===----------------------------------------------------------------------===// #include "llvm/ADT/StringRef.h" #include "llvm/CGData/CodeGenDataReader.h" #include "llvm/CGData/CodeGenDataWriter.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Object/Archive.h" #include "llvm/Object/Binary.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; using namespace llvm::object; enum CGDataFormat { Invalid, Text, Binary, }; enum CGDataAction { Convert, Merge, Show, }; // Command-line option boilerplate. namespace { enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), #include "Opts.inc" #undef OPTION }; #define OPTTABLE_STR_TABLE_CODE #include "Opts.inc" #undef OPTTABLE_STR_TABLE_CODE #define OPTTABLE_PREFIXES_TABLE_CODE #include "Opts.inc" #undef OPTTABLE_PREFIXES_TABLE_CODE using namespace llvm::opt; static constexpr opt::OptTable::Info InfoTable[] = { #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), #include "Opts.inc" #undef OPTION }; class CGDataOptTable : public opt::GenericOptTable { public: CGDataOptTable() : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {} }; } // end anonymous namespace // Options static StringRef ToolName; static StringRef OutputFilename = "-"; static StringRef Filename; static bool ShowCGDataVersion; static bool SkipTrim; static CGDataAction Action; static std::optional OutputFormat; static std::vector InputFilenames; static void exitWithError(Twine Message, StringRef Whence = "", StringRef Hint = "") { WithColor::error(); if (!Whence.empty()) errs() << Whence << ": "; errs() << Message << "\n"; if (!Hint.empty()) WithColor::note() << Hint << "\n"; ::exit(1); } static void exitWithError(Error E, StringRef Whence = "") { if (E.isA()) { handleAllErrors(std::move(E), [&](const CGDataError &IPE) { exitWithError(IPE.message(), Whence); }); return; } exitWithError(toString(std::move(E)), Whence); } static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") { exitWithError(EC.message(), Whence); } static int convert_main(int argc, const char *argv[]) { std::error_code EC; raw_fd_ostream OS(OutputFilename, EC, OutputFormat == CGDataFormat::Text ? sys::fs::OF_TextWithCRLF : sys::fs::OF_None); if (EC) exitWithErrorCode(EC, OutputFilename); auto FS = vfs::getRealFileSystem(); auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS); if (Error E = ReaderOrErr.takeError()) exitWithError(std::move(E), Filename); CodeGenDataWriter Writer; auto Reader = ReaderOrErr->get(); if (Reader->hasOutlinedHashTree()) { OutlinedHashTreeRecord Record(Reader->releaseOutlinedHashTree()); Writer.addRecord(Record); } if (Reader->hasStableFunctionMap()) { StableFunctionMapRecord Record(Reader->releaseStableFunctionMap()); Writer.addRecord(Record); } if (OutputFormat == CGDataFormat::Text) { if (Error E = Writer.writeText(OS)) exitWithError(std::move(E)); } else { if (Error E = Writer.write(OS)) exitWithError(std::move(E)); } return 0; } static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer, OutlinedHashTreeRecord &GlobalOutlineRecord, StableFunctionMapRecord &GlobalFunctionMapRecord); static bool handleArchive(StringRef Filename, Archive &Arch, OutlinedHashTreeRecord &GlobalOutlineRecord, StableFunctionMapRecord &GlobalFunctionMapRecord) { bool Result = true; Error Err = Error::success(); for (const auto &Child : Arch.children(Err)) { auto BuffOrErr = Child.getMemoryBufferRef(); if (Error E = BuffOrErr.takeError()) exitWithError(std::move(E), Filename); auto NameOrErr = Child.getName(); if (Error E = NameOrErr.takeError()) exitWithError(std::move(E), Filename); std::string Name = (Filename + "(" + NameOrErr.get() + ")").str(); Result &= handleBuffer(Name, BuffOrErr.get(), GlobalOutlineRecord, GlobalFunctionMapRecord); } if (Err) exitWithError(std::move(Err), Filename); return Result; } static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer, OutlinedHashTreeRecord &GlobalOutlineRecord, StableFunctionMapRecord &GlobalFunctionMapRecord) { Expected> BinOrErr = object::createBinary(Buffer); if (Error E = BinOrErr.takeError()) exitWithError(std::move(E), Filename); bool Result = true; if (auto *Obj = dyn_cast(BinOrErr->get())) { if (Error E = CodeGenDataReader::mergeFromObjectFile( Obj, GlobalOutlineRecord, GlobalFunctionMapRecord)) exitWithError(std::move(E), Filename); } else if (auto *Arch = dyn_cast(BinOrErr->get())) { Result &= handleArchive(Filename, *Arch, GlobalOutlineRecord, GlobalFunctionMapRecord); } else { // TODO: Support for the MachO universal binary format. errs() << "Error: unsupported binary file: " << Filename << "\n"; Result = false; } return Result; } static bool handleFile(StringRef Filename, OutlinedHashTreeRecord &GlobalOutlineRecord, StableFunctionMapRecord &GlobalFunctionMapRecord) { ErrorOr> BuffOrErr = MemoryBuffer::getFileOrSTDIN(Filename); if (std::error_code EC = BuffOrErr.getError()) exitWithErrorCode(EC, Filename); return handleBuffer(Filename, *BuffOrErr.get(), GlobalOutlineRecord, GlobalFunctionMapRecord); } static int merge_main(int argc, const char *argv[]) { bool Result = true; OutlinedHashTreeRecord GlobalOutlineRecord; StableFunctionMapRecord GlobalFunctionMapRecord; for (auto &Filename : InputFilenames) Result &= handleFile(Filename, GlobalOutlineRecord, GlobalFunctionMapRecord); if (!Result) exitWithError("failed to merge codegen data files."); GlobalFunctionMapRecord.finalize(SkipTrim); CodeGenDataWriter Writer; if (!GlobalOutlineRecord.empty()) Writer.addRecord(GlobalOutlineRecord); if (!GlobalFunctionMapRecord.empty()) Writer.addRecord(GlobalFunctionMapRecord); std::error_code EC; raw_fd_ostream OS(OutputFilename, EC, OutputFormat == CGDataFormat::Text ? sys::fs::OF_TextWithCRLF : sys::fs::OF_None); if (EC) exitWithErrorCode(EC, OutputFilename); if (OutputFormat == CGDataFormat::Text) { if (Error E = Writer.writeText(OS)) exitWithError(std::move(E)); } else { if (Error E = Writer.write(OS)) exitWithError(std::move(E)); } return 0; } static int show_main(int argc, const char *argv[]) { std::error_code EC; raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF); if (EC) exitWithErrorCode(EC, OutputFilename); auto FS = vfs::getRealFileSystem(); auto ReaderOrErr = CodeGenDataReader::create(Filename, *FS); if (Error E = ReaderOrErr.takeError()) exitWithError(std::move(E), Filename); auto Reader = ReaderOrErr->get(); if (ShowCGDataVersion) OS << "Version: " << Reader->getVersion() << "\n"; if (Reader->hasOutlinedHashTree()) { auto Tree = Reader->releaseOutlinedHashTree(); OS << "Outlined hash tree:\n"; OS << " Total Node Count: " << Tree->size() << "\n"; OS << " Terminal Node Count: " << Tree->size(/*GetTerminalCountOnly=*/true) << "\n"; OS << " Depth: " << Tree->depth() << "\n"; } if (Reader->hasStableFunctionMap()) { auto Map = Reader->releaseStableFunctionMap(); OS << "Stable function map:\n"; OS << " Unique hash Count: " << Map->size() << "\n"; OS << " Total function Count: " << Map->size(StableFunctionMap::TotalFunctionCount) << "\n"; OS << " Mergeable function Count: " << Map->size(StableFunctionMap::MergeableFunctionCount) << "\n"; } return 0; } static void parseArgs(int argc, char **argv) { CGDataOptTable Tbl; ToolName = argv[0]; llvm::BumpPtrAllocator A; llvm::StringSaver Saver{A}; llvm::opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { llvm::errs() << Msg << '\n'; std::exit(1); }); if (Args.hasArg(OPT_help)) { Tbl.printHelp( llvm::outs(), "llvm-cgdata [options] (|<.cgdata file>)", ToolName.str().c_str()); std::exit(0); } if (Args.hasArg(OPT_version)) { cl::PrintVersionMessage(); std::exit(0); } ShowCGDataVersion = Args.hasArg(OPT_cgdata_version); SkipTrim = Args.hasArg(OPT_skip_trim); if (opt::Arg *A = Args.getLastArg(OPT_format)) { StringRef OF = A->getValue(); OutputFormat = StringSwitch(OF) .Case("text", CGDataFormat::Text) .Case("binary", CGDataFormat::Binary) .Default(CGDataFormat::Invalid); if (OutputFormat == CGDataFormat::Invalid) exitWithError("unsupported format '" + OF + "'"); } InputFilenames = Args.getAllArgValues(OPT_INPUT); if (InputFilenames.empty()) exitWithError("No input file is specified."); Filename = InputFilenames[0]; if (Args.hasArg(OPT_output)) { OutputFilename = Args.getLastArgValue(OPT_output); for (auto &Filename : InputFilenames) if (Filename == OutputFilename) exitWithError( "Input file name cannot be the same as the output file name!\n"); } opt::Arg *ActionArg = nullptr; for (opt::Arg *Arg : Args.filtered(OPT_action_group)) { if (ActionArg) exitWithError("Only one action is allowed."); ActionArg = Arg; } if (!ActionArg) exitWithError("One action is required."); switch (ActionArg->getOption().getID()) { case OPT_show: if (InputFilenames.size() != 1) exitWithError("only one input file is allowed."); Action = CGDataAction::Show; break; case OPT_convert: // The default output format is text for convert. if (!OutputFormat) OutputFormat = CGDataFormat::Text; if (InputFilenames.size() != 1) exitWithError("only one input file is allowed."); Action = CGDataAction::Convert; break; case OPT_merge: // The default output format is binary for merge. if (!OutputFormat) OutputFormat = CGDataFormat::Binary; Action = CGDataAction::Merge; break; default: llvm_unreachable("unrecognized action"); } } int llvm_cgdata_main(int argc, char **argvNonConst, const llvm::ToolContext &) { const char **argv = const_cast(argvNonConst); parseArgs(argc, argvNonConst); switch (Action) { case CGDataAction::Convert: return convert_main(argc, argv); case CGDataAction::Merge: return merge_main(argc, argv); case CGDataAction::Show: return show_main(argc, argv); } llvm_unreachable("unrecognized action"); }