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 static cl::SubCommand
42     AnnotationCount("annotation-count",
43                     "Collect count information from annotation remarks (uses "
44                     "AnnotationRemarksPass)");
45 } // namespace subopts
46 
47 // Keep input + output help + names consistent across the various modes via a
48 // hideous macro.
49 #define INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SUBOPT)                              \
50   static cl::opt<std::string> InputFileName(                                   \
51       cl::Positional, cl::cat(RemarkUtilCategory), cl::init("-"),              \
52       cl::desc("<input file>"), cl::sub(SUBOPT));                              \
53   static cl::opt<std::string> OutputFileName(                                  \
54       "o", cl::init("-"), cl::cat(RemarkUtilCategory), cl::desc("Output"),     \
55       cl::value_desc("filename"), cl::sub(SUBOPT));
56 
57 // Keep Input format and names consistent accross the modes via a macro.
58 #define INPUT_FORMAT_COMMAND_LINE_OPTIONS(SUBOPT)                              \
59   static cl::opt<Format> InputFormat(                                          \
60       "parser", cl::desc("Input remark format to parse"),                      \
61       cl::values(clEnumValN(Format::YAML, "yaml", "YAML"),                     \
62                  clEnumValN(Format::Bitstream, "bitstream", "Bitstream")),     \
63       cl::sub(SUBOPT));
64 
65 #define DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(SUBOPT)                            \
66   static cl::opt<bool> UseDebugLoc(                                            \
67       "use-debug-loc",                                                         \
68       cl::desc(                                                                \
69           "Add debug loc information when generating tables for "              \
70           "functions. The loc is represented as (path:line number:column "     \
71           "number)"),                                                          \
72       cl::init(false), cl::sub(SUBOPT));
73 namespace yaml2bitstream {
74 /// Remark format to parse.
75 static constexpr Format InputFormat = Format::YAML;
76 /// Remark format to output.
77 static constexpr Format OutputFormat = Format::Bitstream;
78 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::YAML2Bitstream)
79 } // namespace yaml2bitstream
80 
81 namespace bitstream2yaml {
82 /// Remark format to parse.
83 static constexpr Format InputFormat = Format::Bitstream;
84 /// Remark format to output.
85 static constexpr Format OutputFormat = Format::YAML;
86 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::Bitstream2YAML)
87 } // namespace bitstream2yaml
88 
89 namespace instructioncount {
90 INPUT_FORMAT_COMMAND_LINE_OPTIONS(subopts::InstructionCount)
91 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::InstructionCount)
92 DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(subopts::InstructionCount)
93 } // namespace instructioncount
94 
95 namespace annotationcount {
96 INPUT_FORMAT_COMMAND_LINE_OPTIONS(subopts::AnnotationCount)
97 static cl::opt<std::string> AnnotationTypeToCollect(
98     "annotation-type", cl::desc("annotation-type remark to collect count for"),
99     cl::sub(subopts::AnnotationCount));
100 INPUT_OUTPUT_COMMAND_LINE_OPTIONS(subopts::AnnotationCount)
101 DEBUG_LOC_INFO_COMMAND_LINE_OPTIONS(subopts::AnnotationCount)
102 } // namespace annotationcount
103 
104 /// \returns A MemoryBuffer for the input file on success, and an Error
105 /// otherwise.
106 static Expected<std::unique_ptr<MemoryBuffer>>
107 getInputMemoryBuffer(StringRef InputFileName) {
108   auto MaybeBuf = MemoryBuffer::getFileOrSTDIN(InputFileName);
109   if (auto ErrorCode = MaybeBuf.getError())
110     return createStringError(ErrorCode,
111                              Twine("Cannot open file '" + InputFileName +
112                                    "': " + ErrorCode.message()));
113   return std::move(*MaybeBuf);
114 }
115 
116 /// \returns A ToolOutputFile which can be used for outputting the results of
117 /// some tool mode.
118 /// \p OutputFileName is the desired destination.
119 /// \p Flags controls whether or not the file is opened for writing in text
120 /// mode, as a binary, etc. See sys::fs::OpenFlags for more detail.
121 static Expected<std::unique_ptr<ToolOutputFile>>
122 getOutputFileWithFlags(StringRef OutputFileName, sys::fs::OpenFlags Flags) {
123   if (OutputFileName == "")
124     OutputFileName = "-";
125   std::error_code ErrorCode;
126   auto OF = std::make_unique<ToolOutputFile>(OutputFileName, ErrorCode, Flags);
127   if (ErrorCode)
128     return errorCodeToError(ErrorCode);
129   return std::move(OF);
130 }
131 
132 /// \returns A ToolOutputFile which can be used for writing remarks on success,
133 /// and an Error otherwise.
134 /// \p OutputFileName is the desired destination.
135 /// \p OutputFormat
136 static Expected<std::unique_ptr<ToolOutputFile>>
137 getOutputFileForRemarks(StringRef OutputFileName, Format OutputFormat) {
138   assert((OutputFormat == Format::YAML || OutputFormat == Format::Bitstream) &&
139          "Expected one of YAML or Bitstream!");
140   return getOutputFileWithFlags(OutputFileName, OutputFormat == Format::YAML
141                                                     ? sys::fs::OF_TextWithCRLF
142                                                     : sys::fs::OF_None);
143 }
144 
145 static bool shouldSkipRemark(bool UseDebugLoc, Remark &Remark) {
146   return UseDebugLoc && !Remark.Loc.has_value();
147 }
148 
149 namespace yaml2bitstream {
150 /// Parses all remarks in the input YAML file.
151 /// \p [out] ParsedRemarks - Filled with remarks parsed from the input file.
152 /// \p [out] StrTab - A string table populated for later remark serialization.
153 /// \returns Error::success() if all remarks were successfully parsed, and an
154 /// Error otherwise.
155 static Error
156 tryParseRemarksFromYAMLFile(std::vector<std::unique_ptr<Remark>> &ParsedRemarks,
157                             StringTable &StrTab) {
158   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
159   if (!MaybeBuf)
160     return MaybeBuf.takeError();
161   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
162   if (!MaybeParser)
163     return MaybeParser.takeError();
164   auto &Parser = **MaybeParser;
165   auto MaybeRemark = Parser.next();
166   for (; MaybeRemark; MaybeRemark = Parser.next()) {
167     StrTab.internalize(**MaybeRemark);
168     ParsedRemarks.push_back(std::move(*MaybeRemark));
169   }
170   auto E = MaybeRemark.takeError();
171   if (!E.isA<EndOfFileError>())
172     return E;
173   consumeError(std::move(E));
174   return Error::success();
175 }
176 
177 /// Reserialize a list of parsed YAML remarks into bitstream remarks.
178 /// \p ParsedRemarks - A list of remarks.
179 /// \p StrTab - The string table for the remarks.
180 /// \returns Error::success() on success.
181 static Error tryReserializeYAML2Bitstream(
182     const std::vector<std::unique_ptr<Remark>> &ParsedRemarks,
183     StringTable &StrTab) {
184   auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat);
185   if (!MaybeOF)
186     return MaybeOF.takeError();
187   auto OF = std::move(*MaybeOF);
188   auto MaybeSerializer = createRemarkSerializer(
189       OutputFormat, SerializerMode::Standalone, OF->os(), std::move(StrTab));
190   if (!MaybeSerializer)
191     return MaybeSerializer.takeError();
192   auto Serializer = std::move(*MaybeSerializer);
193   for (const auto &Remark : ParsedRemarks)
194     Serializer->emit(*Remark);
195   OF->keep();
196   return Error::success();
197 }
198 
199 /// Parse YAML remarks and reserialize as bitstream remarks.
200 /// \returns Error::success() on success, and an Error otherwise.
201 static Error tryYAML2Bitstream() {
202   StringTable StrTab;
203   std::vector<std::unique_ptr<Remark>> ParsedRemarks;
204   ExitOnErr(tryParseRemarksFromYAMLFile(ParsedRemarks, StrTab));
205   return tryReserializeYAML2Bitstream(ParsedRemarks, StrTab);
206 }
207 } // namespace yaml2bitstream
208 
209 namespace bitstream2yaml {
210 /// Parse bitstream remarks and reserialize as YAML remarks.
211 /// \returns An Error if reserialization fails, or Error::success() on success.
212 static Error tryBitstream2YAML() {
213   // Create the serializer.
214   auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat);
215   if (!MaybeOF)
216     return MaybeOF.takeError();
217   auto OF = std::move(*MaybeOF);
218   auto MaybeSerializer = createRemarkSerializer(
219       OutputFormat, SerializerMode::Standalone, OF->os());
220   if (!MaybeSerializer)
221     return MaybeSerializer.takeError();
222 
223   // Create the parser.
224   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
225   if (!MaybeBuf)
226     return MaybeBuf.takeError();
227   auto Serializer = std::move(*MaybeSerializer);
228   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
229   if (!MaybeParser)
230     return MaybeParser.takeError();
231   auto &Parser = **MaybeParser;
232 
233   // Parse + reserialize all remarks.
234   auto MaybeRemark = Parser.next();
235   for (; MaybeRemark; MaybeRemark = Parser.next())
236     Serializer->emit(**MaybeRemark);
237   auto E = MaybeRemark.takeError();
238   if (!E.isA<EndOfFileError>())
239     return E;
240   consumeError(std::move(E));
241   return Error::success();
242 }
243 } // namespace bitstream2yaml
244 
245 namespace instructioncount {
246 /// Outputs all instruction count remarks in the file as a CSV.
247 /// \returns Error::success() on success, and an Error otherwise.
248 static Error tryInstructionCount() {
249   // Create the output buffer.
250   auto MaybeOF = getOutputFileWithFlags(OutputFileName,
251                                         /*Flags = */ sys::fs::OF_TextWithCRLF);
252   if (!MaybeOF)
253     return MaybeOF.takeError();
254   auto OF = std::move(*MaybeOF);
255   // Create a parser for the user-specified input format.
256   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
257   if (!MaybeBuf)
258     return MaybeBuf.takeError();
259   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
260   if (!MaybeParser)
261     return MaybeParser.takeError();
262   // Emit CSV header.
263   if (UseDebugLoc)
264     OF->os() << "Source,";
265   OF->os() << "Function,InstructionCount\n";
266   // Parse all remarks. Whenever we see an instruction count remark, output
267   // the file name and the number of instructions.
268   auto &Parser = **MaybeParser;
269   auto MaybeRemark = Parser.next();
270   for (; MaybeRemark; MaybeRemark = Parser.next()) {
271     auto &Remark = **MaybeRemark;
272     if (Remark.RemarkName != "InstructionCount")
273       continue;
274     if (shouldSkipRemark(UseDebugLoc, Remark))
275       continue;
276     auto *InstrCountArg = find_if(Remark.Args, [](const Argument &Arg) {
277       return Arg.Key == "NumInstructions";
278     });
279     assert(InstrCountArg != Remark.Args.end() &&
280            "Expected instruction count remarks to have a NumInstructions key?");
281     if (UseDebugLoc) {
282       std::string Loc = Remark.Loc->SourceFilePath.str() + ":" +
283                         std::to_string(Remark.Loc->SourceLine) + +":" +
284                         std::to_string(Remark.Loc->SourceColumn);
285       OF->os() << Loc << ",";
286     }
287     OF->os() << Remark.FunctionName << "," << InstrCountArg->Val << "\n";
288   }
289   auto E = MaybeRemark.takeError();
290   if (!E.isA<EndOfFileError>())
291     return E;
292   consumeError(std::move(E));
293   OF->keep();
294   return Error::success();
295 }
296 } // namespace instructioncount
297 
298 namespace annotationcount {
299 static Error tryAnnotationCount() {
300   // Create the output buffer.
301   auto MaybeOF = getOutputFileWithFlags(OutputFileName,
302                                         /*Flags = */ sys::fs::OF_TextWithCRLF);
303   if (!MaybeOF)
304     return MaybeOF.takeError();
305   auto OF = std::move(*MaybeOF);
306   // Create a parser for the user-specified input format.
307   auto MaybeBuf = getInputMemoryBuffer(InputFileName);
308   if (!MaybeBuf)
309     return MaybeBuf.takeError();
310   auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
311   if (!MaybeParser)
312     return MaybeParser.takeError();
313   // Emit CSV header.
314   if (UseDebugLoc)
315     OF->os() << "Source,";
316   OF->os() << "Function,Count\n";
317   // Parse all remarks. When we see the specified remark collect the count
318   // information.
319   auto &Parser = **MaybeParser;
320   auto MaybeRemark = Parser.next();
321   for (; MaybeRemark; MaybeRemark = Parser.next()) {
322     auto &Remark = **MaybeRemark;
323     if (Remark.RemarkName != "AnnotationSummary")
324       continue;
325     if (shouldSkipRemark(UseDebugLoc, Remark))
326       continue;
327     auto *RemarkNameArg = find_if(Remark.Args, [](const Argument &Arg) {
328       return Arg.Key == "type" && Arg.Val == AnnotationTypeToCollect;
329     });
330     if (RemarkNameArg == Remark.Args.end())
331       continue;
332     auto *CountArg = find_if(
333         Remark.Args, [](const Argument &Arg) { return Arg.Key == "count"; });
334     assert(CountArg != Remark.Args.end() &&
335            "Expected annotation-type remark to have a count key?");
336     if (UseDebugLoc) {
337       std::string Loc = Remark.Loc->SourceFilePath.str() + ":" +
338                         std::to_string(Remark.Loc->SourceLine) + +":" +
339                         std::to_string(Remark.Loc->SourceColumn);
340       OF->os() << Loc << ",";
341     }
342     OF->os() << Remark.FunctionName << "," << CountArg->Val << "\n";
343   }
344   auto E = MaybeRemark.takeError();
345   if (!E.isA<EndOfFileError>())
346     return E;
347   consumeError(std::move(E));
348   OF->keep();
349   return Error::success();
350 }
351 
352 } // namespace annotationcount
353 /// Handle user-specified suboptions (e.g. yaml2bitstream, bitstream2yaml).
354 /// \returns An Error if the specified suboption fails or if no suboption was
355 /// specified. Otherwise, Error::success().
356 static Error handleSuboptions() {
357   if (subopts::Bitstream2YAML)
358     return bitstream2yaml::tryBitstream2YAML();
359   if (subopts::YAML2Bitstream)
360     return yaml2bitstream::tryYAML2Bitstream();
361   if (subopts::InstructionCount)
362     return instructioncount::tryInstructionCount();
363   if (subopts::AnnotationCount)
364     return annotationcount::tryAnnotationCount();
365 
366   return make_error<StringError>(
367       "Please specify a subcommand. (See -help for options)",
368       inconvertibleErrorCode());
369 }
370 
371 int main(int argc, const char **argv) {
372   InitLLVM X(argc, argv);
373   cl::HideUnrelatedOptions(RemarkUtilCategory);
374   cl::ParseCommandLineOptions(argc, argv, "Remark file utilities\n");
375   ExitOnErr.setBanner(std::string(argv[0]) + ": error: ");
376   ExitOnErr(handleSuboptions());
377 }
378