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