diff --git a/llvm/docs/CommandGuide/LLVMRemarkUtil.rst b/llvm/docs/CommandGuide/LLVMRemarkUtil.rst new file mode 100644 index 0000000000000..b068dc318f2c6 --- /dev/null +++ b/llvm/docs/CommandGuide/LLVMRemarkUtil.rst @@ -0,0 +1,50 @@ +llvm-remarkutil - +============================================================== + +.. program:: llvm-remarkutil + +SYNOPSIS +-------- + +:program:`llvm-remarkutil` [*subcommmand*] [*options*] + +DESCRIPTION +----------- + +Utility for displaying information from, and converting between different +`remark `_ formats. + +Subcommands +----------- + + * :ref:`bitstream2yaml_subcommand` - Reserialize bitstream remarks to YAML. + * :ref:`yaml2bitstream_subcommand` - Reserialize YAML remarks to bitstream. + +.. _bitstream2yaml_subcommand: + +bitstream2yaml +~~~~~~ + +.. program:: llvm-remarkutil bitstream2yaml + +USAGE: :program:`llvm-remarkutil` bitstream2yaml -o + +Summary +^^^^^^^^^^^ + +Takes a bitstream remark file as input, and reserializes that file as YAML. + +.. _yaml2bitstream_subcommand: + +yaml2bitstream +~~~~~~ + +.. program:: llvm-remarkutil yaml2bitstream + +USAGE: :program:`llvm-remarkutil` yaml2bitstream -o + +Summary +^^^^^^^^^^^ + +Takes a YAML remark file as input, and reserializes that file in the bitstream +format. diff --git a/llvm/docs/CommandGuide/index.rst b/llvm/docs/CommandGuide/index.rst index da9995a5a3e4f..fc87f15c9d588 100644 --- a/llvm/docs/CommandGuide/index.rst +++ b/llvm/docs/CommandGuide/index.rst @@ -33,7 +33,6 @@ Basic Commands llvm-otool llvm-profdata llvm-readobj - llvm-remark-size-diff llvm-stress llvm-symbolizer opt @@ -86,3 +85,12 @@ Developer Tools llvm-pdbutil llvm-profgen llvm-tli-checker + +Remarks Tools +~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + llvm-remark-size-diff + llvm-remarkutil diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt index 86ca20ada7b80..5cf783c175842 100644 --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -118,6 +118,7 @@ set(LLVM_TEST_DEPENDS llvm-readelf llvm-reduce llvm-remark-size-diff + llvm-remarkutil llvm-rtdyld llvm-sim llvm-size diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py index 75a38b4c5dad5..1622b80356e5d 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -171,7 +171,7 @@ def get_asan_rtlib(): 'llvm-tblgen', 'llvm-tapi-diff', 'llvm-undname', 'llvm-windres', 'llvm-c-test', 'llvm-cxxfilt', 'llvm-xray', 'yaml2obj', 'obj2yaml', 'yaml-bench', 'verify-uselistorder', 'bugpoint', 'llc', 'llvm-symbolizer', - 'opt', 'sancov', 'sanstats']) + 'opt', 'sancov', 'sanstats', 'llvm-remarkutil']) # The following tools are optional tools.extend([ diff --git a/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark b/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark new file mode 100644 index 0000000000000..e3aaf6a440895 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark @@ -0,0 +1,15 @@ +--- !Analysis +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: '1' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark.bitstream b/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark.bitstream new file mode 100644 index 0000000000000..4b14f8fee2d04 Binary files /dev/null and b/llvm/test/tools/llvm-remarkutil/Inputs/broken-remark.bitstream differ diff --git a/llvm/test/tools/llvm-remarkutil/Inputs/empty-file b/llvm/test/tools/llvm-remarkutil/Inputs/empty-file new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.bitstream b/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.bitstream new file mode 100644 index 0000000000000..2a528436791ae Binary files /dev/null and b/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.bitstream differ diff --git a/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml b/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml new file mode 100644 index 0000000000000..ae02fc1102d0b --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml @@ -0,0 +1,16 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: '1' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test new file mode 100644 index 0000000000000..3a81beed03333 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test @@ -0,0 +1,2 @@ +RUN: not llvm-remarkutil bitstream2yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s +CHECK: error: Unknown magic number: expecting RMRK, got --- . diff --git a/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test b/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test new file mode 100644 index 0000000000000..d966725d63ac1 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test @@ -0,0 +1,2 @@ +RUN: not llvm-remarkutil yaml2bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s +CHECK: error: Type, Pass, Name or Function missing diff --git a/llvm/test/tools/llvm-remarkutil/convert.test b/llvm/test/tools/llvm-remarkutil/convert.test new file mode 100644 index 0000000000000..83023c8ce6a89 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/convert.test @@ -0,0 +1,20 @@ +RUN: llvm-remarkutil bitstream2yaml %p/Inputs/two-remarks.bitstream -o - | FileCheck %s -strict-whitespace +RUN: llvm-remarkutil yaml2bitstream %p/Inputs/two-remarks.yaml -o %t +RUN: llvm-remarkutil bitstream2yaml %t -o - | FileCheck %s -strict-whitespace + +; CHECK: --- !Analysis +; CHECK-NEXT: Pass: prologepilog +; CHECK-NEXT: Name: StackSize +; CHECK-NEXT: Function: func0 +; CHECK-NEXT: Args: +; CHECK-NEXT: - NumStackBytes: '1' +; CHECK-NEXT: - String: ' stack bytes in function' +; CHECK-NEXT: ... +; CHECK-NEXT: --- !Analysis +; CHECK-NEXT: Pass: asm-printer +; CHECK-NEXT: Name: InstructionCount +; CHECK-NEXT: Function: func0 +; CHECK-NEXT: Args: +; CHECK-NEXT: - NumInstructions: '1' +; CHECK-NEXT: - String: ' instructions in function' +; CHECK-NEXT: ... diff --git a/llvm/test/tools/llvm-remarkutil/empty-file.test b/llvm/test/tools/llvm-remarkutil/empty-file.test new file mode 100644 index 0000000000000..96bd1871b2410 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/empty-file.test @@ -0,0 +1,7 @@ +RUN: not llvm-remarkutil yaml2bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAML2BITSTREAM +RUN: llvm-remarkutil bitstream2yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=BITSTREAM2YAML + +; YAML2BITSTREAM: error: document root is not of mapping type. + +; An empty bitstream file is valid. +; BITSTREAM2YAML-NOT: error diff --git a/llvm/test/tools/llvm-remarkutil/file-does-not-exist.test b/llvm/test/tools/llvm-remarkutil/file-does-not-exist.test new file mode 100644 index 0000000000000..71ed5ee9453a7 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/file-does-not-exist.test @@ -0,0 +1,3 @@ +RUN: not llvm-remarkutil bitstream2yaml %p/Inputs/i-do-not-exist -o - 2>&1 | FileCheck %s +RUN: not llvm-remarkutil yaml2bitstream %p/Inputs/i-do-not-exist -o - 2>&1 | FileCheck %s +CHECK: error: Cannot open file diff --git a/llvm/test/tools/llvm-remarkutil/missing-subcommand.test b/llvm/test/tools/llvm-remarkutil/missing-subcommand.test new file mode 100644 index 0000000000000..9f4e9d3146643 --- /dev/null +++ b/llvm/test/tools/llvm-remarkutil/missing-subcommand.test @@ -0,0 +1,2 @@ +RUN: not llvm-remarkutil 2>&1 | FileCheck %s +CHECK: error: Please specify a subcommand. (See -help for options) diff --git a/llvm/tools/llvm-remarkutil/CMakeLists.txt b/llvm/tools/llvm-remarkutil/CMakeLists.txt new file mode 100644 index 0000000000000..2def6b8fc0bdb --- /dev/null +++ b/llvm/tools/llvm-remarkutil/CMakeLists.txt @@ -0,0 +1,5 @@ +set(LLVM_LINK_COMPONENTS Core Demangle Object Remarks Support) + +add_llvm_tool(llvm-remarkutil + RemarkUtil.cpp +) diff --git a/llvm/tools/llvm-remarkutil/RemarkUtil.cpp b/llvm/tools/llvm-remarkutil/RemarkUtil.cpp new file mode 100644 index 0000000000000..5cb641a0ba014 --- /dev/null +++ b/llvm/tools/llvm-remarkutil/RemarkUtil.cpp @@ -0,0 +1,196 @@ +//===--------- 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"); +} // namespace subopts + +// Conversions have the same command line options. AFAIK there is no way to +// reuse them, so to avoid duplication, let's just stick this in a hideous +// macro. +#define CONVERSION_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)); +namespace yaml2bitstream { +/// Remark format to parse. +static constexpr Format InputFormat = Format::YAML; +/// Remark format to output. +static constexpr Format OutputFormat = Format::Bitstream; +CONVERSION_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; +CONVERSION_COMMAND_LINE_OPTIONS(subopts::Bitstream2YAML) +} // namespace bitstream2yaml + +/// \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); +} + +/// Parses all remarks in the input file. +/// \p [out] StrTab - A string table populated for later remark serialization. +/// \returns A vector of parsed remarks on success, and an Error otherwise. +static Expected>> +tryParseRemarksFromInputFile(StringRef InputFileName, Format InputFormat, + 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(); + // TODO: If we are converting from bitstream to YAML, we don't need to parse + // early because the string table is not necessary. + std::vector> ParsedRemarks; + for (; MaybeRemark; MaybeRemark = Parser.next()) { + StrTab.internalize(**MaybeRemark); + ParsedRemarks.push_back(std::move(*MaybeRemark)); + } + auto E = MaybeRemark.takeError(); + if (!E.isA()) + return std::move(E); + consumeError(std::move(E)); + return ParsedRemarks; +} + +/// \returns A ToolOutputFile which can be used for writing remarks on success, +/// and an Error otherwise. +static Expected> +getOutputFile(StringRef OutputFileName, Format OutputFormat) { + if (OutputFileName == "") + OutputFileName = "-"; + auto Flags = OutputFormat == Format::YAML ? sys::fs::OF_TextWithCRLF + : sys::fs::OF_None; + std::error_code ErrorCode; + auto OF = std::make_unique(OutputFileName, ErrorCode, Flags); + if (ErrorCode) + return errorCodeToError(ErrorCode); + return std::move(OF); +} + +/// Reserialize a list of remarks into the desired output format, and output +/// to the user-specified output file. +/// \p ParsedRemarks - A list of remarks. +/// \p StrTab - The string table for the remarks. +/// \returns Error::success() on success. +static Error tryReserializeParsedRemarks( + StringRef OutputFileName, Format OutputFormat, + const std::vector> &ParsedRemarks, + StringTable &StrTab) { + auto MaybeOF = getOutputFile(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(); +} + +/// Parses remarks in the input format, and reserializes them in the desired +/// output format. +/// \returns Error::success() on success, and an Error otherwise. +static Error tryReserialize(StringRef InputFileName, StringRef OutputFileName, + Format InputFormat, Format OutputFormat) { + StringTable StrTab; + auto MaybeParsedRemarks = + tryParseRemarksFromInputFile(InputFileName, InputFormat, StrTab); + if (!MaybeParsedRemarks) + return MaybeParsedRemarks.takeError(); + return tryReserializeParsedRemarks(OutputFileName, OutputFormat, + *MaybeParsedRemarks, StrTab); +} + +/// Reserialize bitstream remarks as YAML remarks. +/// \returns An Error if reserialization fails, or Error::success() on success. +static Error tryBitstream2YAML() { + // Use the namespace to get the correct command line globals. + using namespace bitstream2yaml; + return tryReserialize(InputFileName, OutputFileName, InputFormat, + OutputFormat); +} + +/// Reserialize YAML remarks as bitstream remarks. +/// \returns An Error if reserialization fails, or Error::success() on success. +static Error tryYAML2Bitstream() { + // Use the namespace to get the correct command line globals. + using namespace yaml2bitstream; + return tryReserialize(InputFileName, OutputFileName, InputFormat, + OutputFormat); +} + +/// 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 tryBitstream2YAML(); + if (subopts::YAML2Bitstream) + return tryYAML2Bitstream(); + 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()); +}