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