[llvm] 128214f - [llvm-remarkutil] Introduce summary tool (#160549)

via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 27 09:54:42 PDT 2025


Author: Tobias Stadler
Date: 2025-10-27T09:54:38-07:00
New Revision: 128214f3b2a4b470a4b45f9b2eece7a439d795d7

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

LOG: [llvm-remarkutil] Introduce summary tool (#160549)

This tool provides a harness for implementing different strategies that
summarize many remarks (possibly from multiple translation units) into
new summary remarks. The remark summaries can then be viewed using tools
like `opt-viewer`.

The first summary strategy is `--inline-callees`, which generates
remarks that summarize the per-callee inline statistics for functions
that appear in inling remarks. This is useful for troubleshooting
inlining issues/regressions on large codebases.

Pull Request: https://github.com/llvm/llvm-project/pull/160549

Added: 
    llvm/test/tools/llvm-remarkutil/summary/Inputs/inline.yaml
    llvm/test/tools/llvm-remarkutil/summary/inline.test
    llvm/tools/llvm-remarkutil/RemarkSummary.cpp

Modified: 
    llvm/include/llvm/Remarks/Remark.h
    llvm/lib/Remarks/Remark.cpp
    llvm/test/tools/llvm-remarkutil/broken-bitstream-remark-magic.test
    llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test
    llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test
    llvm/test/tools/llvm-remarkutil/empty-file.test
    llvm/tools/llvm-remarkutil/CMakeLists.txt
    llvm/tools/llvm-remarkutil/RemarkCounter.cpp
    llvm/tools/llvm-remarkutil/RemarkUtilHelpers.h

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Remarks/Remark.h b/llvm/include/llvm/Remarks/Remark.h
index 8c8ca769c7d16..663af6302d6ff 100644
--- a/llvm/include/llvm/Remarks/Remark.h
+++ b/llvm/include/llvm/Remarks/Remark.h
@@ -51,12 +51,21 @@ struct Argument {
   // If set, the debug location corresponding to the value.
   std::optional<RemarkLocation> Loc;
 
+  Argument() = default;
+  Argument(StringRef Key, StringRef Val) : Key(Key), Val(Val) {}
+
   /// Implement operator<< on Argument.
   LLVM_ABI void print(raw_ostream &OS) const;
-  /// Return the value of argument as int.
-  LLVM_ABI std::optional<int> getValAsInt() const;
-  /// Check if the argument value can be parsed as int.
-  LLVM_ABI bool isValInt() const;
+
+  /// Return the value of argument as an integer of type T.
+  template <typename T>
+  std::optional<T> getValAsInt(unsigned Radix = 10) const {
+    StringRef Str = Val;
+    T Res;
+    if (Str.consumeInteger<T>(Radix, Res) || !Str.empty())
+      return std::nullopt;
+    return Res;
+  }
 };
 
 // Create wrappers for C Binding types (see CBindingWrapping.h).
@@ -127,6 +136,10 @@ struct Remark {
   /// Return a message composed from the arguments as a string.
   LLVM_ABI std::string getArgsAsMsg() const;
 
+  /// Return the first argument with the specified key or nullptr if no such
+  /// argument was found.
+  LLVM_ABI Argument *getArgByKey(StringRef Key);
+
   /// Clone this remark to explicitly ask for a copy.
   Remark clone() const { return *this; }
 

diff  --git a/llvm/lib/Remarks/Remark.cpp b/llvm/lib/Remarks/Remark.cpp
index 0e98cad8e9045..09f24e93255e0 100644
--- a/llvm/lib/Remarks/Remark.cpp
+++ b/llvm/lib/Remarks/Remark.cpp
@@ -13,6 +13,7 @@
 #include "llvm/Remarks/Remark.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
 #include <optional>
 
 using namespace llvm;
@@ -26,16 +27,13 @@ std::string Remark::getArgsAsMsg() const {
   return Str;
 }
 
-/// Returns the value of a specified key parsed from StringRef.
-std::optional<int> Argument::getValAsInt() const {
-  APInt KeyVal;
-  if (Val.getAsInteger(10, KeyVal))
-    return std::nullopt;
-  return KeyVal.getSExtValue();
+Argument *Remark::getArgByKey(StringRef Key) {
+  auto *It = find_if(Args, [&](auto &Arg) { return Arg.Key == Key; });
+  if (It == Args.end())
+    return nullptr;
+  return &*It;
 }
 
-bool Argument::isValInt() const { return getValAsInt().has_value(); }
-
 void RemarkLocation::print(raw_ostream &OS) const {
   OS << "{ "
      << "File: " << SourceFilePath << ", Line: " << SourceLine

diff  --git a/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark-magic.test b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark-magic.test
index c21dbd72a2a18..9d64201cc071e 100644
--- a/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark-magic.test
+++ b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark-magic.test
@@ -3,5 +3,6 @@ RUN: not llvm-remarkutil instruction-mix %p/Inputs/broken-remark-magic.bitstream
 RUN: not llvm-remarkutil annotation-count --annotation-type=remark %p/Inputs/broken-remark-magic.bitstream -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil count %p/Inputs/broken-remark-magic.bitstream -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil filter %p/Inputs/broken-remark-magic.bitstream -o - 2>&1 | FileCheck %s
+RUN: not llvm-remarkutil summary %p/Inputs/broken-remark-magic.bitstream -o - 2>&1 | FileCheck %s
 
 CHECK: error: Automatic detection of remark format failed. Unknown magic number: '1234'

diff  --git a/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test
index 339f082d4825b..0a668131c801c 100644
--- a/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test
+++ b/llvm/test/tools/llvm-remarkutil/broken-bitstream-remark.test
@@ -3,5 +3,6 @@ RUN: not llvm-remarkutil instruction-count --parser=bitstream %p/Inputs/broken-r
 RUN: not llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remark %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil count --parser=bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil filter --parser=bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
+RUN: not llvm-remarkutil summary --parser=bitstream %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 
 CHECK: error: Unknown magic number: expecting RMRK, got --- .

diff  --git a/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test b/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test
index 9da3de4034b0f..76b2d5610d8cd 100644
--- a/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test
+++ b/llvm/test/tools/llvm-remarkutil/broken-yaml-remark.test
@@ -4,5 +4,6 @@ RUN: not llvm-remarkutil instruction-mix --parser=yaml %p/Inputs/broken-remark -
 RUN: not llvm-remarkutil annotation-count --parser=yaml --annotation-type=remark %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil count --parser=yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 RUN: not llvm-remarkutil filter --parser=yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
+RUN: not llvm-remarkutil summary --parser=yaml %p/Inputs/broken-remark -o - 2>&1 | FileCheck %s
 
 CHECK: error: Type, Pass, Name or Function missing

diff  --git a/llvm/test/tools/llvm-remarkutil/empty-file.test b/llvm/test/tools/llvm-remarkutil/empty-file.test
index 9b2b000e9c24b..53f04f36226a5 100644
--- a/llvm/test/tools/llvm-remarkutil/empty-file.test
+++ b/llvm/test/tools/llvm-remarkutil/empty-file.test
@@ -4,18 +4,21 @@ RUN: not llvm-remarkutil instruction-mix --parser=yaml %p/Inputs/empty-file -o -
 RUN: not llvm-remarkutil annotation-count --parser=yaml --annotation-type=remark %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
 RUN: not llvm-remarkutil count --parser=yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
 RUN: not llvm-remarkutil filter --parser=yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
+RUN: not llvm-remarkutil summary --parser=yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --check-prefix=YAMLPARSER
 RUN: llvm-remarkutil bitstream2yaml %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=BITSTREAM2YAML
 RUN: llvm-remarkutil instruction-count --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=SIZEBITSTREAM
 RUN: llvm-remarkutil instruction-mix --parser=bitstream %p/Inputs/empty-file --report_style=csv -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=MIXBITSTREAM
 RUN: llvm-remarkutil annotation-count --parser=bitstream --annotation-type=remark %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=ANNOTATIONBITSTREAM
 RUN: llvm-remarkutil count --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=COUNTBITSTREAM
-RUN: llvm-remarkutil filter --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=FILTERBITSTREAM
+RUN: llvm-remarkutil filter --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=EMPTYBITSTREAM
+RUN: llvm-remarkutil summary --parser=bitstream %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=EMPTYBITSTREAM
 ; Parser format auto-detection should treat empty files as bitstream files
 RUN: llvm-remarkutil instruction-count %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=SIZEBITSTREAM
 RUN: llvm-remarkutil instruction-mix %p/Inputs/empty-file --report_style=csv -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=MIXBITSTREAM
 RUN: llvm-remarkutil annotation-count --annotation-type=remark %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=ANNOTATIONBITSTREAM
 RUN: llvm-remarkutil count %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=COUNTBITSTREAM
-RUN: llvm-remarkutil filter %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=FILTERBITSTREAM
+RUN: llvm-remarkutil filter %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=EMPTYBITSTREAM
+RUN: llvm-remarkutil summary %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allow-empty --check-prefix=EMPTYBITSTREAM
 
 ; YAMLPARSER: error: document root is not of mapping type.
 
@@ -34,4 +37,4 @@ RUN: llvm-remarkutil filter %p/Inputs/empty-file -o - 2>&1 | FileCheck %s --allo
 ; MIXBITSTREAM-LABEL: Instruction,Count
 ; MIXBITSTREAM-EMPTY:
 
-; FILTERBITSTREAM-NOT: {{.}}
+; EMPTYBITSTREAM-NOT: {{.}}

diff  --git a/llvm/test/tools/llvm-remarkutil/summary/Inputs/inline.yaml b/llvm/test/tools/llvm-remarkutil/summary/Inputs/inline.yaml
new file mode 100644
index 0000000000000..efb8cd6ecf5a9
--- /dev/null
+++ b/llvm/test/tools/llvm-remarkutil/summary/Inputs/inline.yaml
@@ -0,0 +1,50 @@
+--- !Missed
+Pass:            inline
+Name:            TooCostly
+DebugLoc:        { File: 'foo.cpp', Line: 21, Column: 6 }
+Function:        fooCaller
+Args:
+  - Callee:          fooCallee
+    DebugLoc:        { File: 'foo.cpp', Line: 10, Column: 0 }
+  - Caller:          fooCaller
+    DebugLoc:        { File: 'foo.cpp', Line: 20, Column: 0 }
+  - Cost:            '125'
+  - Threshold:       '100'
+...
+--- !Passed
+Pass:            inline
+Name:            Inlined
+DebugLoc:        { File: 'foo.cpp', Line: 21, Column: 6 }
+Function:        fooCaller2
+Args:
+  - Callee:          fooCallee
+    DebugLoc:        { File: 'foo.cpp', Line: 10, Column: 0 }
+  - Caller:          fooCaller
+    DebugLoc:        { File: 'foo.cpp', Line: 20, Column: 0 }
+  - Cost:            '-15'
+  - Threshold:       '100'
+  - Line:            '1'
+  - Column:          '6'
+...
+--- !Passed
+Pass:            inline
+Name:            AlwaysInline
+DebugLoc:        { File: 'bar.cpp', Line: 23, Column: 10 }
+Function:        barCaller
+Args:
+  - Callee:          barCallee
+    DebugLoc:        { File: 'bar.cpp', Line: 5, Column: 0 }
+  - Caller:          barCaller
+    DebugLoc:        { File: 'bar.cpp', Line: 22, Column: 0 }
+  - Reason:          always inline attribute
+  - Line:            '23'
+  - Column:          '10'
+...
+--- !Missed
+Pass:            inline
+Name:            NoDefinition
+Function:        bazCaller
+Args:
+  - Callee:          bazCallee
+  - Caller:          bazCaller
+...

diff  --git a/llvm/test/tools/llvm-remarkutil/summary/inline.test b/llvm/test/tools/llvm-remarkutil/summary/inline.test
new file mode 100644
index 0000000000000..57473186e63e3
--- /dev/null
+++ b/llvm/test/tools/llvm-remarkutil/summary/inline.test
@@ -0,0 +1,54 @@
+RUN: llvm-remarkutil summary --inline-callees %p/Inputs/inline.yaml | FileCheck -strict-whitespace %s
+
+; CHECK: --- !Analysis
+; CHECK-NEXT: Pass:            inline
+; CHECK-NEXT: Name:            Summary
+; CHECK-NEXT: DebugLoc:        { File: bar.cpp, Line: 5, Column: 0 }
+; CHECK-NEXT: Function:        barCallee
+; CHECK-NEXT: Args:
+; CHECK-NEXT:   - String:          'Incoming Calls ('
+; CHECK-NEXT:   - String:          AlwaysInline
+; CHECK-NEXT:   - String:          ': '
+; CHECK-NEXT:   - AlwaysInline:    '1'
+; CHECK-NEXT:   - String:          ')'
+; CHECK-NEXT: ...
+; CHECK-NEXT: --- !Analysis
+; CHECK-NEXT: Pass:            inline
+; CHECK-NEXT: Name:            Summary
+; CHECK-NEXT: Function:        bazCallee
+; CHECK-NEXT: Args:
+; CHECK-NEXT:   - String:          'Incoming Calls ('
+; CHECK-NEXT:   - String:          NoDefinition
+; CHECK-NEXT:   - String:          ': '
+; CHECK-NEXT:   - NoDefinition:    '1'
+; CHECK-NEXT:   - String:          ')'
+; CHECK-NEXT: ...
+; CHECK-NEXT: --- !Analysis
+; CHECK-NEXT: Pass:            inline
+; CHECK-NEXT: Name:            Summary
+; CHECK-NEXT: DebugLoc:        { File: foo.cpp, Line: 10, Column: 0 }
+; CHECK-NEXT: Function:        fooCallee
+; CHECK-NEXT: Args:
+; CHECK-NEXT:   - String:          'Incoming Calls ('
+; CHECK-NEXT:   - String:          Inlined
+; CHECK-NEXT:   - String:          ': '
+; CHECK-NEXT:   - Inlined:         '1'
+; CHECK-NEXT:   - String:          ', '
+; CHECK-NEXT:   - String:          TooCostly
+; CHECK-NEXT:   - String:          ': '
+; CHECK-NEXT:   - TooCostly:       '1'
+; CHECK-NEXT:   - String:          ')'
+; CHECK-NEXT:   - String:          "\nLeast profitable (cost="
+; CHECK-NEXT:   - LeastProfitCost: '125'
+; CHECK-NEXT:     DebugLoc:        { File: foo.cpp, Line: 21, Column: 6 }
+; CHECK-NEXT:   - String:          ', threshold='
+; CHECK-NEXT:   - LeastProfitThreshold: '100'
+; CHECK-NEXT:   - String:          ')'
+; CHECK-NEXT:   - String:          "\nMost profitable (cost="
+; CHECK-NEXT:   - MostProfitCost:  '-15'
+; CHECK-NEXT:     DebugLoc:        { File: foo.cpp, Line: 21, Column: 6 }
+; CHECK-NEXT:   - String:          ', threshold='
+; CHECK-NEXT:   - MostProfitThreshold: '100'
+; CHECK-NEXT:   - String:          ')'
+; CHECK-NEXT: ...
+; CHECK-NOT: {{.}}

diff  --git a/llvm/tools/llvm-remarkutil/CMakeLists.txt b/llvm/tools/llvm-remarkutil/CMakeLists.txt
index c6e9334d87c04..3f0a4360266e1 100644
--- a/llvm/tools/llvm-remarkutil/CMakeLists.txt
+++ b/llvm/tools/llvm-remarkutil/CMakeLists.txt
@@ -11,6 +11,7 @@ add_llvm_tool(llvm-remarkutil
   RemarkFilter.cpp
   RemarkInstructionMix.cpp
   RemarkSizeDiff.cpp
+  RemarkSummary.cpp
   RemarkUtil.cpp
   RemarkUtilHelpers.cpp
   RemarkUtilRegistry.cpp

diff  --git a/llvm/tools/llvm-remarkutil/RemarkCounter.cpp b/llvm/tools/llvm-remarkutil/RemarkCounter.cpp
index 2e842c8c2d72e..4e429b75e3c2d 100644
--- a/llvm/tools/llvm-remarkutil/RemarkCounter.cpp
+++ b/llvm/tools/llvm-remarkutil/RemarkCounter.cpp
@@ -70,11 +70,11 @@ static cl::opt<GroupBy> GroupByOpt(
 /// integer value or 0 if it is has no integer value.
 static unsigned getValForKey(StringRef Key, const Remark &Remark) {
   auto *RemarkArg = find_if(Remark.Args, [&Key](const Argument &Arg) {
-    return Arg.Key == Key && Arg.isValInt();
+    return Arg.Key == Key && Arg.getValAsInt<unsigned>();
   });
   if (RemarkArg == Remark.Args.end())
     return 0;
-  return *RemarkArg->getValAsInt();
+  return *RemarkArg->getValAsInt<unsigned>();
 }
 
 Error ArgumentCounter::getAllMatchingArgumentsInRemark(
@@ -91,7 +91,7 @@ Error ArgumentCounter::getAllMatchingArgumentsInRemark(
       continue;
     for (auto &Key : Arguments) {
       for (Argument Arg : Remark.Args)
-        if (Key.match(Arg.Key) && Arg.isValInt())
+        if (Key.match(Arg.Key) && Arg.getValAsInt<unsigned>())
           ArgumentSetIdxMap.insert({Arg.Key, ArgumentSetIdxMap.size()});
     }
   }

diff  --git a/llvm/tools/llvm-remarkutil/RemarkSummary.cpp b/llvm/tools/llvm-remarkutil/RemarkSummary.cpp
new file mode 100644
index 0000000000000..124bd51720d17
--- /dev/null
+++ b/llvm/tools/llvm-remarkutil/RemarkSummary.cpp
@@ -0,0 +1,254 @@
+//===- RemarkSummary.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Specialized tool to summarize remarks
+//
+//===----------------------------------------------------------------------===//
+
+#include "RemarkUtilHelpers.h"
+#include "RemarkUtilRegistry.h"
+
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/WithColor.h"
+#include <memory>
+
+using namespace llvm;
+using namespace remarks;
+using namespace llvm::remarkutil;
+
+namespace summary {
+
+static cl::SubCommand
+    SummarySub("summary", "Summarize remarks using 
diff erent strategies.");
+
+INPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub)
+OUTPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub)
+INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SummarySub)
+
+static cl::OptionCategory SummaryStrategyCat("Strategy options");
+
+enum class KeepMode { None, Used, All };
+
+static cl::opt<KeepMode> KeepInputOpt(
+    "keep", cl::desc("Keep input remarks in output"), cl::init(KeepMode::None),
+    cl::values(clEnumValN(KeepMode::None, "none",
+                          "Don't keep input remarks (default)"),
+               clEnumValN(KeepMode::Used, "used",
+                          "Keep only remarks used for summary"),
+               clEnumValN(KeepMode::All, "all", "Keep all input remarks")),
+    cl::sub(SummarySub));
+
+static cl::opt<bool>
+    IgnoreMalformedOpt("ignore-malformed",
+                       cl::desc("Ignore remarks that fail to process"),
+                       cl::init(false), cl::Hidden, cl::sub(SummarySub));
+
+// Use one cl::opt per Strategy, because future strategies might need to take
+// per-strategy parameters.
+static cl::opt<bool> EnableInlineSummaryOpt(
+    "inline-callees", cl::desc("Summarize per-callee inling statistics"),
+    cl::cat(SummaryStrategyCat), cl::init(false), cl::sub(SummarySub));
+
+/// An interface to implement 
diff erent strategies for creating remark
+/// summaries. Override this class to develop new strategies.
+class SummaryStrategy {
+public:
+  virtual ~SummaryStrategy() = default;
+
+  /// Strategy should return true if it wants to process the remark \p R.
+  virtual bool filter(Remark &R) = 0;
+
+  /// Hook to process the remark \p R (i.e. collect the necessary data for
+  /// producing summary remarks). This will only be called with remarks
+  /// accepted by filter(). Can return an error if \p R is malformed or
+  /// unexpected.
+  virtual Error process(Remark &R) = 0;
+
+  /// Hook to emit new remarks based on the collected data.
+  virtual void emit(RemarkSerializer &Serializer) = 0;
+};
+
+/// Check if any summary strategy options are explicitly enabled.
+static bool isAnyStrategyRequested() {
+  StringMap<cl::Option *> Opts = cl::getRegisteredOptions(SummarySub);
+  for (auto &[_, Opt] : Opts) {
+    if (!is_contained(Opt->Categories, &SummaryStrategyCat))
+      continue;
+    if (!Opt->getNumOccurrences())
+      continue;
+    return true;
+  }
+  return false;
+}
+
+class InlineCalleeSummary : public SummaryStrategy {
+  struct CallsiteCost {
+    int Cost = 0;
+    int Threshold = 0;
+    std::optional<RemarkLocation> Loc;
+
+    int getProfit() const { return Threshold - Cost; }
+
+    friend bool operator==(const CallsiteCost &A, const CallsiteCost &B) {
+      return A.Cost == B.Cost && A.Threshold == B.Threshold && A.Loc == B.Loc;
+    }
+
+    friend bool operator!=(const CallsiteCost &A, const CallsiteCost &B) {
+      return !(A == B);
+    }
+  };
+
+  struct CalleeSummary {
+    SmallDenseMap<StringRef, size_t> Stats;
+    std::optional<RemarkLocation> Loc;
+    std::optional<CallsiteCost> LeastProfit;
+    std::optional<CallsiteCost> MostProfit;
+
+    void updateCost(CallsiteCost NewCost) {
+      if (!LeastProfit || NewCost.getProfit() < LeastProfit->getProfit())
+        LeastProfit = NewCost;
+      if (!MostProfit || NewCost.getProfit() > MostProfit->getProfit())
+        MostProfit = NewCost;
+    }
+  };
+
+  DenseMap<StringRef, CalleeSummary> Callees;
+
+  Error malformed() { return createStringError("Malformed inline remark."); }
+
+  bool filter(Remark &R) override {
+    return R.PassName == "inline" && R.RemarkName != "Summary";
+  }
+
+  Error process(Remark &R) override {
+    auto *CalleeArg = R.getArgByKey("Callee");
+    if (!CalleeArg)
+      return Error::success();
+    auto &Callee = Callees[CalleeArg->Val];
+    ++Callee.Stats[R.RemarkName];
+    if (!Callee.Loc)
+      Callee.Loc = CalleeArg->Loc;
+
+    Argument *CostArg = R.getArgByKey("Cost");
+    Argument *ThresholdArg = R.getArgByKey("Threshold");
+    if (!CostArg || !ThresholdArg)
+      return Error::success();
+    auto CostVal = CostArg->getValAsInt<int>();
+    auto ThresholdVal = ThresholdArg->getValAsInt<int>();
+    if (!CostVal || !ThresholdVal)
+      return malformed();
+    Callee.updateCost({*CostVal, *ThresholdVal, R.Loc});
+    return Error::success();
+  }
+
+  void emit(RemarkSerializer &Serializer) override {
+    SmallVector<StringRef> SortedKeys(Callees.keys());
+    llvm::sort(SortedKeys);
+    for (StringRef K : SortedKeys) {
+      auto &V = Callees[K];
+      RemarkBuilder RB(Type::Analysis, "inline", "Summary", K);
+      if (V.Stats.empty())
+        continue;
+      RB.R.Loc = V.Loc;
+      RB << "Incoming Calls (";
+      SmallVector<StringRef> StatKeys(V.Stats.keys());
+      llvm::sort(StatKeys);
+      bool First = true;
+      for (StringRef StatK : StatKeys) {
+        if (!First)
+          RB << ", ";
+        RB << StatK << ": " << NV(StatK, V.Stats[StatK]);
+        First = false;
+      }
+      RB << ")";
+      if (V.LeastProfit && V.MostProfit != V.LeastProfit) {
+        RB << "\nLeast profitable (cost="
+           << NV("LeastProfitCost", V.LeastProfit->Cost, V.LeastProfit->Loc)
+           << ", threshold="
+           << NV("LeastProfitThreshold", V.LeastProfit->Threshold) << ")";
+      }
+      if (V.MostProfit) {
+        RB << "\nMost profitable (cost="
+           << NV("MostProfitCost", V.MostProfit->Cost, V.MostProfit->Loc)
+           << ", threshold="
+           << NV("MostProfitThreshold", V.MostProfit->Threshold) << ")";
+      }
+      Serializer.emit(RB.R);
+    }
+  }
+};
+
+static Error trySummary() {
+  auto MaybeBuf = getInputMemoryBuffer(InputFileName);
+  if (!MaybeBuf)
+    return MaybeBuf.takeError();
+  auto MaybeParser = createRemarkParser(InputFormat, (*MaybeBuf)->getBuffer());
+  if (!MaybeParser)
+    return MaybeParser.takeError();
+  auto &Parser = **MaybeParser;
+
+  Format SerializerFormat =
+      getSerializerFormat(OutputFileName, OutputFormat, Parser.ParserFormat);
+
+  auto MaybeOF = getOutputFileForRemarks(OutputFileName, SerializerFormat);
+  if (!MaybeOF)
+    return MaybeOF.takeError();
+  auto OF = std::move(*MaybeOF);
+
+  auto MaybeSerializer = createRemarkSerializer(SerializerFormat, OF->os());
+  if (!MaybeSerializer)
+    return MaybeSerializer.takeError();
+  auto &Serializer = **MaybeSerializer;
+
+  bool UseDefaultStrategies = !isAnyStrategyRequested();
+  SmallVector<std::unique_ptr<SummaryStrategy>> Strategies;
+  if (EnableInlineSummaryOpt || UseDefaultStrategies)
+    Strategies.push_back(std::make_unique<InlineCalleeSummary>());
+
+  auto MaybeRemark = Parser.next();
+  for (; MaybeRemark; MaybeRemark = Parser.next()) {
+    Remark &Remark = **MaybeRemark;
+    bool UsedRemark = false;
+    for (auto &Strategy : Strategies) {
+      if (!Strategy->filter(Remark))
+        continue;
+      UsedRemark = true;
+      if (auto E = Strategy->process(Remark)) {
+        if (IgnoreMalformedOpt) {
+          WithColor::warning() << "Ignored error: " << E << "\n";
+          consumeError(std::move(E));
+          continue;
+        }
+        return E;
+      }
+    }
+    if (KeepInputOpt == KeepMode::All ||
+        (KeepInputOpt == KeepMode::Used && UsedRemark))
+      Serializer.emit(Remark);
+  }
+
+  auto E = MaybeRemark.takeError();
+  if (!E.isA<EndOfFileError>())
+    return E;
+  consumeError(std::move(E));
+
+  for (auto &Strategy : Strategies)
+    Strategy->emit(Serializer);
+
+  OF->keep();
+  return Error::success();
+}
+
+static CommandRegistration SummaryReg(&SummarySub, trySummary);
+
+} // namespace summary

diff  --git a/llvm/tools/llvm-remarkutil/RemarkUtilHelpers.h b/llvm/tools/llvm-remarkutil/RemarkUtilHelpers.h
index 73867fe35f06c..39e7b423c4dc0 100644
--- a/llvm/tools/llvm-remarkutil/RemarkUtilHelpers.h
+++ b/llvm/tools/llvm-remarkutil/RemarkUtilHelpers.h
@@ -9,6 +9,7 @@
 // Helpers for remark utilites
 //
 //===----------------------------------------------------------------------===//
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Remarks/Remark.h"
 #include "llvm/Remarks/RemarkFormat.h"
@@ -19,6 +20,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Regex.h"
+#include "llvm/Support/StringSaver.h"
 #include "llvm/Support/ToolOutputFile.h"
 
 // Keep input + output help + names consistent across the various modes via a
@@ -205,5 +207,54 @@ struct Filters {
   bool filterRemark(const Remark &Remark);
 };
 
+/// Helper to construct Remarks using an API similar to DiagnosticInfo.
+/// Once this is more fully featured, consider implementing DiagnosticInfo using
+/// RemarkBuilder.
+class RemarkBuilder {
+  BumpPtrAllocator Alloc;
+  UniqueStringSaver Strs;
+
+public:
+  Remark R;
+  struct Argument {
+    std::string Key;
+    std::string Val;
+    std::optional<RemarkLocation> Loc;
+    Argument(StringRef Key, StringRef Val,
+             std::optional<RemarkLocation> Loc = std::nullopt)
+        : Key(Key), Val(Val), Loc(Loc) {}
+    Argument(StringRef Key, int Val,
+             std::optional<RemarkLocation> Loc = std::nullopt)
+        : Key(Key), Val(itostr(Val)), Loc(Loc) {}
+  };
+
+  RemarkBuilder(Type RemarkType, StringRef PassName, StringRef RemarkName,
+                StringRef FunctionName)
+      : Strs(Alloc) {
+    R.RemarkType = RemarkType;
+    R.PassName = Strs.save(PassName);
+    R.RemarkName = Strs.save(RemarkName);
+    R.FunctionName = Strs.save(FunctionName);
+  }
+
+  RemarkBuilder &operator<<(Argument &&Arg) {
+    auto &RArg = R.Args.emplace_back(Strs.save(Arg.Key), Strs.save(Arg.Val));
+    RArg.Loc = Arg.Loc;
+    return *this;
+  }
+
+  RemarkBuilder &operator<<(const char *Str) {
+    R.Args.emplace_back("String", Str);
+    return *this;
+  }
+
+  RemarkBuilder &operator<<(StringRef Str) {
+    R.Args.emplace_back("String", Strs.save(Str));
+    return *this;
+  }
+};
+
+using NV = RemarkBuilder::Argument;
+
 } // namespace remarks
 } // namespace llvm


        


More information about the llvm-commits mailing list