|  | //===-- 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<CGDataFormat> OutputFormat; | 
|  | static std::vector<std::string> 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<CGDataError>()) { | 
|  | 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<std::unique_ptr<object::Binary>> BinOrErr = | 
|  | object::createBinary(Buffer); | 
|  | if (Error E = BinOrErr.takeError()) | 
|  | exitWithError(std::move(E), Filename); | 
|  |  | 
|  | bool Result = true; | 
|  | if (auto *Obj = dyn_cast<ObjectFile>(BinOrErr->get())) { | 
|  | if (Error E = CodeGenDataReader::mergeFromObjectFile( | 
|  | Obj, GlobalOutlineRecord, GlobalFunctionMapRecord)) | 
|  | exitWithError(std::move(E), Filename); | 
|  | } else if (auto *Arch = dyn_cast<Archive>(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<std::unique_ptr<MemoryBuffer>> 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 <action> [options] (<binary files>|<.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<CGDataFormat>(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<const char **>(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"); | 
|  | } |