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