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