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