1 //===-------------- llvm-remark-size-diff/RemarkSizeDiff.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 ///
9 /// \file
10 /// Diffs instruction count and stack size remarks between two remark files.
11 ///
12 /// This is intended for use by compiler developers who want to see how their
13 /// changes impact program code size.
14 ///
15 //===----------------------------------------------------------------------===//
16 
17 #include "llvm-c/Remarks.h"
18 #include "llvm/ADT/Optional.h"
19 #include "llvm/ADT/STLExtras.h"
20 #include "llvm/ADT/SmallSet.h"
21 #include "llvm/Remarks/Remark.h"
22 #include "llvm/Remarks/RemarkParser.h"
23 #include "llvm/Remarks/RemarkSerializer.h"
24 #include "llvm/Support/CommandLine.h"
25 #include "llvm/Support/Compiler.h"
26 #include "llvm/Support/Error.h"
27 #include "llvm/Support/FileSystem.h"
28 #include "llvm/Support/FormatVariadic.h"
29 #include "llvm/Support/InitLLVM.h"
30 #include "llvm/Support/JSON.h"
31 #include "llvm/Support/MemoryBuffer.h"
32 #include "llvm/Support/ToolOutputFile.h"
33 #include "llvm/Support/WithColor.h"
34 #include "llvm/Support/raw_ostream.h"
35 
36 using namespace llvm;
37 
38 enum ParserFormatOptions { yaml, bitstream };
39 enum ReportStyleOptions { human_output, json_output };
40 static cl::OptionCategory SizeDiffCategory("llvm-remark-size-diff options");
41 static cl::opt<std::string> InputFileNameA(cl::Positional, cl::Required,
42                                            cl::cat(SizeDiffCategory),
43                                            cl::desc("remarks_a"));
44 static cl::opt<std::string> InputFileNameB(cl::Positional, cl::Required,
45                                            cl::cat(SizeDiffCategory),
46                                            cl::desc("remarks_b"));
47 static cl::opt<std::string> OutputFilename("o", cl::init("-"),
48                                            cl::cat(SizeDiffCategory),
49                                            cl::desc("Output"),
50                                            cl::value_desc("file"));
51 static cl::opt<ParserFormatOptions>
52     ParserFormat("parser", cl::cat(SizeDiffCategory), cl::init(bitstream),
53                  cl::desc("Set the remark parser format:"),
54                  cl::values(clEnumVal(yaml, "YAML format"),
55                             clEnumVal(bitstream, "Bitstream format")));
56 static cl::opt<ReportStyleOptions> ReportStyle(
57     "report_style", cl::cat(SizeDiffCategory),
58     cl::init(ReportStyleOptions::human_output),
59     cl::desc("Choose the report output format:"),
60     cl::values(clEnumValN(human_output, "human", "Human-readable format"),
61                clEnumValN(json_output, "json", "JSON format")));
62 static cl::opt<bool> PrettyPrint("pretty", cl::cat(SizeDiffCategory),
63                                  cl::init(false),
64                                  cl::desc("Pretty-print JSON"));
65 
66 /// Contains information from size remarks.
67 // This is a little nicer to read than a std::pair.
68 struct InstCountAndStackSize {
69   int64_t InstCount = 0;
70   int64_t StackSize = 0;
71 };
72 
73 /// Represents which files a function appeared in.
74 enum FilesPresent { A, B, BOTH };
75 
76 /// Contains the data from the remarks in file A and file B for some function.
77 /// E.g. instruction count, stack size...
78 struct FunctionDiff {
79   /// Function name from the remark.
80   std::string FuncName;
81   // Idx 0 = A, Idx 1 = B.
82   int64_t InstCount[2] = {0, 0};
83   int64_t StackSize[2] = {0, 0};
84 
85   // Calculate diffs between the first and second files.
getInstDiffFunctionDiff86   int64_t getInstDiff() const { return InstCount[1] - InstCount[0]; }
getStackDiffFunctionDiff87   int64_t getStackDiff() const { return StackSize[1] - StackSize[0]; }
88 
89   // Accessors for the remarks from the first file.
getInstCountAFunctionDiff90   int64_t getInstCountA() const { return InstCount[0]; }
getStackSizeAFunctionDiff91   int64_t getStackSizeA() const { return StackSize[0]; }
92 
93   // Accessors for the remarks from the second file.
getInstCountBFunctionDiff94   int64_t getInstCountB() const { return InstCount[1]; }
getStackSizeBFunctionDiff95   int64_t getStackSizeB() const { return StackSize[1]; }
96 
97   /// \returns which files this function was present in.
getFilesPresentFunctionDiff98   FilesPresent getFilesPresent() const {
99     if (getInstCountA() == 0)
100       return B;
101     if (getInstCountB() == 0)
102       return A;
103     return BOTH;
104   }
105 
FunctionDiffFunctionDiff106   FunctionDiff(StringRef FuncName, const InstCountAndStackSize &A,
107                const InstCountAndStackSize &B)
108       : FuncName(FuncName) {
109     InstCount[0] = A.InstCount;
110     InstCount[1] = B.InstCount;
111     StackSize[0] = A.StackSize;
112     StackSize[1] = B.StackSize;
113   }
114 };
115 
116 /// Organizes the diffs into 3 categories:
117 /// - Functions which only appeared in the first file
118 /// - Functions which only appeared in the second file
119 /// - Functions which appeared in both files
120 struct DiffsCategorizedByFilesPresent {
121   /// Diffs for functions which only appeared in the first file.
122   SmallVector<FunctionDiff> OnlyInA;
123 
124   /// Diffs for functions which only appeared in the second file.
125   SmallVector<FunctionDiff> OnlyInB;
126 
127   /// Diffs for functions which appeared in both files.
128   SmallVector<FunctionDiff> InBoth;
129 
130   /// Add a diff to the appropriate list.
addDiffDiffsCategorizedByFilesPresent131   void addDiff(FunctionDiff &FD) {
132     switch (FD.getFilesPresent()) {
133     case A:
134       OnlyInA.push_back(FD);
135       break;
136     case B:
137       OnlyInB.push_back(FD);
138       break;
139     case BOTH:
140       InBoth.push_back(FD);
141       break;
142     }
143   }
144 };
145 
printFunctionDiff(const FunctionDiff & FD,llvm::raw_ostream & OS)146 static void printFunctionDiff(const FunctionDiff &FD, llvm::raw_ostream &OS) {
147   // Describe which files the function had remarks in.
148   FilesPresent FP = FD.getFilesPresent();
149   const std::string &FuncName = FD.FuncName;
150   const int64_t InstDiff = FD.getInstDiff();
151   assert(InstDiff && "Shouldn't get functions with no size change?");
152   const int64_t StackDiff = FD.getStackDiff();
153   // Output an indicator denoting which files the function was present in.
154   switch (FP) {
155   case FilesPresent::A:
156     OS << "-- ";
157     break;
158   case FilesPresent::B:
159     OS << "++ ";
160     break;
161   case FilesPresent::BOTH:
162     OS << "== ";
163     break;
164   }
165   // Output an indicator denoting if a function changed in size.
166   if (InstDiff > 0)
167     OS << "> ";
168   else
169     OS << "< ";
170   OS << FuncName << ", ";
171   OS << InstDiff << " instrs, ";
172   OS << StackDiff << " stack B";
173   OS << "\n";
174 }
175 
176 /// Print an item in the summary section.
177 ///
178 /// \p TotalA - Total count of the metric in file A.
179 /// \p TotalB - Total count of the metric in file B.
180 /// \p Metric - Name of the metric we want to print (e.g. instruction
181 /// count).
182 /// \p OS - The output stream.
printSummaryItem(int64_t TotalA,int64_t TotalB,StringRef Metric,llvm::raw_ostream & OS)183 static void printSummaryItem(int64_t TotalA, int64_t TotalB, StringRef Metric,
184                              llvm::raw_ostream &OS) {
185   OS << "  " << Metric << ": ";
186   int64_t TotalDiff = TotalB - TotalA;
187   if (TotalDiff == 0) {
188     OS << "None\n";
189     return;
190   }
191   OS << TotalDiff << " (" << formatv("{0:p}", TotalDiff / (double)TotalA)
192      << ")\n";
193 }
194 
195 /// Print all contents of \p Diff and a high-level summary of the differences.
printDiffsCategorizedByFilesPresent(DiffsCategorizedByFilesPresent & DiffsByFilesPresent,llvm::raw_ostream & OS)196 static void printDiffsCategorizedByFilesPresent(
197     DiffsCategorizedByFilesPresent &DiffsByFilesPresent,
198     llvm::raw_ostream &OS) {
199   int64_t InstrsA = 0;
200   int64_t InstrsB = 0;
201   int64_t StackA = 0;
202   int64_t StackB = 0;
203   // Helper lambda to sort + print a list of diffs.
204   auto PrintDiffList = [&](SmallVector<FunctionDiff> &FunctionDiffList) {
205     if (FunctionDiffList.empty())
206       return;
207     stable_sort(FunctionDiffList,
208                 [](const FunctionDiff &LHS, const FunctionDiff &RHS) {
209                   return LHS.getInstDiff() < RHS.getInstDiff();
210                 });
211     for (const auto &FuncDiff : FunctionDiffList) {
212       // If there is a difference in instruction count, then print out info for
213       // the function.
214       if (FuncDiff.getInstDiff())
215         printFunctionDiff(FuncDiff, OS);
216       InstrsA += FuncDiff.getInstCountA();
217       InstrsB += FuncDiff.getInstCountB();
218       StackA += FuncDiff.getStackSizeA();
219       StackB += FuncDiff.getStackSizeB();
220     }
221   };
222   PrintDiffList(DiffsByFilesPresent.OnlyInA);
223   PrintDiffList(DiffsByFilesPresent.OnlyInB);
224   PrintDiffList(DiffsByFilesPresent.InBoth);
225   OS << "\n### Summary ###\n";
226   OS << "Total change: \n";
227   printSummaryItem(InstrsA, InstrsB, "instruction count", OS);
228   printSummaryItem(StackA, StackB, "stack byte usage", OS);
229 }
230 
231 /// Collects an expected integer value from a given argument index in a remark.
232 ///
233 /// \p Remark - The remark.
234 /// \p ArgIdx - The index where the integer value should be found.
235 /// \p ExpectedKeyName - The expected key name for the index
236 /// (e.g. "InstructionCount")
237 ///
238 /// \returns the integer value at the index if it exists, and the key-value pair
239 /// is what is expected. Otherwise, returns an Error.
getIntValFromKey(const remarks::Remark & Remark,unsigned ArgIdx,StringRef ExpectedKeyName)240 static Expected<int64_t> getIntValFromKey(const remarks::Remark &Remark,
241                                           unsigned ArgIdx,
242                                           StringRef ExpectedKeyName) {
243   auto KeyName = Remark.Args[ArgIdx].Key;
244   if (KeyName != ExpectedKeyName)
245     return createStringError(
246         inconvertibleErrorCode(),
247         Twine("Unexpected key at argument index " + std::to_string(ArgIdx) +
248               ": Expected '" + ExpectedKeyName + "', got '" + KeyName + "'"));
249   long long Val;
250   auto ValStr = Remark.Args[ArgIdx].Val;
251   if (getAsSignedInteger(ValStr, 0, Val))
252     return createStringError(
253         inconvertibleErrorCode(),
254         Twine("Could not convert string to signed integer: " + ValStr));
255   return static_cast<int64_t>(Val);
256 }
257 
258 /// Collects relevant size information from \p Remark if it is an size-related
259 /// remark of some kind (e.g. instruction count). Otherwise records nothing.
260 ///
261 /// \p Remark - The remark.
262 /// \p FuncNameToSizeInfo - Maps function names to relevant size info.
263 /// \p NumInstCountRemarksParsed - Keeps track of the number of instruction
264 /// count remarks parsed. We need at least 1 in both files to produce a diff.
processRemark(const remarks::Remark & Remark,StringMap<InstCountAndStackSize> & FuncNameToSizeInfo,unsigned & NumInstCountRemarksParsed)265 static Error processRemark(const remarks::Remark &Remark,
266                            StringMap<InstCountAndStackSize> &FuncNameToSizeInfo,
267                            unsigned &NumInstCountRemarksParsed) {
268   const auto &RemarkName = Remark.RemarkName;
269   const auto &PassName = Remark.PassName;
270   // Collect remarks which contain the number of instructions in a function.
271   if (PassName == "asm-printer" && RemarkName == "InstructionCount") {
272     // Expecting the 0-th argument to have the key "NumInstructions" and an
273     // integer value.
274     auto MaybeInstCount =
275         getIntValFromKey(Remark, /*ArgIdx = */ 0, "NumInstructions");
276     if (!MaybeInstCount)
277       return MaybeInstCount.takeError();
278     FuncNameToSizeInfo[Remark.FunctionName].InstCount = *MaybeInstCount;
279     ++NumInstCountRemarksParsed;
280   }
281   // Collect remarks which contain the stack size of a function.
282   else if (PassName == "prologepilog" && RemarkName == "StackSize") {
283     // Expecting the 0-th argument to have the key "NumStackBytes" and an
284     // integer value.
285     auto MaybeStackSize =
286         getIntValFromKey(Remark, /*ArgIdx = */ 0, "NumStackBytes");
287     if (!MaybeStackSize)
288       return MaybeStackSize.takeError();
289     FuncNameToSizeInfo[Remark.FunctionName].StackSize = *MaybeStackSize;
290   }
291   // Either we collected a remark, or it's something we don't care about. In
292   // both cases, this is a success.
293   return Error::success();
294 }
295 
296 /// Process all of the size-related remarks in a file.
297 ///
298 /// \param[in] InputFileName - Name of file to read from.
299 /// \param[in, out] FuncNameToSizeInfo - Maps function names to relevant
300 /// size info.
readFileAndProcessRemarks(StringRef InputFileName,StringMap<InstCountAndStackSize> & FuncNameToSizeInfo)301 static Error readFileAndProcessRemarks(
302     StringRef InputFileName,
303     StringMap<InstCountAndStackSize> &FuncNameToSizeInfo) {
304   auto Buf = MemoryBuffer::getFile(InputFileName);
305   if (auto EC = Buf.getError())
306     return createStringError(
307         EC, Twine("Cannot open file '" + InputFileName + "': " + EC.message()));
308   auto MaybeParser = remarks::createRemarkParserFromMeta(
309       ParserFormat == bitstream ? remarks::Format::Bitstream
310                                 : remarks::Format::YAML,
311       (*Buf)->getBuffer());
312   if (!MaybeParser)
313     return MaybeParser.takeError();
314   auto &Parser = **MaybeParser;
315   auto MaybeRemark = Parser.next();
316   unsigned NumInstCountRemarksParsed = 0;
317   for (; MaybeRemark; MaybeRemark = Parser.next()) {
318     if (auto E = processRemark(**MaybeRemark, FuncNameToSizeInfo,
319                                NumInstCountRemarksParsed))
320       return E;
321   }
322   auto E = MaybeRemark.takeError();
323   if (!E.isA<remarks::EndOfFileError>())
324     return E;
325   consumeError(std::move(E));
326   // We need at least one instruction count remark in each file to produce a
327   // meaningful diff.
328   if (NumInstCountRemarksParsed == 0)
329     return createStringError(
330         inconvertibleErrorCode(),
331         "File '" + InputFileName +
332             "' did not contain any instruction-count remarks!");
333   return Error::success();
334 }
335 
336 /// Wrapper function for readFileAndProcessRemarks which handles errors.
337 ///
338 /// \param[in] InputFileName - Name of file to read from.
339 /// \param[out] FuncNameToSizeInfo - Populated with information from size
340 /// remarks in the input file.
341 ///
342 /// \returns true if readFileAndProcessRemarks returned no errors. False
343 /// otherwise.
tryReadFileAndProcessRemarks(StringRef InputFileName,StringMap<InstCountAndStackSize> & FuncNameToSizeInfo)344 static bool tryReadFileAndProcessRemarks(
345     StringRef InputFileName,
346     StringMap<InstCountAndStackSize> &FuncNameToSizeInfo) {
347   if (Error E = readFileAndProcessRemarks(InputFileName, FuncNameToSizeInfo)) {
348     handleAllErrors(std::move(E), [&](const ErrorInfoBase &PE) {
349       PE.log(WithColor::error());
350       errs() << '\n';
351     });
352     return false;
353   }
354   return true;
355 }
356 
357 /// Populates \p FuncDiffs with the difference between \p
358 /// FuncNameToSizeInfoA and \p FuncNameToSizeInfoB.
359 ///
360 /// \param[in] FuncNameToSizeInfoA - Size info collected from the first
361 /// remarks file.
362 /// \param[in] FuncNameToSizeInfoB - Size info collected from
363 /// the second remarks file.
364 /// \param[out] DiffsByFilesPresent - Filled with the diff between \p
365 /// FuncNameToSizeInfoA and \p FuncNameToSizeInfoB.
366 static void
computeDiff(const StringMap<InstCountAndStackSize> & FuncNameToSizeInfoA,const StringMap<InstCountAndStackSize> & FuncNameToSizeInfoB,DiffsCategorizedByFilesPresent & DiffsByFilesPresent)367 computeDiff(const StringMap<InstCountAndStackSize> &FuncNameToSizeInfoA,
368             const StringMap<InstCountAndStackSize> &FuncNameToSizeInfoB,
369             DiffsCategorizedByFilesPresent &DiffsByFilesPresent) {
370   SmallSet<std::string, 10> FuncNames;
371   for (const auto &FuncName : FuncNameToSizeInfoA.keys())
372     FuncNames.insert(FuncName.str());
373   for (const auto &FuncName : FuncNameToSizeInfoB.keys())
374     FuncNames.insert(FuncName.str());
375   for (const std::string &FuncName : FuncNames) {
376     const auto &SizeInfoA = FuncNameToSizeInfoA.lookup(FuncName);
377     const auto &SizeInfoB = FuncNameToSizeInfoB.lookup(FuncName);
378     FunctionDiff FuncDiff(FuncName, SizeInfoA, SizeInfoB);
379     DiffsByFilesPresent.addDiff(FuncDiff);
380   }
381 }
382 
383 /// Attempt to get the output stream for writing the diff.
getOutputStream()384 static ErrorOr<std::unique_ptr<ToolOutputFile>> getOutputStream() {
385   if (OutputFilename == "")
386     OutputFilename = "-";
387   std::error_code EC;
388   auto Out = std::make_unique<ToolOutputFile>(OutputFilename, EC,
389                                               sys::fs::OF_TextWithCRLF);
390   if (!EC)
391     return std::move(Out);
392   return EC;
393 }
394 
395 /// \return a json::Array representing all FunctionDiffs in \p FunctionDiffs.
396 /// \p WhichFiles represents which files the functions in \p FunctionDiffs
397 /// appeared in (A, B, or both).
398 json::Array
getFunctionDiffListAsJSON(const SmallVector<FunctionDiff> & FunctionDiffs,const FilesPresent & WhichFiles)399 getFunctionDiffListAsJSON(const SmallVector<FunctionDiff> &FunctionDiffs,
400                           const FilesPresent &WhichFiles) {
401   json::Array FunctionDiffsAsJSON;
402   int64_t InstCountA, InstCountB, StackSizeA, StackSizeB;
403   for (auto &Diff : FunctionDiffs) {
404     InstCountA = InstCountB = StackSizeA = StackSizeB = 0;
405     switch (WhichFiles) {
406     case BOTH:
407       LLVM_FALLTHROUGH;
408     case A:
409       InstCountA = Diff.getInstCountA();
410       StackSizeA = Diff.getStackSizeA();
411       if (WhichFiles != BOTH)
412         break;
413       LLVM_FALLTHROUGH;
414     case B:
415       InstCountB = Diff.getInstCountB();
416       StackSizeB = Diff.getStackSizeB();
417       break;
418     }
419     // Each metric we care about is represented like:
420     //   "Val": [A, B]
421     // This allows any consumer of the JSON to calculate the diff using B - A.
422     // This is somewhat wasteful for OnlyInA and OnlyInB (we only need A or B).
423     // However, this should make writing consuming tools easier, since the tool
424     // writer doesn't need to think about slightly different formats in each
425     // section.
426     json::Object FunctionObject({{"FunctionName", Diff.FuncName},
427                                  {"InstCount", {InstCountA, InstCountB}},
428                                  {"StackSize", {StackSizeA, StackSizeB}}});
429     FunctionDiffsAsJSON.push_back(std::move(FunctionObject));
430   }
431   return FunctionDiffsAsJSON;
432 }
433 
434 /// Output all diffs in \p DiffsByFilesPresent as a JSON report. This is
435 /// intended for consumption by external tools.
436 ///
437 /// \p InputFileNameA - File A used to produce the report.
438 /// \p InputFileNameB - File B used ot produce the report.
439 /// \p OS - Output stream.
440 ///
441 /// JSON output includes:
442 ///  - \p InputFileNameA and \p InputFileNameB under "Files".
443 ///  - Functions present in both files under "InBoth".
444 ///  - Functions present only in A in "OnlyInA".
445 ///  - Functions present only in B in "OnlyInB".
446 ///  - Instruction count and stack size differences for each function.
447 ///
448 /// Differences are represented using [count_a, count_b]. The actual difference
449 /// can be computed via count_b - count_a.
450 static void
outputJSONForAllDiffs(StringRef InputFileNameA,StringRef InputFileNameB,const DiffsCategorizedByFilesPresent & DiffsByFilesPresent,llvm::raw_ostream & OS)451 outputJSONForAllDiffs(StringRef InputFileNameA, StringRef InputFileNameB,
452                       const DiffsCategorizedByFilesPresent &DiffsByFilesPresent,
453                       llvm::raw_ostream &OS) {
454   json::Object Output;
455   // Include file names in the report.
456   json::Object Files(
457       {{"A", InputFileNameA.str()}, {"B", InputFileNameB.str()}});
458   Output["Files"] = std::move(Files);
459   Output["OnlyInA"] = getFunctionDiffListAsJSON(DiffsByFilesPresent.OnlyInA, A);
460   Output["OnlyInB"] = getFunctionDiffListAsJSON(DiffsByFilesPresent.OnlyInB, B);
461   Output["InBoth"] =
462       getFunctionDiffListAsJSON(DiffsByFilesPresent.InBoth, BOTH);
463   json::OStream JOS(OS, PrettyPrint ? 2 : 0);
464   JOS.value(std::move(Output));
465   OS << '\n';
466 }
467 
468 /// Output all diffs in \p DiffsByFilesPresent using the desired output style.
469 /// \returns Error::success() on success, and an Error otherwise.
470 /// \p InputFileNameA - Name of input file A; may be used in the report.
471 /// \p InputFileNameB - Name of input file B; may be used in the report.
472 static Error
outputAllDiffs(StringRef InputFileNameA,StringRef InputFileNameB,DiffsCategorizedByFilesPresent & DiffsByFilesPresent)473 outputAllDiffs(StringRef InputFileNameA, StringRef InputFileNameB,
474                DiffsCategorizedByFilesPresent &DiffsByFilesPresent) {
475   auto MaybeOF = getOutputStream();
476   if (std::error_code EC = MaybeOF.getError())
477     return errorCodeToError(EC);
478   std::unique_ptr<ToolOutputFile> OF = std::move(*MaybeOF);
479   switch (ReportStyle) {
480   case human_output:
481     printDiffsCategorizedByFilesPresent(DiffsByFilesPresent, OF->os());
482     break;
483   case json_output:
484     outputJSONForAllDiffs(InputFileNameA, InputFileNameB, DiffsByFilesPresent,
485                           OF->os());
486     break;
487   }
488   OF->keep();
489   return Error::success();
490 }
491 
492 /// Boolean wrapper for outputDiff which handles errors.
493 static bool
tryOutputAllDiffs(StringRef InputFileNameA,StringRef InputFileNameB,DiffsCategorizedByFilesPresent & DiffsByFilesPresent)494 tryOutputAllDiffs(StringRef InputFileNameA, StringRef InputFileNameB,
495                   DiffsCategorizedByFilesPresent &DiffsByFilesPresent) {
496   if (Error E =
497           outputAllDiffs(InputFileNameA, InputFileNameB, DiffsByFilesPresent)) {
498     handleAllErrors(std::move(E), [&](const ErrorInfoBase &PE) {
499       PE.log(WithColor::error());
500       errs() << '\n';
501     });
502     return false;
503   }
504   return true;
505 }
506 
main(int argc,const char ** argv)507 int main(int argc, const char **argv) {
508   InitLLVM X(argc, argv);
509   cl::HideUnrelatedOptions(SizeDiffCategory);
510   cl::ParseCommandLineOptions(argc, argv,
511                               "Diff instruction count and stack size remarks "
512                               "between two remark files.\n");
513   StringMap<InstCountAndStackSize> FuncNameToSizeInfoA;
514   StringMap<InstCountAndStackSize> FuncNameToSizeInfoB;
515   if (!tryReadFileAndProcessRemarks(InputFileNameA, FuncNameToSizeInfoA) ||
516       !tryReadFileAndProcessRemarks(InputFileNameB, FuncNameToSizeInfoB))
517     return 1;
518   DiffsCategorizedByFilesPresent DiffsByFilesPresent;
519   computeDiff(FuncNameToSizeInfoA, FuncNameToSizeInfoB, DiffsByFilesPresent);
520   if (!tryOutputAllDiffs(InputFileNameA, InputFileNameB, DiffsByFilesPresent))
521     return 1;
522 }
523