//===--------- llvm-remarkutil/RemarkUtil.cpp -----------===// // // 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 // //===----------------------------------------------------------------------===// /// Utility for remark files. //===----------------------------------------------------------------------===// #include "llvm-c/Remarks.h" #include "llvm/ADT/StringRef.h" #include "llvm/Remarks/Remark.h" #include "llvm/Remarks/RemarkFormat.h" #include "llvm/Remarks/RemarkParser.h" #include "llvm/Remarks/YAMLRemarkSerializer.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" using namespace llvm; using namespace remarks; static ExitOnError ExitOnErr; static cl::OptionCategory RemarkUtilCategory("llvm-remarkutil options"); namespace subopts { static cl::SubCommand YAML2Bitstream("yaml2bitstream", "Convert YAML remarks to bitstream remarks"); static cl::SubCommand Bitstream2YAML("bitstream2yaml", "Convert bitstream remarks to YAML remarks"); static cl::SubCommand InstructionCount( "instruction-count", "Function instruction count information (requires asm-printer remarks)"); static cl::SubCommand AnnotationCount("annotation-count", "Collect count information from annotation remarks (uses " "AnnotationRemarksPass)"); } // namespace subopts // Keep input + output help + names consistent across the various modes via a // hideous macro. #define INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SUBOPT) \ static cl::opt InputFileName( \ cl::Positional, cl::cat(RemarkUtilCategory), cl::init("-"), \ cl::desc(""), cl::sub(SUBOPT)); \ static cl::opt OutputFileName( \ "o", cl::init("-"), cl::cat(RemarkUtilCategory), cl::desc("Output"), \ cl::value_desc("filename"), cl::sub(SUBOPT)); // Keep Input format and names consistent accross the modes via a macro. #define INPUT_FORMAT_COMMAND_LINE_OPTIONS(SUBOPT) \ static cl::opt InputFormat( \ "parser", cl::desc("Input remark format to parse"), \ cl::values(clEnumValN(Format::YAML, "yaml", "YAML"), \ clEnumValN(Format::Bitstream, "bitstream", "Bitstream")), \ cl::sub(SUBOPT)); #define DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(SUBOPT) \ static cl::opt UseDebugLoc( \ "use-debug-loc", \ cl::desc( \ "Add debug loc information when generating tables for " \ "functions. The loc is represented as (path:line number:column " \ "number)"), \ cl::init(false), cl::sub(SUBOPT)); namespace yaml2bitstream { /// Remark format to parse. static constexpr Format InputFormat = Format::YAML; /// Remark format to output. static constexpr Format OutputFormat = Format::Bitstream; INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::YAML2Bitstream) } // namespace yaml2bitstream namespace bitstream2yaml { /// Remark format to parse. static constexpr Format InputFormat = Format::Bitstream; /// Remark format to output. static constexpr Format OutputFormat = Format::YAML; INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::Bitstream2YAML) } // namespace bitstream2yaml namespace instructioncount { INPUT_FORMAT_COMMAND_LINE_OPTIONS(subopts::InstructionCount) INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::InstructionCount) DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(subopts::InstructionCount) } // namespace instructioncount namespace annotationcount { INPUT_FORMAT_COMMAND_LINE_OPTIONS(subopts::AnnotationCount) static cl::opt AnnotationTypeToCollect( "annotation-type", cl::desc("annotation-type remark to collect count for"), cl::sub(subopts::AnnotationCount)); INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::AnnotationCount) DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(subopts::AnnotationCount) } // namespace annotationcount /// \returns A MemoryBuffer for the input file on success, and an Error /// otherwise. static Expected> getInputMemoryBuffer(StringRef InputFileName) { auto MaybeBuf = MemoryBuffer::getFileOrSTDIN(InputFileName); if (auto ErrorCode = MaybeBuf.getError()) return createStringError(ErrorCode, Twine("Cannot open file '" + InputFileName + "': " + ErrorCode.message())); return std::move(*MaybeBuf); } /// \returns A ToolOutputFile which can be used for outputting the results of /// some tool mode. /// \p OutputFileName is the desired destination. /// \p Flags controls whether or not the file is opened for writing in text /// mode, as a binary, etc. See sys::fs::OpenFlags for more detail. static Expected> getOutputFileWithFlags(StringRef OutputFileName, sys::fs::OpenFlags Flags) { if (OutputFileName == "") OutputFileName = "-"; std::error_code ErrorCode; auto OF = std::make_unique(OutputFileName, ErrorCode, Flags); if (ErrorCode) return errorCodeToError(ErrorCode); return std::move(OF); } /// \returns A ToolOutputFile which can be used for writing remarks on success, /// and an Error otherwise. /// \p OutputFileName is the desired destination. /// \p OutputFormat static Expected> getOutputFileForRemarks(StringRef OutputFileName, Format OutputFormat) { assert((OutputFormat == Format::YAML || OutputFormat == Format::Bitstream) && "Expected one of YAML or Bitstream!"); return getOutputFileWithFlags(OutputFileName, OutputFormat == Format::YAML ? sys::fs::OF_TextWithCRLF : sys::fs::OF_None); } static bool shouldSkipRemark(bool UseDebugLoc, Remark &Remark) { return UseDebugLoc && !Remark.Loc.has_value(); } namespace yaml2bitstream { /// Parses all remarks in the input YAML file. /// \p [out] ParsedRemarks - Filled with remarks parsed from the input file. /// \p [out] StrTab - A string table populated for later remark serialization. /// \returns Error::success() if all remarks were successfully parsed, and an /// Error otherwise. static Error tryParseRemarksFromYAMLFile(std::vector> &ParsedRemarks, StringTable &StrTab) { auto MaybeBuf = getInputMemoryBuffer(InputFileName); if (!MaybeBuf) return MaybeBuf.takeError(); auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer()); if (!MaybeParser) return MaybeParser.takeError(); auto &Parser = **MaybeParser; auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) { StrTab.internalize(**MaybeRemark); ParsedRemarks.push_back(std::move(*MaybeRemark)); } auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); return Error::success(); } /// Reserialize a list of parsed YAML remarks into bitstream remarks. /// \p ParsedRemarks - A list of remarks. /// \p StrTab - The string table for the remarks. /// \returns Error::success() on success. static Error tryReserializeYAML2Bitstream( const std::vector> &ParsedRemarks, StringTable &StrTab) { auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); auto MaybeSerializer = createRemarkSerializer( OutputFormat, SerializerMode::Standalone, OF->os(), std::move(StrTab)); if (!MaybeSerializer) return MaybeSerializer.takeError(); auto Serializer = std::move(*MaybeSerializer); for (const auto &Remark : ParsedRemarks) Serializer->emit(*Remark); OF->keep(); return Error::success(); } /// Parse YAML remarks and reserialize as bitstream remarks. /// \returns Error::success() on success, and an Error otherwise. static Error tryYAML2Bitstream() { StringTable StrTab; std::vector> ParsedRemarks; ExitOnErr(tryParseRemarksFromYAMLFile(ParsedRemarks, StrTab)); return tryReserializeYAML2Bitstream(ParsedRemarks, StrTab); } } // namespace yaml2bitstream namespace bitstream2yaml { /// Parse bitstream remarks and reserialize as YAML remarks. /// \returns An Error if reserialization fails, or Error::success() on success. static Error tryBitstream2YAML() { // Create the serializer. auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); auto MaybeSerializer = createRemarkSerializer( OutputFormat, SerializerMode::Standalone, OF->os()); if (!MaybeSerializer) return MaybeSerializer.takeError(); // Create the parser. auto MaybeBuf = getInputMemoryBuffer(InputFileName); if (!MaybeBuf) return MaybeBuf.takeError(); auto Serializer = std::move(*MaybeSerializer); auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer()); if (!MaybeParser) return MaybeParser.takeError(); auto &Parser = **MaybeParser; // Parse + reserialize all remarks. auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) Serializer->emit(**MaybeRemark); auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); return Error::success(); } } // namespace bitstream2yaml namespace instructioncount { /// Outputs all instruction count remarks in the file as a CSV. /// \returns Error::success() on success, and an Error otherwise. static Error tryInstructionCount() { // Create the output buffer. auto MaybeOF = getOutputFileWithFlags(OutputFileName, /*Flags = */ sys::fs::OF_TextWithCRLF); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); // Create a parser for the user-specified input format. auto MaybeBuf = getInputMemoryBuffer(InputFileName); if (!MaybeBuf) return MaybeBuf.takeError(); auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer()); if (!MaybeParser) return MaybeParser.takeError(); // Emit CSV header. if (UseDebugLoc) OF->os() << "Source,"; OF->os() << "Function,InstructionCount\n"; // Parse all remarks. Whenever we see an instruction count remark, output // the file name and the number of instructions. auto &Parser = **MaybeParser; auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) { auto &Remark = **MaybeRemark; if (Remark.RemarkName != "InstructionCount") continue; if (shouldSkipRemark(UseDebugLoc, Remark)) continue; auto *InstrCountArg = find_if(Remark.Args, [](const Argument &Arg) { return Arg.Key == "NumInstructions"; }); assert(InstrCountArg != Remark.Args.end() && "Expected instruction count remarks to have a NumInstructions key?"); if (UseDebugLoc) { std::string Loc = Remark.Loc->SourceFilePath.str() + ":" + std::to_string(Remark.Loc->SourceLine) + +":" + std::to_string(Remark.Loc->SourceColumn); OF->os() << Loc << ","; } OF->os() << Remark.FunctionName << "," << InstrCountArg->Val << "\n"; } auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); OF->keep(); return Error::success(); } } // namespace instructioncount namespace annotationcount { static Error tryAnnotationCount() { // Create the output buffer. auto MaybeOF = getOutputFileWithFlags(OutputFileName, /*Flags = */ sys::fs::OF_TextWithCRLF); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); // Create a parser for the user-specified input format. auto MaybeBuf = getInputMemoryBuffer(InputFileName); if (!MaybeBuf) return MaybeBuf.takeError(); auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer()); if (!MaybeParser) return MaybeParser.takeError(); // Emit CSV header. if (UseDebugLoc) OF->os() << "Source,"; OF->os() << "Function,Count\n"; // Parse all remarks. When we see the specified remark collect the count // information. auto &Parser = **MaybeParser; auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) { auto &Remark = **MaybeRemark; if (Remark.RemarkName != "AnnotationSummary") continue; if (shouldSkipRemark(UseDebugLoc, Remark)) continue; auto *RemarkNameArg = find_if(Remark.Args, [](const Argument &Arg) { return Arg.Key == "type" && Arg.Val == AnnotationTypeToCollect; }); if (RemarkNameArg == Remark.Args.end()) continue; auto *CountArg = find_if( Remark.Args, [](const Argument &Arg) { return Arg.Key == "count"; }); assert(CountArg != Remark.Args.end() && "Expected annotation-type remark to have a count key?"); if (UseDebugLoc) { std::string Loc = Remark.Loc->SourceFilePath.str() + ":" + std::to_string(Remark.Loc->SourceLine) + +":" + std::to_string(Remark.Loc->SourceColumn); OF->os() << Loc << ","; } OF->os() << Remark.FunctionName << "," << CountArg->Val << "\n"; } auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); OF->keep(); return Error::success(); } } // namespace annotationcount /// Handle user-specified suboptions (e.g. yaml2bitstream, bitstream2yaml). /// \returns An Error if the specified suboption fails or if no suboption was /// specified. Otherwise, Error::success(). static Error handleSuboptions() { if (subopts::Bitstream2YAML) return bitstream2yaml::tryBitstream2YAML(); if (subopts::YAML2Bitstream) return yaml2bitstream::tryYAML2Bitstream(); if (subopts::InstructionCount) return instructioncount::tryInstructionCount(); if (subopts::AnnotationCount) return annotationcount::tryAnnotationCount(); return make_error( "Please specify a subcommand. (See -help for options)", inconvertibleErrorCode()); } int main(int argc, const char **argv) { InitLLVM X(argc, argv); cl::HideUnrelatedOptions(RemarkUtilCategory); cl::ParseCommandLineOptions(argc, argv, "Remark file utilities\n"); ExitOnErr.setBanner(std::string(argv[0]) + ": error: "); ExitOnErr(handleSuboptions()); }