[Mlir-commits] [mlir] MLIR Rewriters: add new listener to emit match failures to user, enhance docs (PR #94130)

Ryan Thomas Lynch llvmlistbot at llvm.org
Sat Jun 1 19:36:15 PDT 2024


https://github.com/emosy updated https://github.com/llvm/llvm-project/pull/94130

>From cce65283b5f6af6a8070d665fba00c0d20cbc6db Mon Sep 17 00:00:00 2001
From: Ryan Thomas Lynch <rlynch34 at gatech.edu>
Date: Sun, 2 Jun 2024 00:22:57 +0000
Subject: [PATCH 1/2] MLIR Rewriter: New listener to emit match failures

`RewriterBase::MatchFailureEmittingListener` can be used to emit
notifications of match failure as an error, warning, or remark
as if it were emitted via `emitError`, `emitWarning`, or `emitRemark`.

This can be useful, for example, if you use dialect conversion to
convert ops and want to emit a message to the user when you fail to
convert an op other than just "op explicitly marked illegal".

Additionally, fix a defect in the dialect conversion driver which did
not forward the `notifyMatchFailure` call to the configured listener.
---
 mlir/include/mlir/IR/PatternMatch.h           | 45 +++++++++++++++++++
 .../Transforms/Utils/DialectConversion.cpp    |  2 +
 2 files changed, 47 insertions(+)

diff --git a/mlir/include/mlir/IR/PatternMatch.h b/mlir/include/mlir/IR/PatternMatch.h
index 2562301e499dd..7ab104d58d5b2 100644
--- a/mlir/include/mlir/IR/PatternMatch.h
+++ b/mlir/include/mlir/IR/PatternMatch.h
@@ -11,6 +11,7 @@
 
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/Diagnostics.h"
 #include "llvm/ADT/FunctionExtras.h"
 #include "llvm/Support/TypeName.h"
 #include <optional>
@@ -511,6 +512,50 @@ class RewriterBase : public OpBuilder {
     OpBuilder::Listener *listener;
   };
 
+  /// A listener that emits all notifyMatchFailure calls
+  /// with the given DiagnosticSeverity (default is Error).
+  /// This can be used so that any notifyMatchFailure calls will be emitted
+  /// as a message with the given severity (Remark will be used for Note)
+  struct MatchFailureEmittingListener : public RewriterBase::Listener {
+
+    MatchFailureEmittingListener(
+        DiagnosticSeverity severity = DiagnosticSeverity::Error) {
+      switch (severity) {
+      case DiagnosticSeverity::Note:
+        // there is no emitNote, so I will just make it a Remark instead
+        emitFn = [](Location loc) -> InFlightDiagnostic {
+          return emitRemark(loc);
+        };
+        break;
+      case DiagnosticSeverity::Warning:
+        emitFn = [](Location loc) -> InFlightDiagnostic {
+          return emitWarning(loc);
+        };
+        break;
+      case DiagnosticSeverity::Error:
+        emitFn = [](Location loc) -> InFlightDiagnostic {
+          return emitError(loc);
+        };
+        break;
+      case DiagnosticSeverity::Remark:
+        emitFn = [](Location loc) -> InFlightDiagnostic {
+          return emitRemark(loc);
+        };
+        break;
+      }
+    }
+
+    void notifyMatchFailure(
+        Location loc,
+        function_ref<void(Diagnostic &)> reasonCallback) override {
+      auto diag = emitFn(loc);
+      reasonCallback(*diag.getUnderlyingDiagnostic());
+    }
+
+  private:
+    function_ref<InFlightDiagnostic(Location loc)> emitFn;
+  };
+
   /// Move the blocks that belong to "region" before the given position in
   /// another region "parent". The two regions must be different. The caller
   /// is responsible for creating or updating the operation transferring flow
diff --git a/mlir/lib/Transforms/Utils/DialectConversion.cpp b/mlir/lib/Transforms/Utils/DialectConversion.cpp
index d407d60334c70..5fc9ff82b45e8 100644
--- a/mlir/lib/Transforms/Utils/DialectConversion.cpp
+++ b/mlir/lib/Transforms/Utils/DialectConversion.cpp
@@ -1620,6 +1620,8 @@ void ConversionPatternRewriterImpl::notifyMatchFailure(
     if (config.notifyCallback)
       config.notifyCallback(diag);
   });
