diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -113,6 +113,7 @@ llvm-readobj llvm-readelf llvm-reduce + llvm-remark-size-diff llvm-rtdyld llvm-sim llvm-size diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -165,11 +165,12 @@ 'llvm-link', 'llvm-lto', 'llvm-lto2', 'llvm-mc', 'llvm-mca', 'llvm-modextract', 'llvm-nm', 'llvm-objcopy', 'llvm-objdump', 'llvm-otool', 'llvm-pdbutil', 'llvm-profdata', 'llvm-profgen', 'llvm-ranlib', 'llvm-rc', 'llvm-readelf', - 'llvm-readobj', 'llvm-rtdyld', 'llvm-sim', 'llvm-size', 'llvm-split', - 'llvm-stress', 'llvm-strings', 'llvm-strip', 'llvm-tblgen', 'llvm-tapi-diff', - 'llvm-undname', 'llvm-windres', 'llvm-c-test', 'llvm-cxxfilt', - 'llvm-xray', 'yaml2obj', 'obj2yaml', 'yaml-bench', 'verify-uselistorder', - 'bugpoint', 'llc', 'llvm-symbolizer', 'opt', 'sancov', 'sanstats']) + 'llvm-readobj', 'llvm-remark-size-diff', 'llvm-rtdyld', 'llvm-sim', + 'llvm-size', 'llvm-split', 'llvm-stress', 'llvm-strings', 'llvm-strip', + 'llvm-tblgen', 'llvm-tapi-diff', 'llvm-undname', 'llvm-windres', + 'llvm-c-test', 'llvm-cxxfilt', 'llvm-xray', 'yaml2obj', 'obj2yaml', + 'yaml-bench', 'verify-uselistorder', 'bugpoint', 'llc', 'llvm-symbolizer', + 'opt', 'sancov', 'sanstats']) # The following tools are optional tools.extend([ diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-1-instr-1-stack.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-1-instr-1-stack.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-1-instr-1-stack.yaml @@ -0,0 +1,16 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: '1' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-2-instr-2-stack.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-2-instr-2-stack.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-2-instr-2-stack.yaml @@ -0,0 +1,16 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '2' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: '2' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/2-identical-func-1-instr-1-stack.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/2-identical-func-1-instr-1-stack.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/2-identical-func-1-instr-1-stack.yaml @@ -0,0 +1,32 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: '1' + - String: ' instructions in function' +... +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func1 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func1 +Args: + - NumInstructions: '1' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/empty-file.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/empty-file.yaml new file mode 100644 diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/inconvertible-integer.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/inconvertible-integer.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/inconvertible-integer.yaml @@ -0,0 +1,16 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - NumInstructions: 'a' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/no-instruction-count-remarks.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/no-instruction-count-remarks.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/no-instruction-count-remarks.yaml @@ -0,0 +1,8 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/Inputs/unexpected-key.yaml b/llvm/test/tools/llvm-remark-size-diff/Inputs/unexpected-key.yaml new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/Inputs/unexpected-key.yaml @@ -0,0 +1,16 @@ +--- !Analysis +Pass: prologepilog +Name: StackSize +Function: func0 +Args: + - NumStackBytes: '1' + - String: ' stack bytes in function' +... +--- !Analysis +Pass: asm-printer +Name: InstructionCount +Function: func0 +Args: + - Wrong: '1' + - String: ' instructions in function' +... diff --git a/llvm/test/tools/llvm-remark-size-diff/add-remove-func.test b/llvm/test/tools/llvm-remark-size-diff/add-remove-func.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/add-remove-func.test @@ -0,0 +1,13 @@ +RUN: llvm-remark-size-diff %p/Inputs/1-func-1-instr-1-stack.yaml %p/Inputs/2-identical-func-1-instr-1-stack.yaml --parser=yaml | FileCheck -strict-whitespace %s --check-prefix=ADD +RUN: llvm-remark-size-diff %p/Inputs/2-identical-func-1-instr-1-stack.yaml %p/Inputs/1-func-1-instr-1-stack.yaml --parser=yaml | FileCheck -strict-whitespace %s --check-prefix=REMOVE + +; The "two-identical-one-instr-funcs" file contains a single-instruction +; function which does not appear in the other file. + +; ADD: ++ > func1, 1 instrs, 1 stack B +; ADD-DAG: instruction count: 1 (100.00%) +; ADD-DAG: stack byte usage: 1 (100.00%) + +; REMOVE: -- < func1, -1 instrs, -1 stack B +; REMOVE-DAG: instruction count: -1 (-50.00%) +; REMOVE-DAG: stack byte usage: -1 (-50.00%) diff --git a/llvm/test/tools/llvm-remark-size-diff/empty-file.test b/llvm/test/tools/llvm-remark-size-diff/empty-file.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/empty-file.test @@ -0,0 +1,4 @@ +RUN: not llvm-remark-size-diff %p/Inputs/empty-file.yaml %p/Inputs/1-func-2-instr-2-stack.yaml --parser=yaml 2>&1 | FileCheck %s +RUN: not llvm-remark-size-diff %p/Inputs/1-func-2-instr-2-stack.yaml %p/Inputs/empty-file.yaml --parser=yaml 2>&1 | FileCheck %s + +; CHECK: error: document root is not of mapping type. diff --git a/llvm/test/tools/llvm-remark-size-diff/inconvertible-integer.test b/llvm/test/tools/llvm-remark-size-diff/inconvertible-integer.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/inconvertible-integer.test @@ -0,0 +1,3 @@ +RUN: not llvm-remark-size-diff %p/Inputs/inconvertible-integer.yaml %p/Inputs/1-func-1-instr-1-stack.yaml --parser=yaml 2>&1 | FileCheck %s + +; CHECK: Could not convert string to signed integer: a diff --git a/llvm/test/tools/llvm-remark-size-diff/increase-decrease-inst-count.test b/llvm/test/tools/llvm-remark-size-diff/increase-decrease-inst-count.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/increase-decrease-inst-count.test @@ -0,0 +1,12 @@ +RUN: llvm-remark-size-diff %p/Inputs/1-func-1-instr-1-stack.yaml %p/Inputs/1-func-2-instr-2-stack.yaml --parser=yaml | FileCheck -strict-whitespace %s --check-prefix=INCREASE +RUN: llvm-remark-size-diff %p/Inputs/1-func-2-instr-2-stack.yaml %p/Inputs/1-func-1-instr-1-stack.yaml --parser=yaml | FileCheck -strict-whitespace %s --check-prefix=DECREASE + +; Test a size increase/decrease of one instruction + 1 stack byte. + +; INCREASE: == > func0, 1 instrs, 1 stack B +; INCREASE-DAG: instruction count: 1 (100.00%) +; INCREASE-NEXT: stack byte usage: 1 (100.00%) + +; DECREASE: == < func0, -1 instrs, -1 stack B +; DECREASE-DAG: instruction count: -1 (-50.00%) +; DECREASE-NEXT: stack byte usage: -1 (-50.00%) diff --git a/llvm/test/tools/llvm-remark-size-diff/no-difference.test b/llvm/test/tools/llvm-remark-size-diff/no-difference.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/no-difference.test @@ -0,0 +1,7 @@ +RUN: llvm-remark-size-diff %p/Inputs/1-func-1-instr-1-stack.yaml %p/Inputs/1-func-1-instr-1-stack.yaml --parser=yaml | FileCheck -strict-whitespace %s + +; Same file passed twice -> no changes reported. + +; CHECK-NOT: {{[0-9]+}} +; CHECK: instruction count: None +; CHECK: stack byte usage: None diff --git a/llvm/test/tools/llvm-remark-size-diff/no-instruction-count-remarks.test b/llvm/test/tools/llvm-remark-size-diff/no-instruction-count-remarks.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/no-instruction-count-remarks.test @@ -0,0 +1,3 @@ +RUN: not llvm-remark-size-diff %p/Inputs/no-instruction-count-remarks.yaml %p/Inputs/no-instruction-count-remarks.yaml --parser=yaml 2>&1 | FileCheck %s + +; CHECK: error: File {{.*}} did not contain any instruction-count remarks! diff --git a/llvm/test/tools/llvm-remark-size-diff/unexpected-key.test b/llvm/test/tools/llvm-remark-size-diff/unexpected-key.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-remark-size-diff/unexpected-key.test @@ -0,0 +1,3 @@ +RUN: not llvm-remark-size-diff %p/Inputs/unexpected-key.yaml %p/Inputs/1-func-1-instr-1-stack.yaml --parser=yaml 2>&1 | FileCheck %s + +; CHECK: Expected 'NumInstructions', got 'Wrong' diff --git a/llvm/tools/llvm-remark-size-diff/CMakeLists.txt b/llvm/tools/llvm-remark-size-diff/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-remark-size-diff/CMakeLists.txt @@ -0,0 +1,5 @@ +set(LLVM_LINK_COMPONENTS Core Demangle Object Remarks Support) + +add_llvm_tool(llvm-remark-size-diff + RemarkSizeDiff.cpp +) diff --git a/llvm/tools/llvm-remark-size-diff/RemarkSizeDiff.cpp b/llvm/tools/llvm-remark-size-diff/RemarkSizeDiff.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-remark-size-diff/RemarkSizeDiff.cpp @@ -0,0 +1,426 @@ +//===-------------- llvm-remark-size-diff/RemarkSizeDiff.cpp --------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Diffs instruction count and stack size remarks between two remark files. +/// +/// This is intended for use by compiler developers who want to see how their +/// changes impact program code size. +/// +/// TODO: Add structured output (JSON, or YAML, or something...) +/// +//===----------------------------------------------------------------------===// + +#include "llvm-c/Remarks.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/Remarks/Remark.h" +#include "llvm/Remarks/RemarkParser.h" +#include "llvm/Remarks/RemarkSerializer.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +enum ParserFormatOptions { yaml, bitstream }; +static cl::OptionCategory SizeDiffCategory("llvm-remark-size-diff options"); +static cl::opt InputFileNameA(cl::Positional, cl::Required, + cl::cat(SizeDiffCategory), + cl::desc("remarks_a")); +static cl::opt InputFileNameB(cl::Positional, cl::Required, + cl::cat(SizeDiffCategory), + cl::desc("remarks_b")); +static cl::opt OutputFilename("o", cl::init("-"), + cl::cat(SizeDiffCategory), + cl::desc("Output"), + cl::value_desc("file")); +static cl::opt + ParserFormat("parser", cl::cat(SizeDiffCategory), cl::init(bitstream), + cl::desc("Set the remark parser format:"), + cl::values(clEnumVal(yaml, "YAML format"), + clEnumVal(bitstream, "Bitstream format"))); + +/// Contains information from size remarks. +// This is a little nicer to read than a std::pair. +struct InstCountAndStackSize { + int64_t InstCount = 0; + int64_t StackSize = 0; +}; + +/// Represents which files a function appeared in. +enum FilesPresent { A, B, BOTH }; + +/// Contains the data from the remarks in file A and file B for some function. +/// E.g. instruction count, stack size... +struct FunctionDiff { + /// Function name from the remark. + std::string FuncName; + // Idx 0 = A, Idx 1 = B. + int64_t InstCount[2] = {0, 0}; + int64_t StackSize[2] = {0, 0}; + + // Calculate diffs between the first and second files. + int64_t getInstDiff() const { return InstCount[1] - InstCount[0]; } + int64_t getStackDiff() const { return StackSize[1] - StackSize[0]; } + + // Accessors for the remarks from the first file. + int64_t getInstCountA() const { return InstCount[0]; } + int64_t getStackSizeA() const { return StackSize[0]; } + + // Accessors for the remarks from the second file. + int64_t getInstCountB() const { return InstCount[1]; } + int64_t getStackSizeB() const { return StackSize[1]; } + + /// \returns which files this function was present in. + FilesPresent getFilesPresent() const { + if (getInstCountA() == 0) + return B; + if (getInstCountB() == 0) + return A; + return BOTH; + } + + FunctionDiff(StringRef FuncName, const InstCountAndStackSize &A, + const InstCountAndStackSize &B) + : FuncName(FuncName) { + InstCount[0] = A.InstCount; + InstCount[1] = B.InstCount; + StackSize[0] = A.StackSize; + StackSize[1] = B.StackSize; + } +}; + +/// Organizes the diffs into 3 categories: +/// - Functions which only appeared in the first file +/// - Functions which only appeared in the second file +/// - Functions which appeared in both files +struct DiffsCategorizedByFilesPresent { + /// Diffs for functions which only appeared in the first file. + SmallVector OnlyInA; + + /// Diffs for functions which only appeared in the second file. + SmallVector OnlyInB; + + /// Diffs for functions which appeared in both files. + SmallVector InBoth; + + /// Add a diff to the appropriate list. + void addDiff(FunctionDiff &FD) { + switch (FD.getFilesPresent()) { + case A: + OnlyInA.push_back(FD); + break; + case B: + OnlyInB.push_back(FD); + break; + case BOTH: + InBoth.push_back(FD); + break; + } + } +}; + +static void printFunctionDiff(const FunctionDiff &FD, llvm::raw_ostream &OS) { + // Describe which files the function had remarks in. + auto FilesPresent = FD.getFilesPresent(); + const auto &FuncName = FD.FuncName; + const int64_t InstDiff = FD.getInstDiff(); + assert(InstDiff && "Shouldn't get functions with no size change?"); + const int64_t StackDiff = FD.getStackDiff(); + // Output an indicator denoting which files the function was present in. + switch (FilesPresent) { + case FilesPresent::A: + OS << "-- "; + break; + case FilesPresent::B: + OS << "++ "; + break; + case FilesPresent::BOTH: + OS << "== "; + break; + } + // Output an indicator denoting if a function changed in size. + if (InstDiff > 0) + OS << "> "; + else + OS << "< "; + OS << FuncName << ", "; + OS << InstDiff << " instrs, "; + OS << StackDiff << " stack B"; + OS << "\n"; +} + +/// Print an item in the summary section. +/// +/// \p TotalA - Total count of the metric in file A. +/// \p TotalB - Total count of the metric in file B. +/// \p Metric - Name of the metric we want to print (e.g. instruction +/// count). +/// \p OS - The output stream. +static void printSummaryItem(int64_t TotalA, int64_t TotalB, StringRef Metric, + llvm::raw_ostream &OS) { + OS << " " << Metric << ": "; + int64_t TotalDiff = TotalB - TotalA; + if (TotalDiff == 0) { + OS << "None\n"; + return; + } + OS << TotalDiff << " (" << formatv("{0:p}", TotalDiff / (double)TotalA) + << ")\n"; +} + +/// Print all contents of \p Diff and a high-level summary of the differences. +static void printDiffsCategorizedByFilesPresent( + DiffsCategorizedByFilesPresent &DiffsByFilesPresent, + llvm::raw_ostream &OS) { + int64_t InstrsA = 0; + int64_t InstrsB = 0; + int64_t StackA = 0; + int64_t StackB = 0; + // Helper lambda to sort + print a list of diffs. + auto PrintDiffList = [&](SmallVector &FunctionDiffList) { + if (FunctionDiffList.empty()) + return; + stable_sort(FunctionDiffList, + [](const FunctionDiff &LHS, const FunctionDiff &RHS) { + return LHS.getInstDiff() < RHS.getInstDiff(); + }); + for (const auto &FuncDiff : FunctionDiffList) { + // If there is a difference in instruction count, then print out info for + // the function. + if (FuncDiff.getInstDiff()) + printFunctionDiff(FuncDiff, OS); + InstrsA += FuncDiff.getInstCountA(); + InstrsB += FuncDiff.getInstCountB(); + StackA += FuncDiff.getStackSizeA(); + StackB += FuncDiff.getStackSizeB(); + } + }; + PrintDiffList(DiffsByFilesPresent.OnlyInA); + PrintDiffList(DiffsByFilesPresent.OnlyInB); + PrintDiffList(DiffsByFilesPresent.InBoth); + OS << "\n### Summary ###\n"; + OS << "Total change: \n"; + printSummaryItem(InstrsA, InstrsB, "instruction count", OS); + printSummaryItem(StackA, StackB, "stack byte usage", OS); +} + +/// Collects an expected integer value from a given argument index in a remark. +/// +/// \p Remark - The remark. +/// \p ArgIdx - The index where the integer value should be found. +/// \p ExpectedKeyName - The expected key name for the index +/// (e.g. "InstructionCount") +/// +/// \returns the integer value at the index if it exists, and the key-value pair +/// is what is expected. Otherwise, returns an Error. +static Expected getIntValFromKey(const remarks::Remark &Remark, + unsigned ArgIdx, + StringRef ExpectedKeyName) { + auto KeyName = Remark.Args[ArgIdx].Key; + if (KeyName != ExpectedKeyName) + return createStringError( + inconvertibleErrorCode(), + Twine("Unexpected key at argument index " + std::to_string(ArgIdx) + + ": Expected '" + ExpectedKeyName + "', got '" + KeyName + "'")); + int64_t Val; + auto ValStr = Remark.Args[ArgIdx].Val; + if (getAsSignedInteger(ValStr, 0, Val)) + return createStringError( + inconvertibleErrorCode(), + Twine("Could not convert string to signed integer: " + ValStr)); + return Val; +} + +/// Collects relevant size information from \p Remark if it is an size-related +/// remark of some kind (e.g. instruction count). Otherwise records nothing. +/// +/// \p Remark - The remark. +/// \p FuncNameToSizeInfo - Maps function names to relevant size info. +/// \p NumInstCountRemarksParsed - Keeps track of the number of instruction +/// count remarks parsed. We need at least 1 in both files to produce a diff. +static Error processRemark(const remarks::Remark &Remark, + StringMap &FuncNameToSizeInfo, + unsigned &NumInstCountRemarksParsed) { + const auto &RemarkName = Remark.RemarkName; + const auto &PassName = Remark.PassName; + // Collect remarks which contain the number of instructions in a function. + if (PassName == "asm-printer" && RemarkName == "InstructionCount") { + // Expecting the 0-th argument to have the key "NumInstructions" and an + // integer value. + auto MaybeInstCount = + getIntValFromKey(Remark, /*ArgIdx = */ 0, "NumInstructions"); + if (!MaybeInstCount) + return MaybeInstCount.takeError(); + FuncNameToSizeInfo[Remark.FunctionName].InstCount = *MaybeInstCount; + ++NumInstCountRemarksParsed; + } + // Collect remarks which contain the stack size of a function. + else if (PassName == "prologepilog" && RemarkName == "StackSize") { + // Expecting the 0-th argument to have the key "NumStackBytes" and an + // integer value. + auto MaybeStackSize = + getIntValFromKey(Remark, /*ArgIdx = */ 0, "NumStackBytes"); + if (!MaybeStackSize) + return MaybeStackSize.takeError(); + FuncNameToSizeInfo[Remark.FunctionName].StackSize = *MaybeStackSize; + } + // Either we collected a remark, or it's something we don't care about. In + // both cases, this is a success. + return Error::success(); +} + +/// Process all of the size-related remarks in a file. +/// +/// \param[in] InputFileName - Name of file to read from. +/// \param[in, out] FuncNameToSizeInfo - Maps function names to relevant +/// size info. +static Error readFileAndProcessRemarks( + StringRef InputFileName, + StringMap &FuncNameToSizeInfo) { + auto Buf = MemoryBuffer::getFile(InputFileName); + if (auto EC = Buf.getError()) + return createStringError( + EC, Twine("Cannot open file '" + InputFileName + "': " + EC.message())); + auto MaybeParser = remarks::createRemarkParserFromMeta( + ParserFormat == bitstream ? remarks::Format::Bitstream + : remarks::Format::YAML, + (*Buf)->getBuffer()); + if (!MaybeParser) + return MaybeParser.takeError(); + auto &Parser = **MaybeParser; + auto MaybeRemark = Parser.next(); + unsigned NumInstCountRemarksParsed = 0; + for (; MaybeRemark; MaybeRemark = Parser.next()) { + if (auto E = processRemark(**MaybeRemark, FuncNameToSizeInfo, + NumInstCountRemarksParsed)) + return E; + } + auto E = MaybeRemark.takeError(); + if (!E.isA()) + return E; + consumeError(std::move(E)); + // We need at least one instruction count remark in each file to produce a + // meaningful diff. + if (NumInstCountRemarksParsed == 0) + return createStringError( + inconvertibleErrorCode(), + "File '" + InputFileName + + "' did not contain any instruction-count remarks!"); + return Error::success(); +} + +/// Wrapper function for readFileAndProcessRemarks which handles errors. +/// +/// \param[in] InputFileName - Name of file to read from. +/// \param[out] FuncNameToSizeInfo - Populated with information from size +/// remarks in the input file. +/// +/// \returns true if readFileAndProcessRemarks returned no errors. False +/// otherwise. +static bool tryReadFileAndProcessRemarks( + StringRef InputFileName, + StringMap &FuncNameToSizeInfo) { + if (Error E = readFileAndProcessRemarks(InputFileName, FuncNameToSizeInfo)) { + handleAllErrors(std::move(E), [&](const ErrorInfoBase &PE) { + PE.log(WithColor::error()); + errs() << '\n'; + }); + return false; + } + return true; +} + +/// Populates \p FuncDiffs with the difference between \p +/// FuncNameToSizeInfoA and \p FuncNameToSizeInfoB. +/// +/// \param[in] FuncNameToSizeInfoA - Size info collected from the first +/// remarks file. +/// \param[in] FuncNameToSizeInfoB - Size info collected from +/// the second remarks file. +/// \param[out] D - Filled with the diff between \p FuncNameToSizeInfoA and +/// \p FuncNameToSizeInfoB. +static void +computeDiff(const StringMap &FuncNameToSizeInfoA, + const StringMap &FuncNameToSizeInfoB, + DiffsCategorizedByFilesPresent &DiffsByFilesPresent) { + SmallSet FuncNames; + for (const auto &FuncName : FuncNameToSizeInfoA.keys()) + FuncNames.insert(FuncName.str()); + for (const auto &FuncName : FuncNameToSizeInfoB.keys()) + FuncNames.insert(FuncName.str()); + for (const std::string &FuncName : FuncNames) { + const auto &SizeInfoA = FuncNameToSizeInfoA.lookup(FuncName); + const auto &SizeInfoB = FuncNameToSizeInfoB.lookup(FuncName); + FunctionDiff FuncDiff(FuncName, SizeInfoA, SizeInfoB); + DiffsByFilesPresent.addDiff(FuncDiff); + } +} + +/// Attempt to get the output stream for writing the diff. +static ErrorOr> getOutputStream() { + if (OutputFilename == "") + OutputFilename = "-"; + std::error_code EC; + auto Out = std::make_unique(OutputFilename, EC, + sys::fs::OF_TextWithCRLF); + if (!EC) + return std::move(Out); + return EC; +} + +/// Output all diffs in \p DiffsByFilesPresent. +/// \returns Error::success() on success, and an Error otherwise. +static Error +outputAllDiffs(DiffsCategorizedByFilesPresent &DiffsByFilesPresent) { + auto MaybeOF = getOutputStream(); + if (std::error_code EC = MaybeOF.getError()) + return errorCodeToError(EC); + std::unique_ptr OF = std::move(*MaybeOF); + printDiffsCategorizedByFilesPresent(DiffsByFilesPresent, OF->os()); + OF->keep(); + return Error::success(); +} + +/// Boolean wrapper for outputDiff which handles errors. +static bool +tryOutputAllDiffs(DiffsCategorizedByFilesPresent &DiffsByFilesPresent) { + if (Error E = outputAllDiffs(DiffsByFilesPresent)) { + handleAllErrors(std::move(E), [&](const ErrorInfoBase &PE) { + PE.log(WithColor::error()); + errs() << '\n'; + }); + return false; + } + return true; +} + +int main(int argc, const char **argv) { + InitLLVM X(argc, argv); + cl::HideUnrelatedOptions(SizeDiffCategory); + cl::ParseCommandLineOptions(argc, argv, + "Diff instruction count and stack size remarks " + "between two remark files.\n"); + StringMap FuncNameToSizeInfoA; + StringMap FuncNameToSizeInfoB; + if (!tryReadFileAndProcessRemarks(InputFileNameA, FuncNameToSizeInfoA) || + !tryReadFileAndProcessRemarks(InputFileNameB, FuncNameToSizeInfoB)) + return 1; + DiffsCategorizedByFilesPresent DiffsByFilesPresent; + computeDiff(FuncNameToSizeInfoA, FuncNameToSizeInfoB, DiffsByFilesPresent); + if (!tryOutputAllDiffs(DiffsByFilesPresent)) + return 1; +}