[llvm] ded733b - Add a tool for diffing size remarks

Jessica Paquette via llvm-commits llvm-commits at lists.llvm.org
Wed Feb 2 16:10:26 PST 2022


Author: Jessica Paquette
Date: 2022-02-02T16:09:54-08:00
New Revision: ded733bd49ff44cf94bd8a7a821ba37390dc94b1

URL: https://github.com/llvm/llvm-project/commit/ded733bd49ff44cf94bd8a7a821ba37390dc94b1
DIFF: https://github.com/llvm/llvm-project/commit/ded733bd49ff44cf94bd8a7a821ba37390dc94b1.diff

LOG: Add a tool for diffing size remarks

This is a tool which can handle bitstream and YAML remarks. The idea here is to
provide more insight into which functions changed in a benchmark when testing
compiler changes.

E.g. "foo got 20% bigger, so maybe we should look more closely at that."

To use the tool, you can use...

```
$ llvm-remark-size-diff remarks_file_a remarks_file_b --parser=yaml|bitstream
```

... on two remarks files containing at least instruction count remarks. This
will output some data on instruction count change and also other relevant
information such as stack size change from `remarks_file_a` to `remarks_file_b`.

This is a bit of a WIP so I'm happy to change the format etc. Ultimately I think
it'd be best to have some JSON output which could be consumed by another tool.
But some base-level, greppable output is very handy to have anyway.

The format I'm proposing here is

```
<files> <inc/dec in inst count> <fn name> <inst count change> <stack B change>
```

Where the files and increase/decrease are indicated like below:

- `<files>` is one of `++` (file B), `--` (file A), `==` (both)
- `<inc/dec in inst count>` is one of `>` (increase) or `<` (decrease)

This makes it easy to grep for things like "which functions appeared in A but
did not appear in B?" Or "what are all the instruction count decreases?"

Differential Revision: https://reviews.llvm.org/D112940

Added: 
    llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-1-instr-1-stack.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/1-func-2-instr-2-stack.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/2-identical-func-1-instr-1-stack.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/empty-file.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/inconvertible-integer.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/no-instruction-count-remarks.yaml
    llvm/test/tools/llvm-remark-size-diff/Inputs/unexpected-key.yaml
    llvm/test/tools/llvm-remark-size-diff/add-remove-func.test
    llvm/test/tools/llvm-remark-size-diff/empty-file.test
    llvm/test/tools/llvm-remark-size-diff/inconvertible-integer.test
    llvm/test/tools/llvm-remark-size-diff/increase-decrease-inst-count.test
    llvm/test/tools/llvm-remark-size-diff/no-difference.test
    llvm/test/tools/llvm-remark-size-diff/no-instruction-count-remarks.test
    llvm/test/tools/llvm-remark-size-diff/unexpected-key.test
    llvm/tools/llvm-remark-size-diff/CMakeLists.txt
    llvm/tools/llvm-remark-size-diff/RemarkSizeDiff.cpp

Modified: 
    llvm/test/CMakeLists.txt
    llvm/test/lit.cfg.py

Removed: 
    


################################################################################
diff  --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index 2d18625e86133..46f87897a2025 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -113,6 +113,7 @@ set(LLVM_TEST_DEPENDS
           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
index 2b3267ab036bc..dbc3c934c94d1 100644
--- a/llvm/test/lit.cfg.py
+++ b/llvm/test/lit.cfg.py
@@ -165,11 +165,12 @@ def get_asan_rtlib():
     '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
index 0000000000000..ae02fc1102d0b
--- /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
index 0000000000000..f44ad697d2e3c
--- /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
index 0000000000000..22e4b1b944ce3
--- /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
index 0000000000000..e69de29bb2d1d

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
index 0000000000000..106e0b2519dfa
--- /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
index 0000000000000..d765cb8de48b0
--- /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
index 0000000000000..450fbcc77681b
--- /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
index 0000000000000..8c7a4331e4d3e
--- /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
index 0000000000000..3d7a868669d17
--- /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
index 0000000000000..cdf2b65188fad
--- /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
index 0000000000000..c4a8753e74eb9
--- /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-
diff erence.test b/llvm/test/tools/llvm-remark-size-
diff /no-
diff erence.test
new file mode 100644
index 0000000000000..4e7aa7b58e92d
--- /dev/null
+++ b/llvm/test/tools/llvm-remark-size-
diff /no-
diff erence.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
index 0000000000000..98ecf775a80ff
--- /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
index 0000000000000..94f452a9b20d0
--- /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
index 0000000000000..c963c1f919b57
--- /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
index 0000000000000..b49930f08089f
--- /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<std::string> InputFileNameA(cl::Positional, cl::Required,
+                                           cl::cat(SizeDiffCategory),
+                                           cl::desc("remarks_a"));
+static cl::opt<std::string> InputFileNameB(cl::Positional, cl::Required,
+                                           cl::cat(SizeDiffCategory),
+                                           cl::desc("remarks_b"));
+static cl::opt<std::string> OutputFilename("o", cl::init("-"),
+                                           cl::cat(SizeDiffCategory),
+                                           cl::desc("Output"),
+                                           cl::value_desc("file"));
+static cl::opt<ParserFormatOptions>
+    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 
diff s 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 
diff s 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<FunctionDiff> OnlyInA;
+
+  /// Diffs for functions which only appeared in the second file.
+  SmallVector<FunctionDiff> OnlyInB;
+
+  /// Diffs for functions which appeared in both files.
+  SmallVector<FunctionDiff> 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 
diff erences.
+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 
diff s.
+  auto PrintDiffList = [&](SmallVector<FunctionDiff> &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 
diff erence 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<int64_t> 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<InstCountAndStackSize> &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<InstCountAndStackSize> &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<remarks::EndOfFileError>())
+    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<InstCountAndStackSize> &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 
diff erence 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<InstCountAndStackSize> &FuncNameToSizeInfoA,
+            const StringMap<InstCountAndStackSize> &FuncNameToSizeInfoB,
+            DiffsCategorizedByFilesPresent &DiffsByFilesPresent) {
+  SmallSet<std::string, 10> 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<std::unique_ptr<ToolOutputFile>> getOutputStream() {
+  if (OutputFilename == "")
+    OutputFilename = "-";
+  std::error_code EC;
+  auto Out = std::make_unique<ToolOutputFile>(OutputFilename, EC,
+                                              sys::fs::OF_TextWithCRLF);
+  if (!EC)
+    return std::move(Out);
+  return EC;
+}
+
+/// Output all 
diff s 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<ToolOutputFile> 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<InstCountAndStackSize> FuncNameToSizeInfoA;
+  StringMap<InstCountAndStackSize> FuncNameToSizeInfoB;
+  if (!tryReadFileAndProcessRemarks(InputFileNameA, FuncNameToSizeInfoA) ||
+      !tryReadFileAndProcessRemarks(InputFileNameB, FuncNameToSizeInfoB))
+    return 1;
+  DiffsCategorizedByFilesPresent DiffsByFilesPresent;
+  computeDiff(FuncNameToSizeInfoA, FuncNameToSizeInfoB, DiffsByFilesPresent);
+  if (!tryOutputAllDiffs(DiffsByFilesPresent))
+    return 1;
+}


        


More information about the llvm-commits mailing list