+  if (config.listener)
+    config.listener->notifyMatchFailure(loc, reasonCallback);
 }
 
 //===----------------------------------------------------------------------===//

>From 9fb972f324bf0eb6e3c234bb7a24b5116239eff8 Mon Sep 17 00:00:00 2001
From: Ryan Thomas Lynch <rlynch34 at gatech.edu>
Date: Sun, 2 Jun 2024 00:34:49 +0000
Subject: [PATCH 2/2] [NFC] MLIR Docs: add new Rewriting MLIR page

I discovered that there was no documentation of the rewriter listeners,
so I wrote an overview of the rewriter infrastructure (outside of
pattern rewriting) in RewritingMLIR.md.

Hope this helps!
---
 mlir/docs/PatternRewriter.md         |  37 ++++----
 mlir/docs/RewritingMLIR.md           | 129 +++++++++++++++++++++++++++
 mlir/docs/Tutorials/transform/Ch1.md |   3 +-
 3 files changed, 146 insertions(+), 23 deletions(-)
 create mode 100644 mlir/docs/RewritingMLIR.md

diff --git a/mlir/docs/PatternRewriter.md b/mlir/docs/PatternRewriter.md
index 0ba76199874cc..b9b5fcdd70540 100644
--- a/mlir/docs/PatternRewriter.md
+++ b/mlir/docs/PatternRewriter.md
@@ -188,30 +188,27 @@ is properly initialized and prepared for insertion into a `RewritePatternSet`.
 
 ## Pattern Rewriter
 
