1 //===--------- llvm-remarkutil/RemarkUtil.cpp -----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 /// Utility for remark files.
9 //===----------------------------------------------------------------------===//
10 
11 #include "llvm-c/Remarks.h"
12 #include "llvm/ADT/StringRef.h"
13 #include "llvm/Remarks/Remark.h"
14 #include "llvm/Remarks/RemarkFormat.h"
15 #include "llvm/Remarks/RemarkParser.h"
16 #include "llvm/Remarks/YAMLRemarkSerializer.h"
17 #include "llvm/Support/CommandLine.h"
18 #include "llvm/Support/Compiler.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Support/FileSystem.h"
21 #include "llvm/Support/InitLLVM.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/ToolOutputFile.h"
24 #include "llvm/Support/WithColor.h"
25 
26 using namespace llvm;
27 using namespace remarks;
28 
29 static ExitOnError ExitOnErr;
30 static cl::OptionCategory RemarkUtilCategory("llvm-remarkutil options");
31 namespace subopts {
32 static cl::SubCommand
33     YAML2Bitstream("yaml2bitstream",
34                    "Convert YAML remarks to bitstream remarks");
35 static cl::SubCommand
36     Bitstream2YAML("bitstream2yaml",
37                    "Convert bitstream remarks to YAML remarks");
38 static cl::SubCommand InstructionCount(
39     "instruction-count",
40     "Function instruction count information (requires asm-printer remarks)");
41 } // namespace subopts
42 
43 // Keep input + output help + names consistent across the various modes via a
44 // hideous macro.
45 #define INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SUBOPT)                              \
46   static cl::opt<std::string> InputFileName(                                   \
47       cl::Positional, cl::cat(RemarkUtilCategory), cl::init("-"),              \
48       cl::desc("<input file>"), cl::sub(SUBOPT));                              \
49   static cl::opt<std::string> OutputFileName(                                  \
50       "o", cl::init("-"), cl::cat(RemarkUtilCategory), cl::desc("Output"),     \
51       cl::value_desc("filename"), cl::sub(SUBOPT));
52 namespace yaml2bitstream {
53 /// Remark format to parse.
54 static constexpr Format InputFormat = Format::YAML;
55 /// Remark format to output.
56 static constexpr Format OutputFormat = Format::Bitstream;
57 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::YAML2Bitstream)
58 } // namespace yaml2bitstream
59 
60 namespace bitstream2yaml {
61 /// Remark format to parse.
62 static constexpr Format InputFormat = Format::Bitstream;
63 /// Remark format to output.
64 static constexpr Format OutputFormat = Format::YAML;
65 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::Bitstream2YAML)
66 } // namespace bitstream2yaml
67 
68 namespace instructioncount {
69 static cl::opt<Format> InputFormat(
70     "parser", cl::desc("Input remark format to parse"),
71     cl::values(clEnumValN(Format::YAML, "yaml", "YAML"),
72                clEnumValN(Format::Bitstream, "bitstream", "Bitstream")),
73     cl::sub(subopts::InstructionCount));
74 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::InstructionCount)
75 } // namespace instructioncount
76 
77 /// \returns A MemoryBuffer for the input file on success, and an Error
78 /// otherwise.
79 static Expected<std::unique_ptr<MemoryBuffer>>
80 getInputMemoryBuffer(StringRef InputFileName) {
81   auto MaybeBuf = MemoryBuffer::getFileOrSTDIN(InputFileName);
82   if (auto ErrorCode = MaybeBuf.getError())
83     return createStringError(ErrorCode,
84                              Twine("Cannot open file '" + InputFileName +
85                                    "': " + ErrorCode.message()));
86   return std::move(*MaybeBuf);
87 }
88 
89 /// \returns A ToolOutputFile which can be used for outputting the results of
90 /// some tool mode.
91 /// \p OutputFileName is the desired destination.
92 /// \p Flags controls whether or not the file is opened for writing in text
93 /// mode, as a binary, etc. See sys::fs::OpenFlags for more detail.
94 static Expected<std::unique_ptr<ToolOutputFile>>
95 getOutputFileWithFlags(StringRef OutputFileName, sys::fs::OpenFlags Flags) {
96   if (OutputFileName == "")
97     OutputFileName = "-";
98   std::error_code ErrorCode;
99   auto OF = std::make_unique<ToolOutputFile>(OutputFileName, ErrorCode, Flags);
100   if (ErrorCode)
101     return errorCodeToError(ErrorCode);
102   return std::move(OF);
103 }
104 
105 /// \returns A ToolOutputFile which can be used for writing remarks on success,
106 /// and an Error otherwise.
107 /// \p OutputFileName is the desired destination.
108 /// \p OutputFormat
109 static Expected<std::unique_ptr<ToolOutputFile>>
110 getOutputFileForRemarks(StringRef OutputFileName, Format OutputFormat) {
111   assert((OutputFormat == Format::YAML || OutputFormat == Format::Bitstream) &&
112          "Expected one of YAML or Bitstream!");
113   return getOutputFileWithFlags(OutputFileName, OutputFormat == Format::YAML
114                                                     ? sys::fs::OF_TextWithCRLF
115                                                     : sys::fs::OF_None);
116 }
117 
118 namespace yaml2bitstream {
119 /// Parses all remarks in the input YAML file.
120 /// \p [out] ParsedRemarks - Filled with remarks parsed from the input file.
121 /// \p [out] StrTab - A string table populated for later remark serialization.
122 /// \returns Error::success() if all remarks were successfully parsed, and an
123 /// Error otherwise.
124 static Error
125 tryParseRemarksFromYAMLFile(std::vector<std::unique_ptr<Remark>> &ParsedRemarks,
126                             StringTable &StrTab) {
127   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
128   if (!MaybeBuf)
129     return MaybeBuf.takeError();
130   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
131   if (!MaybeParser)
132     return MaybeParser.takeError();
133   auto &Parser = **MaybeParser;
134   auto MaybeRemark = Parser.next();
135   for (; MaybeRemark; MaybeRemark = Parser.next()) {
136     StrTab.internalize(**MaybeRemark);
137     ParsedRemarks.push_back(std::move(*MaybeRemark));
138   }
139   auto E = MaybeRemark.takeError();
140   if (!E.isA<EndOfFileError>())
141     return E;
142   consumeError(std::move(E));
143   return Error::success();
144 }
145 
146 /// Reserialize a list of parsed YAML remarks into bitstream remarks.
147 /// \p ParsedRemarks - A list of remarks.
148 /// \p StrTab - The string table for the remarks.
149 /// \returns Error::success() on success.
150 static Error tryReserializeYAML2Bitstream(
151     const std::vector<std::unique_ptr<Remark>> &ParsedRemarks,
152     StringTable &StrTab) {
153   auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat);
154   if (!MaybeOF)
155     return MaybeOF.takeError();
156   auto OF = std::move(*MaybeOF);
157   auto MaybeSerializer = createRemarkSerializer(
158       OutputFormat, SerializerMode::Standalone, OF->os(), std::move(StrTab));
159   if (!MaybeSerializer)
160     return MaybeSerializer.takeError();
161   auto Serializer = std::move(*MaybeSerializer);
162   for (const auto &Remark : ParsedRemarks)
163     Serializer->emit(*Remark);
164   OF->keep();
165   return Error::success();
166 }
167 
168 /// Parse YAML remarks and reserialize as bitstream remarks.
169 /// \returns Error::success() on success, and an Error otherwise.
170 static Error tryYAML2Bitstream() {
171   StringTable StrTab;
172   std::vector<std::unique_ptr<Remark>> ParsedRemarks;
173   ExitOnErr(tryParseRemarksFromYAMLFile(ParsedRemarks, StrTab));
174   return tryReserializeYAML2Bitstream(ParsedRemarks, StrTab);
175 }
176 } // namespace yaml2bitstream
177 
178 namespace bitstream2yaml {
179 /// Parse bitstream remarks and reserialize as YAML remarks.
180 /// \returns An Error if reserialization fails, or Error::success() on success.
181 static Error tryBitstream2YAML() {
182   // Create the serializer.
183   auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat);
184   if (!MaybeOF)
185     return MaybeOF.takeError();
186   auto OF = std::move(*MaybeOF);
187   auto MaybeSerializer = createRemarkSerializer(
188       OutputFormat, SerializerMode::Standalone, OF->os());
189   if (!MaybeSerializer)
190     return MaybeSerializer.takeError();
191 
192   // Create the parser.
193   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
194   if (!MaybeBuf)
195     return MaybeBuf.takeError();
196   auto Serializer = std::move(*MaybeSerializer);
197   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
198   if (!MaybeParser)
199     return MaybeParser.takeError();
200   auto &Parser = **MaybeParser;
201 
202   // Parse + reserialize all remarks.
203   auto MaybeRemark = Parser.next();
204   for (; MaybeRemark; MaybeRemark = Parser.next())
205     Serializer->emit(**MaybeRemark);
206   auto E = MaybeRemark.takeError();
207   if (!E.isA<EndOfFileError>())
208     return E;
209   consumeError(std::move(E));
210   return Error::success();
211 }
212 } // namespace bitstream2yaml
213 
214 namespace instructioncount {
215 /// Outputs all instruction count remarks in the file as a CSV.
216 /// \returns Error::success() on success, and an Error otherwise.
217 static Error tryInstructionCount() {
218   // Create the output buffer.
219   auto MaybeOF = getOutputFileWithFlags(OutputFileName,
220                                         /*Flags = */ sys::fs::OF_TextWithCRLF);
221   if (!MaybeOF)
222     return MaybeOF.takeError();
223   auto OF = std::move(*MaybeOF);
224   // Create a parser for the user-specified input format.
225   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
226   if (!MaybeBuf)
227     return MaybeBuf.takeError();
228   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
229   if (!MaybeParser)
230     return MaybeParser.takeError();
231   // Emit CSV header.
232   OF->os() << "Function,InstructionCount\n";
233   // Parse all remarks. Whenever we see an instruction count remark, output
234   // the file name and the number of instructions.
235   auto &Parser = **MaybeParser;
236   auto MaybeRemark = Parser.next();
237   for (; MaybeRemark; MaybeRemark = Parser.next()) {
238     auto &Remark = **MaybeRemark;
239     if (Remark.RemarkName != "InstructionCount")
240       continue;
241     auto *InstrCountArg = find_if(Remark.Args, [](const Argument &Arg) {
242       return Arg.Key == "NumInstructions";
243     });
244     assert(InstrCountArg != Remark.Args.end() &&
245            "Expected instruction count remarks to have a NumInstructions key?");
246     OF->os() << Remark.FunctionName << "," << InstrCountArg->Val << "\n";
247   }
248   auto E = MaybeRemark.takeError();
249   if (!E.isA<EndOfFileError>())
250     return E;
251   consumeError(std::move(E));
252   OF->keep();
253   return Error::success();
254 }
255 } // namespace instructioncount
256 
257 /// Handle user-specified suboptions (e.g. yaml2bitstream, bitstream2yaml).
258 /// \returns An Error if the specified suboption fails or if no suboption was
259 /// specified. Otherwise, Error::success().
260 static Error handleSuboptions() {
261   if (subopts::Bitstream2YAML)
262     return bitstream2yaml::tryBitstream2YAML();
263   if (subopts::YAML2Bitstream)
264     return yaml2bitstream::tryYAML2Bitstream();
265   if (subopts::InstructionCount)
266     return instructioncount::tryInstructionCount();
267   return make_error<StringError>(
268       "Please specify a subcommand. (See -help for options)",
269       inconvertibleErrorCode());
270 }
271 
272 int main(int argc, const char **argv) {
273   InitLLVM X(argc, argv);
274   cl::HideUnrelatedOptions(RemarkUtilCategory);
275   cl::ParseCommandLineOptions(argc, argv, "Remark file utilities\n");
276   ExitOnErr.setBanner(std::string(argv[0]) + ": error: ");
277   ExitOnErr(handleSuboptions());
278 }
279