[Mlir-commits] [mlir] b1624fc - [MLIR][Remark] Add remark linking and remarkID (#180953)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Feb 12 11:52:52 PST 2026


Author: Guray Ozen
Date: 2026-02-12T20:52:47+01:00
New Revision: b1624fca5a4a70b0ae4a654359259370e6b4c4e5

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

LOG: [MLIR][Remark] Add remark linking and remarkID  (#180953)

Add RemarkId and relatedTo support to the MLIR Remarks infrastructure.

*Example*
```
remark1 = remark::analyzed(...)
remark::passed(loc, ... .relatedTo(remark1))
```

Added: 
    

Modified: 
    mlir/include/mlir/IR/Remarks.h
    mlir/lib/IR/Remarks.cpp
    mlir/test/Pass/remarks.mlir
    mlir/test/lib/Pass/TestRemarksPass.cpp
    mlir/unittests/IR/RemarkTest.cpp

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h
index eb0518e203d36..93f7993400e4f 100644
--- a/mlir/include/mlir/IR/Remarks.h
+++ b/mlir/include/mlir/IR/Remarks.h
@@ -23,8 +23,42 @@
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/IR/Value.h"
 
+#include <atomic>
+
 namespace mlir::remark {
 
+//===----------------------------------------------------------------------===//
+// RemarkId - Unique identifier for linking related remarks
+//===----------------------------------------------------------------------===//
+
+/// A unique identifier for a remark, used to link related remarks together.
+/// An invalid/empty ID has value 0.
+class RemarkId {
+public:
+  RemarkId() : id(0) {}
+  explicit RemarkId(uint64_t id) : id(id) {}
+
+  /// Check if this is a valid (non-zero) ID.
+  explicit operator bool() const { return id != 0; }
+
+  /// Get the raw ID value.
+  uint64_t getValue() const { return id; }
+
+  bool operator==(const RemarkId &other) const { return id == other.id; }
+  bool operator!=(const RemarkId &other) const { return id != other.id; }
+
+  /// Print the ID.
+  void print(llvm::raw_ostream &os) const { os << "remark-" << id; }
+
+private:
+  uint64_t id;
+};
+
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os, RemarkId id) {
+  id.print(os);
+  return os;
+}
+
 /// Define an the set of categories to accept. By default none are, the provided
 /// regex matches against the category names for each kind of remark.
 struct RemarkCategories {
@@ -53,6 +87,10 @@ enum class RemarkKind {
 
 using namespace llvm;
 
+namespace detail {
+class InFlightRemark; // forward declaration
+} // namespace detail
+
 /// Options to create a Remark
 struct RemarkOpts {
   StringRef remarkName;      // Identifiable name
@@ -60,22 +98,45 @@ struct RemarkOpts {
   StringRef subCategoryName; // Subcategory name
   StringRef functionName;    // Function name if available
 
+  /// Link to a related remark by explicit ID.
+  RemarkId relatedId;
+
   // Construct RemarkOpts from a remark name.
-  static constexpr RemarkOpts name(StringRef n) {
-    return RemarkOpts{n, {}, {}, {}};
+  static RemarkOpts name(StringRef n) {
+    RemarkOpts o;
+    o.remarkName = n;
+    return o;
   }
+
   /// Return a copy with the category set.
-  constexpr RemarkOpts category(StringRef v) const {
-    return {remarkName, v, subCategoryName, functionName};
+  RemarkOpts category(StringRef v) const {
+    auto copy = *this;
+    copy.categoryName = v;
+    return copy;
   }
   /// Return a copy with the subcategory set.
-  constexpr RemarkOpts subCategory(StringRef v) const {
-    return {remarkName, categoryName, v, functionName};
+  RemarkOpts subCategory(StringRef v) const {
+    auto copy = *this;
+    copy.subCategoryName = v;
+    return copy;
   }
   /// Return a copy with the function name set.
-  constexpr RemarkOpts function(StringRef v) const {
-    return {remarkName, categoryName, subCategoryName, v};
+  RemarkOpts function(StringRef v) const {
+    auto copy = *this;
+    copy.functionName = v;
+    return copy;
   }
+
+  /// Link this remark to a previously emitted remark by explicit ID.
+  RemarkOpts relatedTo(RemarkId id) const {
+    auto copy = *this;
+    copy.relatedId = id;
+    return copy;
+  }
+
+  /// Link this remark to a related remark that is still in flight.
+  /// Extracts the ID from the InFlightRemark.
+  inline RemarkOpts relatedTo(const detail::InFlightRemark &r) const;
 };
 
 } // namespace mlir::remark
@@ -97,6 +158,9 @@ class Remark {
       (llvm::Twine(categoryName) + ":" + subCategoryName)
           .toVector(fullCategoryName);
     }
+    // Explicit ID linking from opts.
+    if (opts.relatedId)
+      addRelatedRemark(opts.relatedId);
   }
 
   // Remark argument that is a key-value pair that can be printed as machine
@@ -178,6 +242,38 @@ class Remark {
 
   llvm::remarks::Type getRemarkType() const;
 
+  //===--------------------------------------------------------------------===//
+  // Remark Linking Support
+  //===--------------------------------------------------------------------===//
+
+  /// Get this remark's unique ID (0 if not assigned).
+  RemarkId getId() const { return id; }
+
+  /// Set this remark's unique ID. Also adds it as an Arg for serialization.
+  void setId(RemarkId newId) {
+    id = newId;
+    if (id)
+      args.emplace_back("RemarkId", llvm::utostr(id.getValue()));
+  }
+
+  /// Get the list of related remark IDs.
+  ArrayRef<RemarkId> getRelatedRemarkIds() const { return relatedRemarks; }
+
+  /// Add a reference to a related remark. Also adds it as an Arg for
+  /// serialization.
+  void addRelatedRemark(RemarkId relatedId) {
+    if (relatedId) {
+      relatedRemarks.push_back(relatedId);
+      args.emplace_back("RelatedTo", llvm::utostr(relatedId.getValue()));
+    }
+  }
+
+  /// Check if this remark has any related remarks.
+  bool hasRelatedRemarks() const { return !relatedRemarks.empty(); }
+
+  /// Get the remark kind.
+  RemarkKind getRemarkKind() const { return remarkKind; }
+
   StringRef getRemarkTypeString() const;
 
 protected:
@@ -207,6 +303,12 @@ class Remark {
   /// Args collected via the streaming interface.
   SmallVector<Arg, 4> args;
 
+  /// Unique ID for this remark (assigned by RemarkEngine).
+  RemarkId id;
+
+  /// IDs of related remarks (e.g., parent analysis that enabled this opt).
+  SmallVector<RemarkId> relatedRemarks;
+
 private:
   /// Convert the MLIR diagnostic severity to LLVM diagnostic severity.
   static llvm::DiagnosticSeverity
@@ -289,6 +391,9 @@ struct LazyTextBuild {
   std::function<std::string()> thunk;
 };
 
+/// A wrapper for linking remarks by query - searches the engine's registry
+/// at stream time and links to all matching remarks.
+
 /// InFlightRemark is a RAII class that holds a reference to a Remark
 /// instance and allows to build the remark using the << operator. The remark
 /// is emitted when the InFlightRemark instance is destroyed, which happens
@@ -321,6 +426,9 @@ class InFlightRemark {
 
   explicit operator bool() const { return remark != nullptr; }
 
+  /// Get this remark's unique ID (for linking from other remarks).
+  RemarkId getId() const { return remark ? remark->getId() : RemarkId(); }
+
   ~InFlightRemark();
 
   InFlightRemark(const InFlightRemark &) = delete;
@@ -372,6 +480,15 @@ class RemarkEmittingPolicyBase {
 
   virtual void reportRemark(const Remark &remark) = 0;
   virtual void finalize() = 0;
+
+  /// Find previously reported remarks matching the given criteria.
+  /// Default returns empty -- only policies that store remarks (like
+  /// PolicyFinal) override this to enable query-based linking.
+  virtual SmallVector<RemarkId>
+  findRemarks(const RemarkOpts &opts,
+              std::optional<RemarkKind> kind = std::nullopt) const {
+    return {};
+  }
 };
 
 //===----------------------------------------------------------------------===//
@@ -395,6 +512,8 @@ class RemarkEngine {
   std::unique_ptr<RemarkEmittingPolicyBase> remarkEmittingPolicy;
   /// When is enabled, engine also prints remarks as mlir::emitRemarks.
   bool printAsEmitRemarks = false;
+  /// Atomic counter for generating unique remark IDs.
+  std::atomic<uint64_t> nextRemarkId{1};
 
   /// Return true if missed optimization remarks are enabled, override
   /// to provide 
diff erent implementation.
@@ -423,8 +542,8 @@ class RemarkEngine {
   /// Emit a remark using the given maker function, which should return
   /// a Remark instance. The remark will be emitted using the main
   /// remark streamer.
-  template <typename RemarkT, typename... Args>
-  InFlightRemark makeRemark(Args &&...args);
+  template <typename RemarkT>
+  InFlightRemark makeRemark(Location loc, RemarkOpts opts);
 
   template <typename RemarkT>
   InFlightRemark emitIfEnabled(Location loc, RemarkOpts opts,
@@ -457,6 +576,22 @@ class RemarkEngine {
     return remarkEmittingPolicy.get();
   }
 
+  /// Generate a unique ID for a new remark.
+  RemarkId generateRemarkId() {
+    return RemarkId(nextRemarkId.fetch_add(1, std::memory_order_relaxed));
+  }
+
+  //===--------------------------------------------------------------------===//
+  // Remark Linking - query previously emitted remarks
+  //===--------------------------------------------------------------------===//
+
+  /// Find all remarks matching the given criteria. Delegates to the
+  /// emitting policy. Only works with policies that store remarks
+  /// (e.g. RemarkEmittingPolicyFinal); returns empty for PolicyAll.
+  SmallVector<RemarkId>
+  findRemarks(const RemarkOpts &opts,
+              std::optional<RemarkKind> kind = std::nullopt) const;
+
   /// Report a remark.
   void report(const Remark &&remark);
 
@@ -491,6 +626,12 @@ inline InFlightRemark withEngine(Fn fn, Location loc, Args &&...args) {
 
 } // namespace mlir::remark::detail
 
+// Deferred definition: needs InFlightRemark to be complete.
+inline mlir::remark::RemarkOpts
+mlir::remark::RemarkOpts::relatedTo(const detail::InFlightRemark &r) const {
+  return relatedTo(r.getId());
+}
+
 namespace mlir::remark {
 
 //===----------------------------------------------------------------------===//
@@ -509,7 +650,8 @@ class RemarkEmittingPolicyAll : public detail::RemarkEmittingPolicyBase {
   void finalize() override {}
 };
 
-/// Policy that emits final remarks.
+/// Policy that emits final remarks. Stores all remarks until finalize(),
+/// which enables query-based linking via findRemarks().
 class RemarkEmittingPolicyFinal : public detail::RemarkEmittingPolicyBase {
 private:
   /// user can intercept them for custom processing via a registered callback,
@@ -524,13 +666,9 @@ class RemarkEmittingPolicyFinal : public detail::RemarkEmittingPolicyBase {
     postponedRemarks.insert(remark);
   }
 
-  void finalize() override {
-    assert(reportImpl && "reportImpl is not set");
-    for (auto &remark : postponedRemarks) {
-      if (reportImpl)
-        reportImpl(remark);
-    }
-  }
+  /// Emits all stored remarks. Related remarks are printed as nested notes
+  /// under the remark that references them.
+  void finalize() override;
 };
 
 /// Create a Reason with llvm::formatv formatting.
@@ -559,6 +697,7 @@ inline detail::LazyTextBuild metric(StringRef key, V &&v) {
             return detail::Remark::Arg(key, std::move(vv)).val;
           }};
 }
+
 //===----------------------------------------------------------------------===//
 // Emitters
 //===----------------------------------------------------------------------===//
@@ -640,7 +779,8 @@ struct DenseMapInfo<mlir::remark::detail::Remark> {
     return llvm::hash_combine(
         remark.getLocation().getAsOpaquePointer(),
         llvm::hash_value(remark.getRemarkName()),
-        llvm::hash_value(remark.getCombinedCategoryName()));
+        llvm::hash_value(remark.getCombinedCategoryName()),
+        static_cast<unsigned>(remark.getRemarkKind()));
   }
 
   static bool isEqual(const mlir::remark::detail::Remark &lhs,
@@ -656,7 +796,8 @@ struct DenseMapInfo<mlir::remark::detail::Remark> {
     // For regular remarks, compare key identifying fields
     return lhs.getLocation() == rhs.getLocation() &&
            lhs.getRemarkName() == rhs.getRemarkName() &&
-           lhs.getCombinedCategoryName() == rhs.getCombinedCategoryName();
+           lhs.getCombinedCategoryName() == rhs.getCombinedCategoryName() &&
+           lhs.getRemarkKind() == rhs.getRemarkKind();
   }
 };
 } // namespace llvm

diff  --git a/mlir/lib/IR/Remarks.cpp b/mlir/lib/IR/Remarks.cpp
index 197cf421f9ba4..273918171746d 100644
--- a/mlir/lib/IR/Remarks.cpp
+++ b/mlir/lib/IR/Remarks.cpp
@@ -149,6 +149,7 @@ llvm::remarks::Remark Remark::generateRemark() const {
   r.PassName = getCombinedCategoryName();
   r.FunctionName = getFunction();
   r.Loc = locLambda();
+  // Add all args (includes RemarkId and RelatedTo if they were added).
   for (const Remark::Arg &arg : getArgs()) {
     r.Args.emplace_back();
     r.Args.back().Key = arg.key;
@@ -171,12 +172,13 @@ InFlightRemark::~InFlightRemark() {
 // Remark Engine
 //===----------------------------------------------------------------------===//
 
-template <typename RemarkT, typename... Args>
-InFlightRemark RemarkEngine::makeRemark(Args &&...args) {
+template <typename RemarkT>
+InFlightRemark RemarkEngine::makeRemark(Location loc, RemarkOpts opts) {
   static_assert(std::is_base_of_v<Remark, RemarkT>,
                 "RemarkT must derive from Remark");
-  return InFlightRemark(*this,
-                        std::make_unique<RemarkT>(std::forward<Args>(args)...));
+  auto remark = std::make_unique<RemarkT>(loc, opts);
+  remark->setId(generateRemarkId());
+  return InFlightRemark(*this, std::move(remark));
 }
 
 template <typename RemarkT>
@@ -345,4 +347,37 @@ llvm::LogicalResult mlir::remark::enableOptimizationRemarks(
 namespace mlir::remark {
 RemarkEmittingPolicyAll::RemarkEmittingPolicyAll() = default;
 RemarkEmittingPolicyFinal::RemarkEmittingPolicyFinal() = default;
+
+void RemarkEmittingPolicyFinal::finalize() {
+  assert(reportImpl && "reportImpl is not set");
+
+  // Build ID -> Remark* lookup for resolving related remark references.
+  llvm::DenseMap<uint64_t, const detail::Remark *> idMap;
+  llvm::DenseSet<uint64_t> childIds; // IDs referenced as children
+
+  for (const auto &remark : postponedRemarks) {
+    if (remark.getId())
+      idMap[remark.getId().getValue()] = &remark;
+    for (auto relId : remark.getRelatedRemarkIds())
+      childIds.insert(relId.getValue());
+  }
+
+  // Emit remarks with related remarks grouped after their parents.
+  // Parent remarks are emitted first, followed by their related (child)
+  // remarks. Child-only remarks are skipped at the top level to avoid
+  // duplication.
+  for (const auto &remark : postponedRemarks) {
+    if (remark.getId() && childIds.count(remark.getId().getValue()))
+      continue; // will be printed grouped under its parent
+
+    reportImpl(remark);
+
+    // Emit related remarks immediately after the parent.
+    for (auto relId : remark.getRelatedRemarkIds()) {
+      if (const auto *related = idMap.lookup(relId.getValue()))
+        reportImpl(*related);
+    }
+  }
+}
+
 } // namespace mlir::remark

diff  --git a/mlir/test/Pass/remarks.mlir b/mlir/test/Pass/remarks.mlir
index 8aa04e3c98d80..f99a98e32ed1f 100644
--- a/mlir/test/Pass/remarks.mlir
+++ b/mlir/test/Pass/remarks.mlir
@@ -4,25 +4,35 @@
 // RUN: mlir-opt %s --test-remark --remarks-filter-analyse="category-2-analysis" 2>&1 | FileCheck %s -check-prefix=CHECK-ANALYSIS
 // RUN: mlir-opt %s --test-remark --remarks-filter="category.*" 2>&1 | FileCheck %s -check-prefix=CHECK-ALL
 // RUN: mlir-opt %s --test-remark --remarks-filter="category-1.*" 2>&1 | FileCheck %s -check-prefix=CHECK-ALL1
+// RUN: mlir-opt %s --test-remark --remark-policy=final --remarks-filter-passed="category-link" --remarks-filter-analyse="category-link" 2>&1 | FileCheck %s -check-prefix=CHECK-LINK
 module @foo {
   "test.op"() : () -> ()
-  
 }
 
+// Each remark now includes a RemarkId=N arg and remark-N prefix, so use
+// {{.*}} to absorb them.
+// CHECK-PASSED: remark: [Passed]{{.*}}test-remark | Category:category-1-passed |{{.*}}Remark="This is a test passed remark"
+// CHECK-MISSED: remark: [Missed]{{.*}}test-remark | Category:a-category-1-missed |{{.*}}Remark="This is a test missed remark"
+// CHECK-FAILED: remark: [Failure]{{.*}}test-remark | Category:category-2-failed |{{.*}}Remark="This is a test failed remark"
+// CHECK-ANALYSIS: remark: [Analysis]{{.*}}test-remark | Category:category-2-analysis |{{.*}}Remark="This is a test analysis remark"
 
-// CHECK-PASSED: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-MISSED:remarks.mlir:8:3: remark: [Missed] test-remark | Category:a-category-1-missed | Reason="because we are testing the remark pipeline", Remark="This is a test missed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-FAILED: remarks.mlir:8:3: remark: [Failure] test-remark | Category:category-2-failed | Reason="because we are testing the remark pipeline", Remark="This is a test failed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-ANALYSIS: remarks.mlir:8:3: remark: [Analysis] test-remark | Category:category-2-analysis | Remark="This is a test analysis remark"
-
-
-// CHECK-ALL: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-ALL: remarks.mlir:8:3: remark: [Failure] test-remark | Category:category-2-failed | Reason="because we are testing the remark pipeline", Remark="This is a test failed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-ALL: remarks.mlir:8:3: remark: [Analysis] test-remark | Category:category-2-analysis | Remark="This is a test analysis remark"
-
-// CHECK-ALL1: remarks.mlir:8:3: remark: [Passed] test-remark | Category:category-1-passed | Reason="because we are testing the remark pipeline", Remark="This is a test passed remark", Suggestion="try using the remark pipeline feature"
-// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Missed]
-// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Failure]
-// CHECK-ALL1-NOT: remarks.mlir:8:3: remark: [Analysis]
+// CHECK-ALL: remark: [Passed]{{.*}}test-remark | Category:category-1-passed |
+// CHECK-ALL: remark: [Passed]{{.*}}test-remark | Category:category-1-passed |
+// CHECK-ALL: remark: [Failure]{{.*}}test-remark | Category:category-2-failed |
+// CHECK-ALL: remark: [Analysis]{{.*}}test-remark | Category:category-2-analysis |
+// CHECK-ALL: remark: [Passed]{{.*}}test-remark | Category:category-link |
+// CHECK-ALL: remark: [Analysis]{{.*}}test-remark | Category:category-link |
 
+// CHECK-ALL1: remark: [Passed]{{.*}}test-remark | Category:category-1-passed |
+// CHECK-ALL1-NOT: remark: [Missed]
+// CHECK-ALL1-NOT: remark: [Failure]
+// CHECK-ALL1-NOT: remark: [Analysis]
 
+// Test remark linking via query-based API with PolicyFinal: the passed remark
+// finds the analysis remark by searching postponedRemarks. Related (child)
+// remarks are grouped after their parent.
+// CHECK-LINK: remark: [Passed]{{.*}}Category:category-link |{{.*}}RelatedTo=
+// CHECK-LINK-SAME: Remark="Optimization enabled by analysis"
+// CHECK-LINK-SAME: RemarkId=
+// CHECK-LINK: remark: [Analysis]{{.*}}Category:category-link |{{.*}}Remark="Analysis that enables optimization"
+// CHECK-LINK-SAME: RemarkId=

diff  --git a/mlir/test/lib/Pass/TestRemarksPass.cpp b/mlir/test/lib/Pass/TestRemarksPass.cpp
index 5ca2d1a8550aa..b372d2cd6ad6b 100644
--- a/mlir/test/lib/Pass/TestRemarksPass.cpp
+++ b/mlir/test/lib/Pass/TestRemarksPass.cpp
@@ -66,6 +66,20 @@ class TestRemarkPass : public PassWrapper<TestRemarkPass, OperationPass<>> {
       mlir::remark::analysis(loc, remark::RemarkOpts::name("test-remark")
                                       .category("category-2-analysis"))
           << remark::add("This is a test analysis remark");
+
+      // Test remark linking: emit an analysis remark, then link a passed
+      // remark to it using relatedTo on RemarkOpts (no manual ID threading).
+      auto analysisRemark = mlir::remark::analysis(
+          loc,
+          remark::RemarkOpts::name("test-remark").category("category-link"));
+      analysisRemark << remark::add("Analysis that enables optimization");
+
+      // The passed remark finds the analysis via relatedTo on RemarkOpts:
+      mlir::remark::passed(loc, remark::RemarkOpts::name("test-remark")
+                                    .category("category-link")
+                                    .relatedTo(analysisRemark))
+          << remark::add("Optimization enabled by analysis");
+
       return WalkResult::advance();
     });
   }

diff  --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp
index df8e8c8cc066b..81bfcd1446455 100644
--- a/mlir/unittests/IR/RemarkTest.cpp
+++ b/mlir/unittests/IR/RemarkTest.cpp
@@ -230,25 +230,43 @@ TEST(Remark, TestOutputOptimizationRemarkDiagnostic) {
     int tripBad = 4;
     int threshold = 256;
 
-    remark::missed(loc, {"", categoryUnroll, "unroller2", ""})
+    remark::missed(loc, remark::RemarkOpts::name("")
+                            .category(categoryUnroll)
+                            .subCategory("unroller2"))
         << remark::reason("tripCount={0} < threshold={1}", tripBad, threshold);
 
-    remark::missed(loc, {"", categoryUnroll, "", ""})
+    remark::missed(loc, remark::RemarkOpts::name("").category(categoryUnroll))
         << remark::reason("tripCount={0} < threshold={1}", tripBad, threshold)
         << remark::suggest("increase unroll to {0}", target);
 
     // FAILURE: action attempted but failed
-    remark::failed(loc, {"", categoryUnroll, "", ""})
+    remark::failed(loc, remark::RemarkOpts::name("").category(categoryUnroll))
         << remark::reason("failed due to unsupported pattern");
   }
   // clang-format off
+  // Remarks now include RemarkId=N, so use substring checks.
   unsigned long expectedSize = 5;
   ASSERT_EQ(seenMsg.size(), expectedSize);
-  EXPECT_EQ(seenMsg[0], "[Passed] pass1 | Category:Vectorizer:myPass1 | Function=foo | Remark=\"vectorized loop\", tripCount=128");
-  EXPECT_EQ(seenMsg[1], "[Analysis] Analysis1 | Category:Register | Function=foo | Remark=\"Kernel uses 168 registers\"");
-  EXPECT_EQ(seenMsg[2], "[Missed]  | Category:Unroll:unroller2 | Reason=\"tripCount=4 < threshold=256\"");
-  EXPECT_EQ(seenMsg[3], "[Missed]  | Category:Unroll | Reason=\"tripCount=4 < threshold=256\", Suggestion=\"increase unroll to 128\"");
-  EXPECT_EQ(seenMsg[4], "[Failure]  | Category:Unroll | Reason=\"failed due to unsupported pattern\"");
+  EXPECT_THAT(seenMsg[0], HasSubstr("[Passed]"));
+  EXPECT_THAT(seenMsg[0], HasSubstr("pass1 | Category:Vectorizer:myPass1 | Function=foo |"));
+  EXPECT_THAT(seenMsg[0], HasSubstr("Remark=\"vectorized loop\""));
+  EXPECT_THAT(seenMsg[0], HasSubstr("tripCount=128"));
+
+  EXPECT_THAT(seenMsg[1], HasSubstr("[Analysis]"));
+  EXPECT_THAT(seenMsg[1], HasSubstr("Analysis1 | Category:Register | Function=foo |"));
+  EXPECT_THAT(seenMsg[1], HasSubstr("Remark=\"Kernel uses 168 registers\""));
+
+  EXPECT_THAT(seenMsg[2], HasSubstr("[Missed]"));
+  EXPECT_THAT(seenMsg[2], HasSubstr("Category:Unroll:unroller2"));
+  EXPECT_THAT(seenMsg[2], HasSubstr("Reason=\"tripCount=4 < threshold=256\""));
+
+  EXPECT_THAT(seenMsg[3], HasSubstr("[Missed]"));
+  EXPECT_THAT(seenMsg[3], HasSubstr("Category:Unroll |"));
+  EXPECT_THAT(seenMsg[3], HasSubstr("Suggestion=\"increase unroll to 128\""));
+
+  EXPECT_THAT(seenMsg[4], HasSubstr("[Failure]"));
+  EXPECT_THAT(seenMsg[4], HasSubstr("Category:Unroll |"));
+  EXPECT_THAT(seenMsg[4], HasSubstr("Reason=\"failed due to unsupported pattern\""));
   // clang-format on
 }
 
@@ -294,12 +312,20 @@ TEST(Remark, TestCustomOptimizationRemarkDiagnostic) {
     ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine";
 
     // Remark 1: pass, category LoopUnroll
-    remark::passed(loc, {"", categoryLoopunroll, myPassname1, ""}) << pass1Msg;
+    remark::passed(loc, remark::RemarkOpts::name("")
+                            .category(categoryLoopunroll)
+                            .subCategory(myPassname1))
+        << pass1Msg;
     // Remark 2: failure, category LoopUnroll
-    remark::failed(loc, {"", categoryLoopunroll, myPassname2, ""})
+    remark::failed(loc, remark::RemarkOpts::name("")
+                            .category(categoryLoopunroll)
+                            .subCategory(myPassname2))
         << remark::reason(pass2Msg);
     // Remark 3: pass, category Inline (should not be printed)
-    remark::passed(loc, {"", categoryInline, myPassname1, ""}) << pass3Msg;
+    remark::passed(loc, remark::RemarkOpts::name("")
+                            .category(categoryInline)
+                            .subCategory(myPassname1))
+        << pass3Msg;
   }
 
   llvm::errs().flush();
@@ -372,11 +398,14 @@ TEST(Remark, TestRemarkFinal) {
   llvm::errs().flush();
   std::string errOut = ::testing::internal::GetCapturedStderr();
 
-  // Containment checks for messages.
-  EXPECT_EQ(errOut.find(pass1Msg), std::string::npos); // dropped
-  EXPECT_EQ(errOut.find(pass2Msg), std::string::npos); // dropped
-  EXPECT_NE(errOut.find(pass3Msg), std::string::npos); // shown
-  EXPECT_NE(errOut.find(pass4Msg), std::string::npos); // shown
+  // PolicyFinal deduplicates by (location, name, category, kind).
+  // Remarks 1 (failed), 2 (missed), 3 (passed) have 
diff erent kinds, so all
+  // survive. Remark 4 (passed) has a 
diff erent location, so it also survives.
+  EXPECT_NE(errOut.find(pass1Msg), std::string::npos); // shown (failed)
+  EXPECT_NE(errOut.find(pass2Msg), std::string::npos); // shown (missed)
+  EXPECT_NE(errOut.find(pass3Msg), std::string::npos); // shown (passed)
+  EXPECT_NE(errOut.find(pass4Msg),
+            std::string::npos); // shown (passed, 
diff  loc)
 }
 
 TEST(Remark, TestArgWithAttribute) {
@@ -488,4 +517,62 @@ TEST(Remark, TestRemarkOwnsStringData) {
   EXPECT_NE(errOut.find(expectedMessage), std::string::npos)
       << "Expected message not found in output. Got: " << errOut;
 }
+
+// Test that remarks can be linked together using RemarkId.
+TEST(Remark, TestRemarkLinking) {
+  testing::internal::CaptureStderr();
+
+  std::string categoryOpt("Optimizer");
+
+  {
+    MLIRContext context;
+    Location loc = FileLineColLoc::get(&context, "test.cpp", 10, 5);
+
+    // Setup the remark engine
+    mlir::remark::RemarkCategories cats{/*all=*/std::nullopt,
+                                        /*passed=*/categoryOpt,
+                                        /*missed=*/std::nullopt,
+                                        /*analysis=*/categoryOpt,
+                                        /*failed=*/std::nullopt};
+
+    std::unique_ptr<remark::RemarkEmittingPolicyAll> policy =
+        std::make_unique<remark::RemarkEmittingPolicyAll>();
+    LogicalResult isEnabled = remark::enableOptimizationRemarks(
+        context, std::make_unique<MyCustomStreamer>(), std::move(policy), cats,
+        /*printAsEmitRemarks=*/true);
+    ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine";
+
+    // Emit an analysis remark and capture its ID.
+    auto analysisRemark = remark::analysis(
+        loc, remark::RemarkOpts::name("LoopAnalysis").category(categoryOpt));
+    analysisRemark << "analyzed loop with trip count 128";
+    remark::RemarkId analysisId = analysisRemark.getId();
+
+    // Verify we got a valid ID.
+    EXPECT_TRUE(static_cast<bool>(analysisId));
+    EXPECT_GT(analysisId.getValue(), 0u);
+
+    // Emit a passed remark that links to the analysis via RemarkOpts.
+    remark::passed(loc, remark::RemarkOpts::name("LoopOptimized")
+                            .category(categoryOpt)
+                            .relatedTo(analysisId))
+        << "vectorized loop";
+  }
+
+  llvm::errs().flush();
+  std::string errOut = ::testing::internal::GetCapturedStderr();
+
+  // Verify the analysis remark has an ID.
+  EXPECT_THAT(errOut, HasSubstr("RemarkId="));
+
+  // Verify the passed remark links to the analysis remark.
+  EXPECT_THAT(errOut, HasSubstr("RelatedTo="));
+
+  // Verify both remarks are present.
+  EXPECT_THAT(errOut, HasSubstr("LoopAnalysis"));
+  EXPECT_THAT(errOut, HasSubstr("LoopOptimized"));
+  EXPECT_THAT(errOut, HasSubstr("analyzed loop"));
+  EXPECT_THAT(errOut, HasSubstr("vectorized loop"));
+}
+
 } // namespace


        


More information about the Mlir-commits mailing list