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

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Jun 1 17:47:13 PDT 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-core

Author: Ryan Thomas Lynch (emosy)

<details>
<summary>Changes</summary>

I discussed this on Discord, so I'm making my PR now.
I think the commit messages speak for themselves:

> `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.

> 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!

I'm not sure how to check that my new docs page will be rendered properly on the site, but I think it's mostly correct.

---
Full diff: https://github.com/llvm/llvm-project/pull/94130.diff


5 Files Affected:

- (modified) mlir/docs/PatternRewriter.md (+15-22) 
- (added) mlir/docs/RewritingMLIR.md (+127) 
- (modified) mlir/docs/Tutorials/transform/Ch1.md (+2-1) 
- (modified) mlir/include/mlir/IR/PatternMatch.h (+45) 
- (modified) mlir/lib/Transforms/Utils/DialectConversion.cpp (+2) 


``````````diff
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..186c037df7a1a
--- /dev/null
+++ b/mlir/docs/RewritingMLIR.md
@@ -0,0 +1,127 @@
+# 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
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);
 }
 
 //===----------------------------------------------------------------------===//

``````````

</details>


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


More information about the Mlir-commits mailing list