Skip to content

Commit

Permalink
Add a utility for converting between different types of remarks
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Jessica Paquette committed Sep 12, 2022
1 parent d90f7cb commit 7d80b94
Show file tree
Hide file tree
Showing 17 changed files with 329 additions and 2 deletions.
50 changes: 50 additions & 0 deletions llvm/docs/CommandGuide/LLVMRemarkUtil.rst
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 9 additions & 1 deletion llvm/docs/CommandGuide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ Basic Commands
llvm-otool
llvm-profdata
llvm-readobj
llvm-remark-size-diff
llvm-stress
llvm-symbolizer
opt
Expand Down Expand Up @@ -86,3 +85,12 @@ Developer Tools
llvm-pdbutil
llvm-profgen
llvm-tli-checker

Remarks Tools
~~~~~~~~~~~~~~

.. toctree::
:maxdepth: 1

llvm-remark-size-diff
llvm-remarkutil
1 change: 1 addition & 0 deletions llvm/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ set(LLVM_TEST_DEPENDS
llvm-readelf
llvm-reduce
llvm-remark-size-diff
llvm-remarkutil
llvm-rtdyld
llvm-sim
llvm-size
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
15 changes: 15 additions & 0 deletions llvm/test/tools/llvm-remarkutil/Inputs/broken-remark
Original file line number Diff line number Diff line change
@@ -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'
...
Binary file not shown.
Empty file.
Binary file not shown.
16 changes: 16 additions & 0 deletions llvm/test/tools/llvm-remarkutil/Inputs/two-remarks.yaml
Original file line number Diff line number Diff line change
@@ -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'
...
2 changes: 2 additions & 0 deletions llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test
Original file line number Diff line number Diff line change
@@ -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 --- .
2 changes: 2 additions & 0 deletions llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions llvm/test/tools/llvm-remarkutil/convert.test
Original file line number Diff line number Diff line change
@@ -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: ...
7 changes: 7 additions & 0 deletions llvm/test/tools/llvm-remarkutil/empty-file.test
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions llvm/test/tools/llvm-remarkutil/file-does-not-exist.test
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions llvm/test/tools/llvm-remarkutil/missing-subcommand.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
RUN: not llvm-remarkutil 2>&1 | FileCheck %s
CHECK: error: Please specify a subcommand. (See -help for options)
5 changes: 5 additions & 0 deletions llvm/tools/llvm-remarkutil/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
set(LLVM_LINK_COMPONENTS Core Demangle Object Remarks Support)

add_llvm_tool(llvm-remarkutil
RemarkUtil.cpp
)
196 changes: 196 additions & 0 deletions llvm/tools/llvm-remarkutil/RemarkUtil.cpp
Original file line number Diff line number Diff line change
@@ -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());
}

0 comments on commit 7d80b94

Please sign in to comment.