From 7d80b94ca3ab4563c964ebbf3662c0d3033dc91a Mon Sep 17 00:00:00 2001 From: Jessica Paquette Date: Fri, 9 Sep 2022 19:58:02 -0700 Subject: [PATCH] Add a utility for converting between different types of remarks This adds llvm-remarkutil. This is intended to be a general tool for doing stuff with/to remark files. This patch gives it the following powers: * `bitstream2yaml` - To convert bitstream remarks to YAML * `yaml2bitstream` - To convert YAML remarks to bitstream remarks These are both implemented as subcommands, like `llvm-remarkutil bitstream2yaml -o -` I ran into an issue where I had some bitstream remarks coming from CI, and I wanted to be able to do stuff with them (e.g. visualize them). But then I noticed we didn't have any tooling for doing that, so I decided to write this thing. Being able to output YAML as a start seemed like a good idea, since it would allow people to reuse any tooling they may have written based around YAML remarks. Hopefully it can grow into a more featureful remark utility. :) Currently there are is an outstanding performance issue (see the TODO) with the bitstream2yaml case. I decided that I'd keep the tool small to start with and have the yaml2bitstream and bitstream2yaml cases be symmetric. Also I moved the remarks documentation to its own header because it seems a little out of place with "basic commands" and "developer tools"; it's really kind of its own thing. Differential Revision: https://reviews.llvm.org/D133646 --- llvm/docs/CommandGuide/LLVMRemarkUtil.rst | 50 ++++++ llvm/docs/CommandGuide/index.rst | 10 +- llvm/test/CMakeLists.txt | 1 + llvm/test/lit.cfg.py | 2 +- .../tools/llvm-remarkutil/Inputs/broken-remark | 15 ++ .../llvm-remarkutil/Inputs/broken-remark.bitstream | Bin 0 -> 688 bytes llvm/test/tools/llvm-remarkutil/Inputs/empty-file | 0 .../llvm-remarkutil/Inputs/two-remarks.bitstream | Bin 0 -> 500 bytes .../tools/llvm-remarkutil/Inputs/two-remarks.yaml | 16 ++ .../llvm-remarkutil/broken-bitstream-remark.test | 2 + .../tools/llvm-remarkutil/broken-yaml-remark.test | 2 + llvm/test/tools/llvm-remarkutil/convert.test | 20 +++ llvm/test/tools/llvm-remarkutil/empty-file.test | 7 + .../tools/llvm-remarkutil/file-does-not-exist.test | 3 + .../tools/llvm-remarkutil/missing-subcommand.test | 2 + llvm/tools/llvm-remarkutil/CMakeLists.txt | 5 + llvm/tools/llvm-remarkutil/RemarkUtil.cpp | 196 +++++++++++++++++++++ 17 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 llvm/docs/CommandGuide/LLVMRemarkUtil.rst create mode 100644 llvm/test/tools/llvm-remarkutil/Inputs/broken-remark create mode 100644 llvm/test/tools/llvm-remarkutil/Inputs/broken-remark.bitstream create mode 100644 llvm/test/tools/llvm-remarkutil/Inputs/empty-file create mode 100644 llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.bitstream create mode 100644 llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml create mode 100644 llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test create mode 100644 llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test create mode 100644 llvm/test/tools/llvm-remarkutil/convert.test create mode 100644 llvm/test/tools/llvm-remarkutil/empty-file.test create mode 100644 llvm/test/tools/llvm-remarkutil/file-does-not-exist.test create mode 100644 llvm/test/tools/llvm-remarkutil/missing-subcommand.test create mode 100644 llvm/tools/llvm-remarkutil/CMakeLists.txt create mode 100644 llvm/tools/llvm-remarkutil/RemarkUtil.cpp diff --git a/llvm/docs/CommandGuide/LLVMRemarkUtil.rst b/llvm/docs/CommandGuide/LLVMRemarkUtil.rst new file mode 100644 index 0000000..b068dc3 --- /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 da9995a..fc87f15 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 86ca20a..5cf783c 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 75a38b4..1622b80 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -171,7 +171,7 @@ tools.extend([ '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 0000000..e3aaf6a --- /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 0000000000000000000000000000000000000000..4b14f8fee2d0403b9194935b506a3fc69e068231 GIT binary patch literal 688 zcmZWm-AV#c5T4DSqbcDDT%QJ`R8(S@mjx&=xQg;(8WX10{;vOlTYP2rRHAQ5yG z(M9wky+YsYZe;}X&CL1c=ggUND8+~lh)77EPmeXAu?wY^s#TaA0I7J)uMdx-^|DWK z0Uv=U=vtC4T|SO%<;*w5bTT-{yBlu1bvZEI@Vy*K#JWnsij-C>3F<4 obab1W&2bi?@+aeO8y;>WMHKRfBwm9gC8G2E4mjq+Qeu($FQ8^k(*OVf literal 0 HcmV?d00001 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 0000000..e69de29 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 0000000000000000000000000000000000000000..2a528436791aeb4b28ef64cf6f9746804a6047eb GIT binary patch literal 500 zcmWIY4f1B>U|?`%U|?Wp+~lyu$?}4;g^kTYjXllQK-Q9G>nY7P4USejnl1Mnc5JX< z;}B7jlXDb1wNUnuquez{o;M3+o+Qd$dMJCxG0_-`GIVQ0>!}L5nsF|`*=9VH*pX>n!*;9%#CxB)=RPfN@PMM(S{NkVyLb^fbl%ve6 zhcX8mWbY-)9BPDEbg7}wK{3F_rd?->vvo_e%^GHj1&6IK0Nr}n=1lV=#)a*CoUD}= z?Nu4=MFs3t49(RZ>{Slz1`6#(0_{~mmnF29OSBg(XfLT?FTdcjz_}f4o{Fc&iNlsx zoPk~j8kWo?YsH|*!@!^k3>y|8-T=f5%QzVdit=;v(^CsFfh0q4Nn&z#aAs91Lt1HG zvH^o%X)ajEsj?)sn86SzUzC}b&Y(~XQm2pv5>?2|QvfM0$;{7VNG#6PEdVMhNiAaV z%quP_Dg}!>=a=S{fDA(tD`o)dgsVU`2IMqV0R{$UAiae_-~b~dHyg5;$rFJCER5V- GATa>F(5cP< literal 0 HcmV?d00001 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 0000000..ae02fc11 --- /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 0000000..3a81bee --- /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 0000000..d966725 --- /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 0000000..83023c8 --- /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 0000000..96bd187 --- /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 0000000..71ed5ee --- /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 0000000..9f4e9d3 --- /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 0000000..2def6b8 --- /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 0000000..5cb641a --- /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()); +} -- 2.7.4