Add a utility for converting between different types of remarks
authorJessica Paquette <jpaquette@apple.com>
Sat, 10 Sep 2022 02:58:02 +0000 (19:58 -0700)
committerJessica Paquette <jpaquette@apple.com>
Mon, 12 Sep 2022 22:04:19 +0000 (15:04 -0700)
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 <input_file> -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

17 files changed:
llvm/docs/CommandGuide/LLVMRemarkUtil.rst [new file with mode: 0644]
llvm/docs/CommandGuide/index.rst
llvm/test/CMakeLists.txt
llvm/test/lit.cfg.py
llvm/test/tools/llvm-remarkutil/Inputs/broken-remark [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/Inputs/broken-remark.bitstream [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/Inputs/empty-file [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.bitstream [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/convert.test [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/empty-file.test [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/file-does-not-exist.test [new file with mode: 0644]
llvm/test/tools/llvm-remarkutil/missing-subcommand.test [new file with mode: 0644]
llvm/tools/llvm-remarkutil/CMakeLists.txt [new file with mode: 0644]
llvm/tools/llvm-remarkutil/RemarkUtil.cpp [new file with mode: 0644]

diff --git a/llvm/docs/CommandGuide/LLVMRemarkUtil.rst b/llvm/docs/CommandGuide/LLVMRemarkUtil.rst
new file mode 100644 (file)
index 0000000..b068dc3
--- /dev/null
@@ -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 <https://llvm.org/docs/Remarks.html>`_ 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 <input file> -o <output file>
+
+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 <input file> -o <output file>
+
+Summary
+^^^^^^^^^^^
+
+Takes a YAML remark file as input, and reserializes that file in the bitstream
+format.
index da9995a..fc87f15 100644 (file)
@@ -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
index 86ca20a..5cf783c 100644 (file)
@@ -118,6 +118,7 @@ set(LLVM_TEST_DEPENDS
           llvm-readelf
           llvm-reduce
           llvm-remark-size-diff
+          llvm-remarkutil
           llvm-rtdyld
           llvm-sim
           llvm-size
index 75a38b4..1622b80 100644 (file)
@@ -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 (file)
index 0000000..e3aaf6a
--- /dev/null
@@ -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 (file)
index 0000000..4b14f8f
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 (file)
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 (file)
index 0000000..2a52843
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 (file)
index 0000000..ae02fc1
--- /dev/null
@@ -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 (file)
index 0000000..3a81bee
--- /dev/null
@@ -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 (file)
index 0000000..d966725
--- /dev/null
@@ -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 (file)
index 0000000..83023c8
--- /dev/null
@@ -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 (file)
index 0000000..96bd187
--- /dev/null
@@ -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 (file)
index 0000000..71ed5ee
--- /dev/null
@@ -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 (file)
index 0000000..9f4e9d3
--- /dev/null
@@ -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 (file)
index 0000000..2def6b8
--- /dev/null
@@ -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 (file)
index 0000000..5cb641a
--- /dev/null
@@ -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<std::string> InputFileName(                                   \
+      cl::Positional, cl::cat(RemarkUtilCategory), cl::init("-"),              \
+      cl::desc("<input file>"), cl::sub(SUBOPT));                              \
+  static cl::opt<std::string> 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<std::unique_ptr<MemoryBuffer>>
+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<std::vector<std::unique_ptr<Remark>>>
+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<std::unique_ptr<Remark>> ParsedRemarks;
+  for (; MaybeRemark; MaybeRemark = Parser.next()) {
+    StrTab.internalize(**MaybeRemark);
+    ParsedRemarks.push_back(std::move(*MaybeRemark));
+  }
+  auto E = MaybeRemark.takeError();
+  if (!E.isA<EndOfFileError>())
+    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<std::unique_ptr<ToolOutputFile>>
+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<ToolOutputFile>(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<std::unique_ptr<Remark>> &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<StringError>(
+      "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());
+}