-A `PatternRewriter` is a special class that allows for a pattern to communicate
-with the driver of pattern application. As noted above, *all* IR mutations,
+A `PatternRewriter` is a special class which extends from [`RewriterBase`](RewritingMLIR.md/#rewriters)
+that allows for a pattern to communicate with the driver of pattern
+application. `RewriterBase` provides a base level of functions
+As noted above, *all* IR mutations,
 including creations, are required to be performed via the `PatternRewriter`
 class. This is required because the underlying pattern driver may have state
 that would be invalidated when a mutation takes place. Examples of some of the
 more prevalent `PatternRewriter` API is shown below, please refer to the
-[class documentation](https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/IR/PatternMatch.h#L235)
+[class documentation](https://mlir.llvm.org/doxygen/classmlir_1_1PatternRewriter.html)
 for a more up-to-date listing of the available API:
 
-*   Erase an Operation : `eraseOp`
 
-This method erases an operation that either has no results, or whose results are
-all known to have no uses.
+* `RewriterBase` API and `OpBuilder` API
 
-*   Notify why a `match` failed : `notifyMatchFailure`
-
-This method allows for providing a diagnostic message within a `matchAndRewrite`
-as to why a pattern failed to match. How this message is displayed back to the
-user is determined by the specific pattern driver.
-
-*   Replace an Operation : `replaceOp`/`replaceOpWithNewOp`
-
-This method replaces an operation's results with a set of provided values, and
-erases the operation.
+The `PatternRewriter` inherits from the `RewriterBase` class, which inherits
+from the `OpBuilder` class. `PatternWriter` provides all of the same
+functionality present within a `RewriterBase` and an `OpBuilder`. This
+includes operation replacement, modification, erasure, and creation, as well
+as many useful attribute and type construction methods.
+See the explanation of the functions provided by [`RewriterBase`](RewritingMLIR.md#rewriter-functions)
+for functions used for erasure, replacement, and notifying match failures.
 
 *   Update an Operation in-place : `(start|cancel|finalize)OpModification`
 
@@ -223,11 +220,6 @@ within a pattern. An in-place update transaction is started with
 wrapper, `modifyOpInPlace`, is provided that wraps a `start` and `finalize`
 around a callback.
 
-*   OpBuilder API
-
-The `PatternRewriter` inherits from the `OpBuilder` class, and thus provides all
-of the same functionality present within an `OpBuilder`. This includes operation
-creation, as well as many useful attribute and type construction methods.
 
 ## Pattern Application
 
@@ -362,7 +354,8 @@ from being added to the worklist throughout the rewrite process:
     the worklist was initialized) are added to the worklist.
 
 Note: This driver listens for IR changes via the callbacks provided by
-`RewriterBase`. It is important that patterns announce all IR changes to the
+[`RewriterBase`](RewritingMLIR.md#rewriterbase. It is important that patterns
+announce all IR changes to the
 rewriter and do not bypass the rewriter API by modifying ops directly.
 
 Note: This driver is the one used by the [canonicalization](Canonicalization.md)
diff --git a/mlir/docs/RewritingMLIR.md b/mlir/docs/RewritingMLIR.md
new file mode 100644
index 0000000000000..aae3760b03c57
--- /dev/null
+++ b/mlir/docs/RewritingMLIR.md
@@ -0,0 +1,129 @@
+# Rewriting MLIR
+
+[TOC]
+
+This document details the design and API of the general IR rewriting 
+infrastructure present in MLIR. There are specific implemenations
+of this infrastructure, for example [Pattern Rewriting](PatternRewriter.md),
+which is widely used throughout MLIR for canonicalization, conversion, and
+general transformation.
+
+## Rewriters
+
+### `RewriterBase`
+
+All rewriters extend from [`RewriterBase`](https://mlir.llvm.org/doxygen/classmlir_1_1RewriterBase.html), which inherits from [`OpBuilder`](https://mlir.llvm.org/doxygen/classmlir_1_1OpBuilder.html).
+`RewriterBase` provides common API functions for any general types of rewriting
+IR. It additionally provides a [`Listener`](#listeners) mechanism to keep track of IR
+modifications.
+
+#### Rewriter Implementations
+
+Currently, there are only two implementations of `RewriterBase`:
+[`PatternRewriter`](https://mlir.llvm.org/doxygen/classmlir_1_1PatternRewriter.html) and [`IRRewriter`](https://mlir.llvm.org/doxygen/classmlir_1_1IRRewriter.html).
+The `RewriterBase` class is more designed towards being a base class of
+`PatternRewriter`, which is described in more detail in [Pattern Rewriting](PatternRewriter.md/#pattern-rewriter).
+However, the `IRRewriter` class is provided as a thin wrapper to use when
+`PatternRewriter` is not available for use.
+
+#### Rewriter Functions
+
+Here is a list of commonly used functions that are provided to all 
+rewriter implementations. For a complete list, see the
+[list of public member functions in the class documentation](https://mlir.llvm.org/doxygen/classmlir_1_1RewriterBase.html).
+
+*   Erase an Operation : `eraseOp`
+
+This method erases an operation that either has no results, or whose results are
+all known to have no uses.
+
+*   Replace an Operation : `replaceOp`/`replaceOpWithNewOp`
+
+This method replaces an operation's results with a set of provided values, and
+erases the operation.
+
+*   Replace all uses of a Value(s) or Operation : `replaceAllUsesWith`/`replaceAllOpUsesWith`
+
+This method replaces a value, values, or an operation's results with a set of
+provided values, but does NOT erase the operation.
+
+*   Notify why a `match` failed : `notifyMatchFailure`
+
+This method allows for providing a diagnostic message within a `matchAndRewrite`
+as to why a pattern failed to match. How this message is displayed back to the
+user is determined by the specific pattern driver.
+
+### Listeners
+
+The `RewriteBase::Listener` struct extends the [`OpBuilder::Listener`](https://mlir.llvm.org/doxygen/structmlir_1_1OpBuilder_1_1Listener.html)
+class to add additional functions to handle the modification and erasure of IR.
+These functions are called to notify the listener of IR changes.
+
+#### Listener Functions
+
+Here is a list of commonly used functions that are provided to all 
+listener implementations. For a complete list, see the
+[list of public member functions in the struct documentation](https://mlir.llvm.org/doxygen/structmlir_1_1RewriterBase_1_1Listener.html).
+
+Since the listener functions are specialized to a specific usage, one
+insertion, modification, or deletion _may_ lead to multipler listener functions
+being called.
+These listener notification functions map closely (almost 1:1) to the rewriter
+replacement functions.
+
+*   Inserting/Moving Ops/Blocks: `notifyOperationInserted`/`notifyBlockInserted`
+
+These are provided from the parent `OpBuilder::Listener` struct, and provide
+the op/block and the previous location of the op/block (if present).
+
+*   Erasing Ops/Blocks: `notifyOperationErased`/`notifyBlockErased`
+
+When this is called, the op/block already has zero uses. This is not called
+when an op is unlinked.
+
+*   Replacing Ops: `notifyOperationReplaced`
+
+When an op is replaced with a single op or a range of values, one of the
+overloaded versions of this function is called.
+
+*   Notification of pattern application: `notifyPatternBegin`/`notifyPatternEnd`
+
+These specify the pattern and either the root op to apply on or the 
+success/failure status of the pattern application.
+
+#### Listener Implementations
+
+Currently, there are two implementations of `RewriterBase::Listener`:
+[`RewriterBase::ForwardingListener`](https://mlir.llvm.org/doxygen/structmlir_1_1RewriterBase_1_1ForwardingListener.html)
+and [`RewriterBase::MatchFailureEmittingListener`](https://mlir.llvm.org/doxygen/structmlir_1_1RewriterBase_1_1MatchFailureEmittingListener.html).
+
+`ForwardingListener` can be extended to forward one notification to multiple listeners.
+For an example, see [`NewOpsListener`](https://github.com/llvm/llvm-project/blob/0310f7f2d0c56a5697710251cec9803cbf7b4d56/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp#L281-L287) in `LinalgTransformOps.cpp` or [`ExpensiveChecks`](https://github.com/llvm/llvm-project/blob/0310f7f2d0c56a5697710251cec9803cbf7b4d56/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp#L55-L57) in `GreedyPatternRewriteDriver.cpp`.
+
+`MatchFailureEmittingListener` can be used to emit the `Diagnostic` from
+`notifyMatchFailure` calls with a given [`DiagnosticSeverity`](https://llvm.org/doxygen/namespacellvm.html#abfcab32516704f11d146c757f402ad5c).
+By default, the severity is `Error`, and the location associated with the
+diagnostic will be the location provided to `notifyMatchFailure`.
+
+#### Using Listeners
+
+`OpBuilder` (from which `RewriterBase` extends) provides the `setListener`
+function to set the listener. However, some frameworks do not directly
+expose the rewriter before starting rewriting, so they usually provide a
+config struct which can be used to set the listener.
+
+For example, here is an example from `TransformOps.cpp` which uses the dialect
+conversion framework:
+
+```cpp
+// Attach a tracking listener if handles should be preserved. We configure the
+// listener to allow op replacements with different names, as conversion
+// patterns typically replace ops with replacement ops that have a different
+// name.
+TrackingListenerConfig trackingConfig;
+trackingConfig.requireMatchingReplacementOpName = false;
+ErrorCheckingTrackingListener trackingListener(state, *this, trackingConfig);
+ConversionConfig conversionConfig;
+if (getPreserveHandles())
+  conversionConfig.listener = &trackingListener;
+```
\ No newline at end of file
diff --git a/mlir/docs/Tutorials/transform/Ch1.md b/mlir/docs/Tutorials/transform/Ch1.md
index b0fdf085854c7..95601b909d59a 100644
--- a/mlir/docs/Tutorials/transform/Ch1.md
+++ b/mlir/docs/Tutorials/transform/Ch1.md
@@ -405,4 +405,5 @@ multi-result op is replaced with values that are defined by multiple ops, or if
 an op is replaced with an op of a different type, an error is produced. This is
 because it is unclear whether the direct replacements actually represent the
 computation of the original op. There are ways to customize this behavior. More
-details can be found at the documentation of `transform::TrackingListener`.
+details can be found at the documentation of [`transform::TrackingListener`](https://mlir.llvm.org/doxygen/classmlir_1_1transform_1_1TrackingListener.html).
+For an overview of how listeners work in general, see [Rewriting MLIR](../../RewritingMLIR.md).
\ No newline at end of file



More information about the Mlir-commits mailing list