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 | ("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(Val: "-" ), |
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(Val: 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(Val: 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(Elt: FD); |
121 | break; |
122 | case B: |
123 | OnlyInB.push_back(Elt: FD); |
124 | break; |
125 | case BOTH: |
126 | InBoth.push_back(Elt: 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(Fmt: "{0:p}" , Vals: 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(Range&: FunctionDiffList, |
194 | C: [](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(FD: 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(TotalA: InstrsA, TotalB: InstrsB, Metric: "instruction count" , OS); |
214 | printSummaryItem(TotalA: StackA, TotalB: StackB, Metric: "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> (const remarks::Remark &, |
227 | unsigned ArgIdx, |
228 | StringRef ExpectedKeyName) { |
229 | auto KeyName = Remark.Args[ArgIdx].Key; |
230 | if (KeyName != ExpectedKeyName) |
231 | return createStringError( |
232 | EC: inconvertibleErrorCode(), |
233 | S: Twine("Unexpected key at argument index " + std::to_string(val: ArgIdx) + |
234 | ": Expected '" + ExpectedKeyName + "', got '" + KeyName + "'" )); |
235 | long long Val; |
236 | auto ValStr = Remark.Args[ArgIdx].Val; |
237 | if (getAsSignedInteger(Str: ValStr, Radix: 0, Result&: Val)) |
238 | return createStringError( |
239 | EC: inconvertibleErrorCode(), |
240 | S: 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 &, |
252 | StringMap<InstCountAndStackSize> &FuncNameToSizeInfo, |
253 | unsigned &) { |
254 | const auto & = 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, ExpectedKeyName: "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, ExpectedKeyName: "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(ParserFormat: InputFormat, Buf: (*MaybeBuf)->getBuffer()); |
296 | if (!MaybeParser) |
297 | return MaybeParser.takeError(); |
298 | auto &Parser = **MaybeParser; |
299 | auto = Parser.next(); |
300 | unsigned = 0; |
301 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
302 | if (auto E = processRemark(Remark: **MaybeRemark, FuncNameToSizeInfo, |
303 | NumInstCountRemarksParsed)) |
304 | return E; |
305 | } |
306 | auto E = MaybeRemark.takeError(); |
307 | if (!E.isA<remarks::EndOfFileError>()) |
308 | return E; |
309 | consumeError(Err: 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 | EC: inconvertibleErrorCode(), |
315 | S: "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(V: FuncName.str()); |
353 | for (const auto &FuncName : FuncNameToSizeInfoB.keys()) |
354 | FuncNames.insert(V: FuncName.str()); |
355 | for (const std::string &FuncName : FuncNames) { |
356 | const auto &SizeInfoA = FuncNameToSizeInfoA.lookup(Key: FuncName); |
357 | const auto &SizeInfoB = FuncNameToSizeInfoB.lookup(Key: FuncName); |
358 | FunctionDiff FuncDiff(FuncName, SizeInfoA, SizeInfoB); |
359 | DiffsByFilesPresent.addDiff(FD&: 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>(args&: OutputFilename, args&: EC, |
369 | args: 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({{.K: "FunctionName" , .V: Diff.FuncName}, |
407 | {.K: "InstCount" , .V: {InstCountA, InstCountB}}, |
408 | {.K: "StackSize" , .V: {StackSizeA, StackSizeB}}}); |
409 | FunctionDiffsAsJSON.push_back(E: 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 | {{.K: "A" , .V: InputFileNameA.str()}, {.K: "B" , .V: InputFileNameB.str()}}); |
438 | Output["Files" ] = std::move(Files); |
439 | Output["OnlyInA" ] = getFunctionDiffListAsJSON(FunctionDiffs: DiffsByFilesPresent.OnlyInA, WhichFiles: A); |
440 | Output["OnlyInB" ] = getFunctionDiffListAsJSON(FunctionDiffs: DiffsByFilesPresent.OnlyInB, WhichFiles: B); |
441 | Output["InBoth" ] = |
442 | getFunctionDiffListAsJSON(FunctionDiffs: DiffsByFilesPresent.InBoth, WhichFiles: BOTH); |
443 | json::OStream JOS(OS, PrettyPrint ? 2 : 0); |
444 | JOS.value(V: 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, OS&: OF->os()); |
462 | break; |
463 | case json_output: |
464 | outputJSONForAllDiffs(InputFileNameA, InputFileNameB, DiffsByFilesPresent, |
465 | OS&: 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(InputFileName: InputFileNameA, FuncNameToSizeInfo&: FuncNameToSizeInfoA)) |
488 | return E; |
489 | if (auto E = |
490 | tryReadFileAndProcessRemarks(InputFileName: InputFileNameB, FuncNameToSizeInfo&: 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 (&RemarkSizeDiffUtil, |
501 | trySizeSiff); |