[Mlir-commits] [mlir] [mlir] Add searchable remark history and query-based links (PR #237762)
Guray Ozen
llvmlistbot at llvm.org
Sun Nov 1 10:49:15 PST 2026
https://github.com/grypp created https://github.com/llvm/llvm-project/pull/237762
Remark producers currently need to retain explicit `RemarkId`s to link a later decision to earlier evidence. This adds opt-in, engine-owned history and exact-match queries so producers can find previously reported remarks and link all matches without handling their IDs.
```cpp
engine->enableHistory(); // Before reporting the evidence to be searched.
// After the analysis remarks have been reported:
auto query = remark::RemarkQuery()
.name("VectorSizeInference")
.category("Vectorizer")
.function("kernel")
.at(loc);
remark::missed(loc, opts) << remark::relatedTo(query);
```
Queries support name, base category, subcategory, function, kind, and complete MLIR location. Specified criteria are combined with AND; results follow report order. `relatedTo(query)` resolves at insertion time and uses the existing repeated `RelatedTo` arguments for serialization. The existing `findRemarks(RemarkOpts, kind)` overload is also implemented.
History stores owned identifying metadata independently of the emitting policy. It records subsequent reports until cleared, disabled, or the engine is destroyed; filtered and still-in-flight remarks are absent. History locking is released before callbacks, allowing streamers to query the report being delivered.
Use `ALL` when serialized output must contain every referenced event: `FINAL` can discard a target that remains searchable in history. Query matches express relatedness; location matching does not establish operation identity or causality. This change adds the C++ API and documentation.
Validation in an isolated Debug build with assertions:
- `MLIRIRTests`: 189 passed, including 12 new cases covering both emitting policies.
- `MLIRRemarkTests`: 11 passed.
- New coverage includes exact and empty-field matching, owned strings, history lifecycle, report order, immediate multi-result links, serialized link arguments, and reentrant callback queries.
>From 0f00bce6a7390bacf23585a31b7b288524eb4d7e Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Sun, 1 Nov 2026 19:43:51 +0100
Subject: [PATCH] [mlir] Add searchable remark history and query-based links
---
mlir/docs/Remarks.md | 85 +++++++++
mlir/include/mlir/IR/Remarks.h | 113 ++++++++++--
mlir/lib/IR/Remarks.cpp | 83 +++++++++
mlir/unittests/IR/RemarkTest.cpp | 298 +++++++++++++++++++++++++++++++
4 files changed, 562 insertions(+), 17 deletions(-)
diff --git a/mlir/docs/Remarks.md b/mlir/docs/Remarks.md
index e747f7bc8f5958..663eb1161e9ad8 100644
--- a/mlir/docs/Remarks.md
+++ b/mlir/docs/Remarks.md
@@ -251,6 +251,91 @@ if (engine->isRemarkEnabled(remark::RemarkKind::RemarkPassed, "Vectorizer"))
***
+## Searching and Linking Remarks
+
+Remarks can refer to other remarks by ID using `RemarkOpts::relatedTo(id)`.
+When the producer does not have the ID, it can search previously reported
+remarks and link to the matches.
+
+First, enable history on an already configured engine:
+
+```c++
+auto *engine = context.getRemarkEngine();
+engine->enableHistory();
+```
+
+History is disabled by default. Enabling it retains the IDs and identifying
+metadata of subsequent reports, without retaining their messages or arguments.
+It does not recover earlier reports or remarks excluded by the engine's
+category and kind filters. An in-flight remark becomes searchable when it is
+reported at the end of its lifetime.
+
+Use `RemarkQuery` to match a name, base category, subcategory, function, kind,
+or location. Specified fields are combined with AND and match exactly, without
+regular expressions. Unspecified fields match anything; an explicitly empty
+string matches empty metadata. Location matching compares the complete MLIR
+location, including wrappers, and does not identify a unique operation.
+
+```c++
+auto analysisOpts = remark::RemarkOpts::name("VectorSizeInference")
+ .category("Vectorizer")
+ .function("kernel");
+remark::analysis(loc, analysisOpts)
+ << remark::reason("Unable to infer vector sizes");
+
+auto query = remark::RemarkQuery()
+ .name("VectorSizeInference")
+ .category("Vectorizer")
+ .function("kernel")
+ .kind(remark::RemarkKind::RemarkAnalysis)
+ .at(loc);
+
+// Obtain the IDs explicitly, in report order.
+SmallVector<remark::RemarkId> matches = engine->findRemarks(query);
+
+// Or link all matching reports without handling their IDs.
+remark::missed(loc, remark::RemarkOpts::name("Vectorization")
+ .category("Vectorizer")
+ .function("kernel"))
+ << remark::reason("Cannot vectorize without vector sizes")
+ << remark::relatedTo(query);
+```
+
+The helper resolves the query when inserted with `<<`. Later reports do not
+change the links. No matches, including when history is disabled, adds no
+links. Links use the existing repeated `RelatedTo` arguments in serialized
+output. Consumers must preserve repeated keys. A query match indicates a
+relationship, not causality; the producer is responsible for choosing relevant
+criteria. There is no automatic selection of the latest match or inference of
+whether earlier evidence is still valid for changed IR.
+
+Queries own their strings, so they can be retained and reused. A query with a
+location must not outlive that location's `MLIRContext`. History access is
+synchronized, and streamer callbacks may query the history, including the
+report being delivered. This does not synchronize the emitting policy or
+streamer. Report order follows insertion into history and can differ from ID
+creation order, including when remarks are emitted concurrently.
+
+History is independent of the output policy: both `All` and `Final` record
+every report, and finalizing a policy does not clear history. Use `All` when
+the output must contain every referenced event. `Final` may deduplicate a
+referenced report or emit it in an earlier batch, so finding its ID in history
+does not guarantee that the current output batch contains it.
+
+History grows with the number of reports until explicitly cleared, disabled,
+or the engine is destroyed. Clear it between compilations when reusing an
+engine:
+
+```c++
+engine->clearHistory(); // Keep recording subsequent reports.
+engine->enableHistory(false); // Discard history and stop recording.
+```
+
+Neither operation resets the ID counter or changes existing links. Repeated
+calls to `enableHistory()` preserve already recorded history.
+
+***
+
## Enabling Remarks
### Option 1: LLVM Remark Streamer (YAML or Bitstream)
diff --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h
index 0ecc63717b5aee..6acbc722944ff4 100644
--- a/mlir/include/mlir/IR/Remarks.h
+++ b/mlir/include/mlir/IR/Remarks.h
@@ -24,6 +24,7 @@
#include "mlir/IR/Value.h"
#include <atomic>
+#include <mutex>
namespace mlir::remark {
@@ -89,6 +90,7 @@ using namespace llvm;
namespace detail {
class InFlightRemark; // forward declaration
+class RemarkEngine;
} // namespace detail
/// Options to create a Remark
@@ -139,6 +141,52 @@ struct RemarkOpts {
inline RemarkOpts relatedTo(const detail::InFlightRemark &r) const;
};
+/// Exact-match criteria for searching previously reported remarks. Unspecified
+/// fields match any value; a specified empty string matches an empty field.
+/// Strings are owned by the query. Locations belong to their MLIRContext and
+/// must not outlive it. A location match does not imply operation identity.
+class RemarkQuery {
+public:
+ RemarkQuery name(StringRef value) const {
+ auto copy = *this;
+ copy.remarkName = value.str();
+ return copy;
+ }
+ /// Match the base category, without the subcategory.
+ RemarkQuery category(StringRef value) const {
+ auto copy = *this;
+ copy.categoryName = value.str();
+ return copy;
+ }
+ RemarkQuery subCategory(StringRef value) const {
+ auto copy = *this;
+ copy.subCategoryName = value.str();
+ return copy;
+ }
+ RemarkQuery function(StringRef value) const {
+ auto copy = *this;
+ copy.functionName = value.str();
+ return copy;
+ }
+ RemarkQuery kind(RemarkKind value) const {
+ auto copy = *this;
+ copy.remarkKind = value;
+ return copy;
+ }
+ /// Match the complete location, including any wrapping locations.
+ RemarkQuery at(Location value) const {
+ auto copy = *this;
+ copy.location = value;
+ return copy;
+ }
+
+private:
+ friend class detail::RemarkEngine;
+ std::optional<std::string> remarkName, categoryName, subCategoryName,
+ functionName;
+ std::optional<RemarkKind> remarkKind;
+ std::optional<Location> location;
+};
} // namespace mlir::remark
namespace mlir::remark::detail {
@@ -146,6 +194,7 @@ namespace mlir::remark::detail {
// Remark Base Class
//===----------------------------------------------------------------------===//
class Remark {
+ friend class RemarkEngine;
public:
Remark(RemarkKind remarkKind, DiagnosticSeverity severity, Location loc,
@@ -391,8 +440,10 @@ 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.
+/// Resolves a query when inserted into an in-flight remark.
+struct RelatedRemarkQuery {
+ RemarkQuery query;
+};
/// InFlightRemark is a RAII class that holds a reference to a Remark
/// instance and allows to build the remark using the << operator. The remark
@@ -415,9 +466,13 @@ class InFlightRemark {
return *this;
}
- // Generic path, but *not* for Lazy
- template <typename T, typename = std::enable_if_t<
- !std::is_same_v<std::decay_t<T>, LazyTextBuild>>>
+ InFlightRemark &operator<<(const RelatedRemarkQuery &related);
+
+ // Generic path, excluding wrappers handled by dedicated overloads.
+ template <typename T,
+ typename = std::enable_if_t<
+ !std::is_same_v<std::decay_t<T>, LazyTextBuild> &&
+ !std::is_same_v<std::decay_t<T>, RelatedRemarkQuery>>>
InFlightRemark &operator<<(T &&arg) {
if (remark)
*remark << std::forward<T>(arg);
@@ -480,15 +535,6 @@ 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 {};
- }
};
//===----------------------------------------------------------------------===//
@@ -515,6 +561,13 @@ class RemarkEngine {
/// Atomic counter for generating unique remark IDs.
std::atomic<uint64_t> nextRemarkId{1};
+ /// Search history is independent of the emitting policy. Only metadata is
+ /// retained, and only after explicitly enabling history.
+ struct RemarkHistory;
+ std::unique_ptr<RemarkHistory> history;
+ std::atomic<bool> historyEnabled{false};
+ mutable std::mutex historyMutex;
+
/// Emit a remark using the given maker function, which should return
/// a Remark instance. The remark will be emitted using the main
/// remark streamer.
@@ -600,9 +653,28 @@ class RemarkEngine {
// 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.
+ /// Enable recording metadata for subsequent reports. Repeated enabling
+ /// preserves history. Disabling discards history. No past events are
+ /// recovered. History is retained until cleared, disabled, or the engine is
+ /// destroyed; callers reusing an engine across compilations should clear it
+ /// between runs.
+ void enableHistory(bool enable = true);
+
+ /// Discard recorded metadata without changing whether history is enabled.
+ /// Existing remark IDs and links are unaffected.
+ void clearHistory();
+
+ /// Find all recorded remarks matching every specified criterion, in report
+ /// order. Queries see a snapshot of history, including reports not yet
+ /// emitted by a deferred policy. In-flight and filtered-out remarks are not
+ /// recorded. Returns empty when history is disabled. Policy finalization does
+ /// not clear history. History access is synchronized; this does not make
+ /// emitting policies or streamers thread-safe.
+ SmallVector<RemarkId> findRemarks(const RemarkQuery &query) const;
+
+ /// Search using nonempty option fields as exact-match criteria. The related
+ /// ID in opts is ignored. Use RemarkQuery to match empty fields or a
+ /// location.
SmallVector<RemarkId>
findRemarks(const RemarkOpts &opts,
std::optional<RemarkKind> kind = std::nullopt) const;
@@ -648,6 +720,13 @@ mlir::remark::RemarkOpts::relatedTo(const detail::InFlightRemark &r) const {
}
namespace mlir::remark {
+/// Link to all remarks matching the query when this helper is inserted into an
+/// in-flight remark. History must have been enabled before reporting the target
+/// remarks. No matches (including disabled history) adds no links. Matching
+/// indicates a relationship, not causality or operation identity.
+inline detail::RelatedRemarkQuery relatedTo(RemarkQuery query) {
+ return {std::move(query)};
+}
//===----------------------------------------------------------------------===//
// Remark Emitting Policies
diff --git a/mlir/lib/IR/Remarks.cpp b/mlir/lib/IR/Remarks.cpp
index 14d095824f6712..e3f90ebf617b89 100644
--- a/mlir/lib/IR/Remarks.cpp
+++ b/mlir/lib/IR/Remarks.cpp
@@ -15,6 +15,8 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
+#include <vector>
+
using namespace mlir::remark::detail;
using namespace mlir::remark;
//------------------------------------------------------------------------------
@@ -164,6 +166,13 @@ llvm::remarks::Remark Remark::generateRemark() const {
// InFlightRemark
//===----------------------------------------------------------------------===//
+InFlightRemark &InFlightRemark::operator<<(const RelatedRemarkQuery &related) {
+ if (remark && owner)
+ for (RemarkId id : owner->findRemarks(related.query))
+ remark->addRelatedRemark(id);
+ return *this;
+}
+
InFlightRemark::~InFlightRemark() {
if (remark && owner)
owner->report(std::move(*remark));
@@ -174,6 +183,71 @@ InFlightRemark::~InFlightRemark() {
// Remark Engine
//===----------------------------------------------------------------------===//
+struct RemarkEngine::RemarkHistory {
+ struct Entry {
+ RemarkId id;
+ RemarkKind kind;
+ Location location;
+ std::string name, category, subCategory, function;
+ };
+ std::vector<Entry> entries;
+};
+
+void RemarkEngine::enableHistory(bool enable) {
+ std::lock_guard<std::mutex> lock(historyMutex);
+ if (enable) {
+ if (!history)
+ history = std::make_unique<RemarkHistory>();
+ } else {
+ history.reset();
+ }
+ historyEnabled.store(enable, std::memory_order_release);
+}
+
+void RemarkEngine::clearHistory() {
+ std::lock_guard<std::mutex> lock(historyMutex);
+ if (history)
+ history->entries.clear();
+}
+
+SmallVector<RemarkId>
+RemarkEngine::findRemarks(const RemarkQuery &query) const {
+ SmallVector<RemarkId> result;
+ std::lock_guard<std::mutex> lock(historyMutex);
+ if (!history)
+ return result;
+
+ auto matches = [](const auto &filter, const auto &value) {
+ return !filter || *filter == value;
+ };
+ for (const RemarkHistory::Entry &entry : history->entries)
+ if (matches(query.remarkName, entry.name) &&
+ matches(query.categoryName, entry.category) &&
+ matches(query.subCategoryName, entry.subCategory) &&
+ matches(query.functionName, entry.function) &&
+ matches(query.remarkKind, entry.kind) &&
+ matches(query.location, entry.location))
+ result.push_back(entry.id);
+ return result;
+}
+
+SmallVector<RemarkId>
+RemarkEngine::findRemarks(const RemarkOpts &opts,
+ std::optional<RemarkKind> kind) const {
+ RemarkQuery query;
+ if (!opts.remarkName.empty())
+ query = query.name(opts.remarkName);
+ if (!opts.categoryName.empty())
+ query = query.category(opts.categoryName);
+ if (!opts.subCategoryName.empty())
+ query = query.subCategory(opts.subCategoryName);
+ if (!opts.functionName.empty())
+ query = query.function(opts.functionName);
+ if (kind)
+ query = query.kind(*kind);
+ return findRemarks(query);
+}
+
template <typename RemarkT>
InFlightRemark RemarkEngine::makeRemark(Location loc, RemarkOpts opts) {
static_assert(std::is_base_of_v<Remark, RemarkT>,
@@ -264,6 +338,15 @@ void RemarkEngine::reportImpl(const Remark &remark) {
}
void RemarkEngine::report(const Remark &&remark) {
+ if (historyEnabled.load(std::memory_order_acquire)) {
+ std::lock_guard<std::mutex> lock(historyMutex);
+ if (history && remark.getId())
+ history->entries.push_back({remark.getId(), remark.getRemarkKind(),
+ remark.getLocation(), remark.remarkName,
+ remark.categoryName, remark.subCategoryName,
+ remark.functionName});
+ }
+ // Release the history lock before callbacks, which may themselves query it.
if (remarkEmittingPolicy)
remarkEmittingPolicy->reportRemark(remark);
}
diff --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp
index ece76e628739fa..84cd2657792ffd 100644
--- a/mlir/unittests/IR/RemarkTest.cpp
+++ b/mlir/unittests/IR/RemarkTest.cpp
@@ -13,6 +13,7 @@
#include "mlir/IR/Remarks.h"
#include "mlir/Remark/RemarkStreamer.h"
#include "mlir/Support/TypeID.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/LLVMRemarkStreamer.h"
@@ -23,6 +24,7 @@
#include "llvm/Support/YAMLParser.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <functional>
#include <optional>
#include <vector>
@@ -795,6 +797,302 @@ TEST(Remark, TestRemarkLinking) {
EXPECT_THAT(errOut, HasSubstr("vectorized loop"));
}
+class QueryCallbackStreamer final
+ : public remark::detail::MLIRRemarkStreamerBase {
+public:
+ explicit QueryCallbackStreamer(
+ std::function<void(const remark::detail::Remark &)> callback)
+ : callback(std::move(callback)) {}
+
+ void streamOptimizationRemark(const remark::detail::Remark &remark) override {
+ callback(remark);
+ }
+
+private:
+ std::function<void(const remark::detail::Remark &)> callback;
+};
+
+class RemarkHistoryTest : public TestWithParam<bool> {
+protected:
+ struct Record {
+ remark::RemarkId id;
+ SmallVector<remark::RemarkId> related;
+ std::vector<std::string> serializedLinks;
+ };
+
+ void SetUp() override {
+ remark::RemarkCategories categories;
+ categories.passed = "(Opt.*)?";
+ categories.analysis = "(Opt.*)?";
+ std::unique_ptr<remark::detail::RemarkEmittingPolicyBase> policy;
+ if (GetParam())
+ policy = std::make_unique<remark::RemarkEmittingPolicyFinal>();
+ else
+ policy = std::make_unique<remark::RemarkEmittingPolicyAll>();
+ auto streamer = std::make_unique<QueryCallbackStreamer>([this](
+ const auto &r) {
+ Record record{r.getId(), {}, {}};
+ llvm::append_range(record.related, r.getRelatedRemarkIds());
+ for (const auto &arg : r.generateRemark().Args)
+ if (arg.Key == "RelatedTo")
+ record.serializedLinks.push_back(arg.Val.str());
+ records.push_back(std::move(record));
+ if (queryInCallback)
+ callbackHistory.push_back(engine().findRemarks(remark::RemarkQuery{}));
+ });
+ ASSERT_TRUE(succeeded(remark::enableOptimizationRemarks(
+ context, std::move(streamer), std::move(policy), categories)));
+ ASSERT_TRUE(engine().isPassedOptRemarkEnabled("Opt"));
+ ASSERT_TRUE(engine().isAnalysisOptRemarkEnabled("Opt"));
+ ASSERT_TRUE(engine().isPassedOptRemarkEnabled(""));
+ ASSERT_TRUE(engine().isAnalysisOptRemarkEnabled(""));
+ }
+
+ remark::detail::RemarkEngine &engine() { return *context.getRemarkEngine(); }
+
+ remark::RemarkId emit(Location loc, remark::RemarkOpts opts,
+ bool analysis = false) {
+ auto r = analysis ? remark::analysis(loc, opts) : remark::passed(loc, opts);
+ return r.getId();
+ }
+
+ void flush() { engine().getRemarkEmittingPolicy()->finalize(); }
+
+ std::vector<Record> records;
+ bool queryInCallback = false;
+ std::vector<SmallVector<remark::RemarkId>> callbackHistory;
+ // Keep callback state alive while the context drains its remark engine.
+ MLIRContext context;
+};
+
+TEST_P(RemarkHistoryTest, LifecycleAndReportOrder) {
+ Location loc = UnknownLoc::get(&context);
+ auto opts = remark::RemarkOpts::name("Candidate").category("Opt");
+ auto any = remark::RemarkQuery{};
+ emit(loc, opts);
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+
+ engine().enableHistory();
+ remark::RemarkId firstCreated, secondCreated;
+ {
+ auto pending = remark::passed(loc, opts);
+ firstCreated = pending.getId();
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+ secondCreated = emit(loc, opts);
+ EXPECT_THAT(engine().findRemarks(any), ElementsAre(secondCreated));
+ }
+ EXPECT_LT(firstCreated.getValue(), secondCreated.getValue());
+ EXPECT_THAT(engine().findRemarks(any),
+ ElementsAre(secondCreated, firstCreated));
+
+ // FINAL may discard an earlier report, but history keeps both events.
+ flush();
+ engine().enableHistory();
+ EXPECT_THAT(engine().findRemarks(any),
+ ElementsAre(secondCreated, firstCreated));
+ engine().clearHistory();
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+ auto afterClear = emit(loc, opts);
+ EXPECT_THAT(engine().findRemarks(any), ElementsAre(afterClear));
+ EXPECT_GT(afterClear.getValue(), secondCreated.getValue());
+
+ engine().enableHistory(false);
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+ emit(loc, opts);
+ engine().clearHistory();
+ emit(loc, opts);
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+ engine().enableHistory();
+ EXPECT_THAT(engine().findRemarks(any), IsEmpty());
+ auto afterEnable = emit(loc, opts);
+ EXPECT_THAT(engine().findRemarks(any), ElementsAre(afterEnable));
+ EXPECT_GT(afterEnable.getValue(), afterClear.getValue());
+
+ EXPECT_FALSE(static_cast<bool>(
+ emit(loc, remark::RemarkOpts::name("Filtered").category("Unselected"))));
+ EXPECT_THAT(engine().findRemarks(any), ElementsAre(afterEnable));
+}
+
+TEST_P(RemarkHistoryTest, ExactFieldsAndConjunction) {
+ engine().enableHistory();
+ Location loc = FileLineColLoc::get(&context, "input.mlir", 7, 2);
+ Location otherLoc = NameLoc::get(StringAttr::get(&context, "wrapped"), loc);
+ auto opts = remark::RemarkOpts::name("Candidate")
+ .category("Opt")
+ .subCategory("Vector")
+ .function("kernel");
+ auto target = emit(loc, opts);
+ auto otherSub = emit(loc, opts.subCategory("Unroll"));
+ auto analysis = emit(loc, opts, /*analysis=*/true);
+ auto other = emit(otherLoc, remark::RemarkOpts::name("Other")
+ .category("OptOther")
+ .subCategory("Other")
+ .function("helper"));
+ auto empty = emit(UnknownLoc::get(&context), remark::RemarkOpts{});
+ auto any = remark::RemarkQuery{};
+
+ EXPECT_THAT(engine().findRemarks(any.name("Candidate")),
+ ElementsAre(target, otherSub, analysis));
+ EXPECT_THAT(engine().findRemarks(any.category("Opt")),
+ ElementsAre(target, otherSub, analysis));
+ EXPECT_THAT(engine().findRemarks(any.subCategory("Vector")),
+ ElementsAre(target, analysis));
+ EXPECT_THAT(engine().findRemarks(any.function("kernel")),
+ ElementsAre(target, otherSub, analysis));
+ EXPECT_THAT(engine().findRemarks(any.kind(remark::RemarkKind::RemarkPassed)),
+ ElementsAre(target, otherSub, other, empty));
+ EXPECT_THAT(engine().findRemarks(any.at(loc)),
+ ElementsAre(target, otherSub, analysis));
+ EXPECT_THAT(engine().findRemarks(any.at(otherLoc)), ElementsAre(other));
+
+ auto exact = any.name("Candidate")
+ .category("Opt")
+ .subCategory("Vector")
+ .function("kernel")
+ .kind(remark::RemarkKind::RemarkPassed)
+ .at(loc);
+ EXPECT_THAT(engine().findRemarks(exact), ElementsAre(target));
+ EXPECT_THAT(engine().findRemarks(exact.function("helper")), IsEmpty());
+ EXPECT_THAT(engine().findRemarks(any.category("Opt.*")), IsEmpty());
+ EXPECT_THAT(engine().findRemarks(any.category("Opt:Vector")), IsEmpty());
+ EXPECT_THAT(engine().findRemarks(any.name("")), ElementsAre(empty));
+ EXPECT_THAT(engine().findRemarks(any.category("")), ElementsAre(empty));
+ EXPECT_THAT(engine().findRemarks(any.subCategory("")), ElementsAre(empty));
+ EXPECT_THAT(engine().findRemarks(any.function("")), ElementsAre(empty));
+ EXPECT_THAT(engine().findRemarks(any.name("<unknown remark name>")),
+ IsEmpty());
+ EXPECT_THAT(engine().findRemarks(any.function("<unknown function>")),
+ IsEmpty());
+
+ // The existing options overload treats empty fields as wildcards.
+ EXPECT_THAT(engine().findRemarks(remark::RemarkOpts{}),
+ ElementsAre(target, otherSub, analysis, other, empty));
+ EXPECT_THAT(engine().findRemarks(opts, remark::RemarkKind::RemarkAnalysis),
+ ElementsAre(analysis));
+ EXPECT_THAT(engine().findRemarks(opts.relatedTo(other)),
+ ElementsAre(target, analysis));
+}
+
+TEST_P(RemarkHistoryTest, OwnsQueryAndHistoryStrings) {
+ engine().enableHistory();
+ Location loc = UnknownLoc::get(&context);
+ remark::RemarkQuery query;
+ remark::RemarkId target;
+ {
+ std::string name = "Candidate", category = "Opt", sub = "Vector";
+ std::string function = "kernel";
+ query =
+ query.name(name).category(category).subCategory(sub).function(function);
+ target = emit(loc, remark::RemarkOpts::name(name)
+ .category(category)
+ .subCategory(sub)
+ .function(function));
+ name.assign("Changed");
+ category.assign("Changed");
+ sub.assign("Changed");
+ function.assign("Changed");
+ }
+ EXPECT_THAT(engine().findRemarks(query), ElementsAre(target));
+ const auto original = query;
+ auto changed = original.name("Other");
+ EXPECT_THAT(engine().findRemarks(changed), IsEmpty());
+ EXPECT_THAT(engine().findRemarks(original), ElementsAre(target));
+}
+
+TEST_P(RemarkHistoryTest, QueryLinksSnapshotAndSerialization) {
+ engine().enableHistory();
+ Location loc = UnknownLoc::get(&context);
+ auto query = remark::RemarkQuery{}.kind(remark::RemarkKind::RemarkAnalysis);
+ auto first =
+ emit(loc, remark::RemarkOpts::name("First").category("Opt"), true);
+ auto second =
+ emit(loc, remark::RemarkOpts::name("Second").category("Opt"), true);
+ remark::RemarkId linked, later, pendingId, noMatches, copiedWrapper;
+ {
+ auto pending = remark::analysis(
+ loc, remark::RemarkOpts::name("Pending").category("Opt"));
+ pendingId = pending.getId();
+ {
+ auto r = remark::passed(
+ loc, remark::RemarkOpts::name("Linked").category("Opt"));
+ linked = r.getId();
+ r << remark::relatedTo(query);
+ // This report occurs before Linked is reported, but after its query.
+ later =
+ emit(loc, remark::RemarkOpts::name("Later").category("Opt"), true);
+ }
+ }
+ {
+ const auto wrapper = remark::relatedTo(query);
+ auto r = remark::passed(
+ loc, remark::RemarkOpts::name("CopiedWrapper").category("Opt"));
+ copiedWrapper = r.getId();
+ r << wrapper;
+ }
+ {
+ auto r = remark::passed(
+ loc, remark::RemarkOpts::name("NoMatches").category("Opt"));
+ noMatches = r.getId();
+ r << remark::relatedTo(query.name("Absent"));
+ }
+ flush();
+
+ for (const auto &record : records) {
+ if (record.id == linked) {
+ EXPECT_THAT(record.related, ElementsAre(first, second));
+ EXPECT_THAT(record.serializedLinks,
+ ElementsAre(std::to_string(first.getValue()),
+ std::to_string(second.getValue())));
+ } else if (record.id == copiedWrapper) {
+ EXPECT_THAT(record.related, ElementsAre(first, second, later, pendingId));
+ } else if (record.id == noMatches) {
+ EXPECT_THAT(record.related, IsEmpty());
+ EXPECT_THAT(record.serializedLinks, IsEmpty());
+ }
+ }
+ EXPECT_THAT(records, Contains(Field(&Record::id, linked)));
+ EXPECT_THAT(records, Contains(Field(&Record::id, copiedWrapper)));
+ EXPECT_THAT(records, Contains(Field(&Record::id, noMatches)));
+}
+
+TEST_P(RemarkHistoryTest, QueryLinksDoNotEnableHistory) {
+ Location loc = UnknownLoc::get(&context);
+ auto query = remark::RemarkQuery{};
+ emit(loc, remark::RemarkOpts::name("First").category("Opt"));
+ remark::RemarkId linked;
+ {
+ auto r =
+ remark::passed(loc, remark::RemarkOpts::name("Linked").category("Opt"));
+ linked = r.getId();
+ auto wrapper = remark::relatedTo(query);
+ r << wrapper;
+ }
+ // Both filtered and ownerless remarks accept the lazy wrapper harmlessly.
+ remark::passed(loc, remark::RemarkOpts::name("Filtered").category("Other"))
+ << remark::relatedTo(query);
+ remark::detail::InFlightRemark{} << remark::relatedTo(query);
+ EXPECT_THAT(engine().findRemarks(query), IsEmpty());
+ engine().enableHistory();
+ EXPECT_THAT(engine().findRemarks(query), IsEmpty());
+ flush();
+ EXPECT_THAT(records, Contains(AllOf(Field(&Record::id, linked),
+ Field(&Record::related, IsEmpty()))));
+}
+
+TEST_P(RemarkHistoryTest, StreamerCanQueryCurrentReport) {
+ engine().enableHistory();
+ queryInCallback = true;
+ auto id = emit(UnknownLoc::get(&context),
+ remark::RemarkOpts::name("Candidate").category("Opt"));
+ flush();
+ EXPECT_THAT(callbackHistory, ElementsAre(ElementsAre(id)));
+}
+
+INSTANTIATE_TEST_SUITE_P(EmittingPolicies, RemarkHistoryTest, Bool(),
+ [](const TestParamInfo<bool> &info) {
+ return info.param ? "Final" : "All";
+ });
+
TEST(Remark, TestNestedLocation) {
MLIRContext context;
Location fileLoc = FileLineColLoc::get(&context, "test.mlir", 42, 7);
More information about the Mlir-commits
mailing list