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