[Mlir-commits] [mlir] fa4b314 - [mlir][DialectConversion] Update the documentation for dialect conversion

River Riddle llvmlistbot at llvm.org
Thu Aug 13 12:06:10 PDT 2020


Author: River Riddle
Date: 2020-08-13T12:05:54-07:00
New Revision: fa4b3147e3368f63e27b86ef66cd35f484ceb6d6

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

LOG: [mlir][DialectConversion] Update the documentation for dialect conversion

This revision updates the documentation for dialect conversion, as many concepts have changed/evolved over time.

Differential Revision: https://reviews.llvm.org/D85167

Added: 
    

Modified: 
    mlir/docs/DialectConversion.md

Removed: 
    


################################################################################
diff  --git a/mlir/docs/DialectConversion.md b/mlir/docs/DialectConversion.md
index c7174147b72e..8a308dd67882 100644
--- a/mlir/docs/DialectConversion.md
+++ b/mlir/docs/DialectConversion.md
@@ -7,7 +7,7 @@ of pattern-based operation rewriting patterns.
 
 [TOC]
 
-To utilize the framework, a few things must be provided:
+The dialect conversion framework consists of the following components:
 
 *   A [Conversion Target](#conversion-target)
 *   A set of [Rewrite Patterns](#rewrite-pattern-specification)
@@ -15,41 +15,44 @@ To utilize the framework, a few things must be provided:
 
 ## Modes of Conversion
 
-When applying a conversion to a set of operations, there are several conversion
-modes that can be selected from:
+When applying a conversion to a set of operations, there are several 
diff erent
+conversion modes that may be selected from:
 
 *   Partial Conversion
 
     -   A partial conversion will legalize as many operations to the target as
         possible, but will allow pre-existing operations that were not
-        explicitly marked as `illegal` to remain unconverted. This allows for
-        partially lowering parts of the module in the presence of unknown
+        explicitly marked as "illegal" to remain unconverted. This allows for
+        partially lowering parts of the input in the presence of unknown
         operations.
     -   A partial conversion can be applied via `applyPartialConversion`.
 
 *   Full Conversion
 
-    -   A full conversion is only successful if all operations are properly
-        legalized to the given conversion target. This ensures that only known
-        operations will exist after the conversion process.
+    -   A full conversion legalizes all input operations, and is only successful
+        if all operations are properly legalized to the given conversion target.
+        This ensures that only known operations will exist after the conversion
+        process.
     -   A full conversion can be applied via `applyFullConversion`.
 
 *   Analysis Conversion
 
     -   An analysis conversion will analyze which operations are legalizable to
-        the given conversion target if a conversion were to be applied. Note
-        that no rewrites, or transformations, are actually applied to the input
+        the given conversion target if a conversion were to be applied. This is
+        done by performing a 'partial' conversion and recording which operations
+        would have been successfully converted if successful. Note that no
+        rewrites, or transformations, are actually applied to the input
         operations.
     -   An analysis conversion can be applied via `applyAnalysisConversion`.
 
 ## Conversion Target
 
-The conversion target is the formal definition of what is considered to be legal
+The conversion target is a formal definition of what is considered to be legal
 during the conversion process. The final operations generated by the conversion
 framework must be marked as legal on the `ConversionTarget` for the rewrite to
-be a success. Existing operations need not always be legal, though; see the
-
diff erent conversion modes for why. Operations and dialects may be marked with
-any of the provided legality actions below:
+be a success. Depending on the conversion mode, existing operations need not
+always be legal. Operations and dialects may be marked with any of the provided
+legality actions below:
 
 *   Legal
 
@@ -68,7 +71,7 @@ any of the provided legality actions below:
 *   Illegal
 
     -   This action signals that no instance of a given operation is legal.
-        Operations marked as `illegal` must always be converted for the
+        Operations marked as "illegal" must always be converted for the
         conversion to be successful. This action also allows for selectively
         marking specific operations as illegal in an otherwise legal dialect.
 
@@ -123,13 +126,12 @@ struct MyTarget : public ConversionTarget {
 
 ### Recursive Legality
 
-In some cases, it may be desirable to mark entire regions of operations as
-legal. This provides an additional granularity of context to the concept of
-"legal". The `ConversionTarget` supports marking operations, that were
-previously added as `Legal` or `Dynamic`, as `recursively` legal. Recursive
-legality means that if an operation instance is legal, either statically or
-dynamically, all of the operations nested within are also considered legal. An
-operation can be marked via `markOpRecursivelyLegal<>`:
+In some cases, it may be desirable to mark entire regions as legal. This
+provides an additional granularity of context to the concept of "legal". If an
+operation is marked recursively legal, either statically or dynamically, then
+all of the operations nested within are also considered legal even if they would
+otherwise be considered "illegal". An operation can be marked via
+`markOpRecursivelyLegal<>`:
 
 ```c++
 ConversionTarget &target = ...;
@@ -149,14 +151,12 @@ target.markOpRecursivelyLegal<MyOp>([](MyOp op) { ... });
 ## Rewrite Pattern Specification
 
 After the conversion target has been defined, a set of legalization patterns
-must be provided to transform illegal operations into legal ones. The patterns
-supplied here, that do not [require type changes](#conversion-patterns), are the
-same as those described in the
-[quickstart rewrites guide](Tutorials/QuickstartRewrites.md#adding-patterns), but have a
-few additional [restrictions](#restrictions). The patterns provided do not need
-to generate operations that are directly legal on the target. The framework will
-automatically build a graph of conversions to convert non-legal operations into
-a set of legal ones.
+must be provided to transform illegal operations into legal ones. The structure
+of the patterns supplied here is the same as those described in the
+[quickstart rewrites guide](Tutorials/QuickstartRewrites.md#adding-patterns).
+The patterns provided do not need to generate operations that are directly legal
+on the target. The framework will automatically build a graph of conversions to
+convert non-legal operations into a set of legal ones.
 
 As an example, say you define a target that supports one operation: `foo.add`.
 When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` ->
@@ -165,38 +165,139 @@ When providing the following patterns: [`bar.add` -> `baz.add`, `baz.add` ->
 means that you don’t have to define a direct legalization pattern for `bar.add`
 -> `foo.add`.
 
-### Restrictions
+### Conversion Patterns
+
+Along with the general `RewritePattern` classes, the conversion framework
+provides a special type of rewrite pattern that can be used when a pattern
+relies on interacting with constructs specific to the conversion process, the
+`ConversionPattern`. For example, the conversion process does not necessarily
+update operations in-place and instead creates a mapping of events such as
+replacements and erasures, and only applies them when the entire conversion
+process is successful. Certain classes of patterns rely on using the
+updated/remapped operands of an operation, such as when the types of results
+defined by an operation have changed. The general Rewrite Patterns can no longer
+be used in these situations, as the types of the operands of the operation being
+matched will not correspond with those expected by the user. This pattern
+provides, as an additional argument to the `matchAndRewrite` and `rewrite`
+methods, the list of operands that the operation should use after conversion. If
+an operand was the result of a non-converted operation, for example if it was
+already legal, the original operand is used. This means that the operands
+provided always have a 1-1 non-null correspondence with the operands on the
+operation. The original operands of the operation are still intact and may be
+inspected as normal. These patterns also utilize a special `PatternRewriter`,
+`ConversionPatternRewriter`, that provides special hooks for use with the
+conversion infrastructure.
 
-The framework processes operations in topological order, trying to legalize them
-individually. As such, patterns used in the conversion framework have a few
-additional restrictions:
+```c++
+struct MyConversionPattern : public ConversionPattern {
+  /// The `matchAndRewrite` hooks on ConversionPatterns take an additional
+  /// `operands` parameter, containing the remapped operands of the original
+  /// operation.
+  virtual LogicalResult
+  matchAndRewrite(Operation *op, ArrayRef<Value> operands,
+                  ConversionPatternRewriter &rewriter) const;
+};
+```
 
-1.  If a pattern matches, it must erase or replace the op it matched on.
-    Operations can *not* be updated in place.
-2.  Match criteria should not be based on the IR outside of the op itself. The
-    preceding ops will already have been processed by the framework (although it
-    may not update uses), and the subsequent IR will not yet be processed. This
-    can create confusion if a pattern attempts to match against a sequence of
-    ops (e.g. rewrite A + B -> C). That sort of rewrite should be performed in a
-    separate pass.
+#### Type Safety
+
+The types of the remapped operands provided to a conversion pattern must be of a
+type expected by the pattern. The expected types of a pattern are determined by
+a provided [TypeConverter](#type-converter). If no type converter is provided,
+the types of the remapped operands are expected to match the types of the
+original operands. If a type converter is provided, the types of the remapped
+operands are expected to be legal as determined by the converter. If the
+remapped operand types are not of an expected type, and a materialization to the
+expected type could not be performed, the pattern fails application before the
+`matchAndRewrite` hook is invoked. This ensures that patterns do not have to
+explicitly ensure type safety, or sanitize the types of the incoming remapped
+operands. More information on type conversion is detailed in the
+[dedicated section](#type-conversion) below.
 
 ## Type Conversion
 
 It is sometimes necessary as part of a conversion to convert the set types of
 being operated on. In these cases, a `TypeConverter` object may be defined that
-details how types should be converted. The `TypeConverter` is used by patterns
-and by the general conversion infrastructure to convert the signatures of blocks
-and regions.
+details how types should be converted when interfacing with a pattern. A
+`TypeConverter` may be used to convert the signatures of block arguments and
+regions, to define the expected inputs types of the pattern, and to reconcile
+type 
diff erences in general.
 
 ### Type Converter
 
-As stated above, the `TypeConverter` contains several hooks for detailing how to
-convert types. Several of these hooks are detailed below:
+The `TypeConverter` contains several hooks for detailing how to convert types,
+and how to materialize conversions between types in various situations. The two
+main aspects of the `TypeConverter` are conversion and materialization.
+
+A `conversion` describes how a given illegal source `Type` should be converted
+to N target types. If the source type is already "legal", it should convert to
+itself. Type conversions are specified via the `addConversion` method described
+below.
+
+A `materialization` describes how a set of values should be converted to a
+single value of a desired type. An important distinction with a `conversion` is
+that a `materialization` can produce IR, whereas a `conversion` cannot. These
+materializations are used by the conversion framework to ensure type safety
+during the conversion process. There are several types of materializations
+depending on the situation.
+
+*   Argument Materialization
+
+    -   An argument materialization is used when converting the type of a block
+        argument during a [signature conversion](#region-signature-conversion).
+
+*   Source Materialization
+
+    -   A source materialization converts from a value with a "legal" target
+        type, back to a specific source type. This is used when an operation is
+        "legal" during the conversion process, but contains a use of an illegal
+        type. This may happen during a conversion where some operations are
+        converted to those with 
diff erent resultant types, but still retain
+        users of the original type system.
+    -   This materialization is used in the following situations:
+        *   When a block argument has been converted to a 
diff erent type, but
+            the original argument still has users that will remain live after
+            the conversion process has finished.
+        *   When the result type of an operation has been converted to a
+            
diff erent type, but the original result still has users that will
+            remain live after the conversion process is finished.
+
+*   Target Materialization
+
+    -   A target materialization converts from a value with an "illegal" source
+        type, to a value of a "legal" type. This is used when a pattern expects
+        the remapped operands to be of a certain set of types, but the original
+        input operands have not been converted. This may happen during a
+        conversion where some operations are converted to those with 
diff erent
+        resultant types, but still retain uses of the original type system.
+    -   This materialization is used in the following situations:
+        *   When the remapped operands of a
+            [conversion pattern](#conversion-patterns) are not legal for the
+            type conversion provided by the pattern.
+
+If a converted value is used by an operation that isn't converted, it needs a
+conversion back to the `source` type, hence source materialization; if an
+unconverted value is used by an operation that is being converted, it needs
+conversion to the `target` type, hence target materialization.
+
+As noted above, the conversion process guarantees that the type contract of the
+IR is preserved during the conversion. This means that the types of value uses
+will not implicitly change during the conversion process. When the type of a
+value definition, either block argument or operation result, is being changed,
+the users of that definition must also be updated during the conversion process.
+If they aren't, a type conversion must be materialized to ensure that a value of
+the expected type is still present within the IR. If a target materialization is
+required, but cannot be performed, the pattern application fails. If a source
+materialization is required, but cannot be performed, the entire conversion
+process fails.
+
+Several of the available hooks are detailed below:
 
 ```c++
 class TypeConverter {
  public:
-  /// Register a conversion function. A conversion function must be convertible
+  /// Register a conversion function. A conversion function defines how a given
+  /// source type should be converted. A conversion function must be convertible
   /// to any of the following forms(where `T` is a class derived from `Type`:
   ///   * Optional<Type>(T)
   ///     - This form represents a 1-1 type conversion. It should return nullptr
@@ -210,56 +311,53 @@ class TypeConverter {
   ///       existing value are expected to be removed during conversion. If
   ///       `llvm::None` is returned, the converter is allowed to try another
   ///       conversion function to perform the conversion.
-  ///
-  /// When attempting to convert a type, e.g. via `convertType`, the
-  /// `TypeConverter` will invoke each of the converters starting with the one
-  /// most recently registered.
-  template <typename ConversionFnT>
-  void addConversion(ConversionFnT &&callback);
-
-  /// Register a materialization function, which must be convertibe to the
-  /// following form
-  ///   `Optional<Value>(PatternRewriter &, T, ValueRange, Location)`,
-  /// where `T` is any subclass of `Type`. This function is responsible for
-  /// creating an operation, using the PatternRewriter and Location provided,
-  /// that "casts" a range of values into a single value of the given type `T`.
-  /// It must return a Value of the converted type on success, an `llvm::None`
-  /// if it failed but other materialization can be attempted, and `nullptr` on
-  /// unrecoverable failure. It will only be called for (sub)types of `T`.
-  /// Materialization functions must be provided when a type conversion
-  /// results in more than one type, or if a type conversion may persist after
-  /// the conversion has finished.
-  template <typename FnT>
-  void addMaterialization(FnT &&callback);
-};
-```
-
-### Conversion Patterns
-
-When type conversion comes into play, the general Rewrite Patterns can no longer
-be used. This is due to the fact that the operands of the operation being
-matched will not correspond with the operands of the correct type as determined
-by `TypeConverter`. The operation rewrites on type boundaries must thus use a
-special pattern, the `ConversionPattern`. This pattern provides, as an
-additional argument to the `matchAndRewrite` and `rewrite` methods, the set of
-remapped operands corresponding to the desired type. These patterns also utilize
-a special `PatternRewriter`, `ConversionPatternRewriter`, that provides special
-hooks for use with the conversion infrastructure.
+  /// Note: When attempting to convert a type, e.g. via 'convertType', the
+  ///       mostly recently added conversions will be invoked first.
+  template <typename FnT,
+            typename T = typename llvm::function_traits<FnT>::template arg_t<0>>
+  void addConversion(FnT &&callback) {
+    registerConversion(wrapCallback<T>(std::forward<FnT>(callback)));
+  }
 
-```c++
-struct MyConversionPattern : public ConversionPattern {
-  /// The `matchAndRewrite` hooks on ConversionPatterns take an additional
-  /// `operands` parameter, containing the remapped operands of the original
-  /// operation.
-  virtual LogicalResult
-  matchAndRewrite(Operation *op, ArrayRef<Value> operands,
-                  ConversionPatternRewriter &rewriter) const;
+  /// Register a materialization function, which must be convertible to the
+  /// following form:
+  ///   `Optional<Value> (OpBuilder &, T, ValueRange, Location)`,
+  ///   where `T` is any subclass of `Type`.
+  /// This function is responsible for creating an operation, using the
+  /// OpBuilder and Location provided, that "converts" a range of values into a
+  /// single value of the given type `T`. It must return a Value of the
+  /// converted type on success, an `llvm::None` if it failed but other
+  /// materialization can be attempted, and `nullptr` on unrecoverable failure.
+  /// It will only be called for (sub)types of `T`.
+  ///
+  /// This method registers a materialization that will be called when
+  /// converting an illegal block argument type, to a legal type.
+  template <typename FnT,
+            typename T = typename llvm::function_traits<FnT>::template arg_t<1>>
+  void addArgumentMaterialization(FnT &&callback) {
+    argumentMaterializations.emplace_back(
+        wrapMaterialization<T>(std::forward<FnT>(callback)));
+  }
+  /// This method registers a materialization that will be called when
+  /// converting a legal type to an illegal source type. This is used when
+  /// conversions to an illegal type must persist beyond the main conversion.
+  template <typename FnT,
+            typename T = typename llvm::function_traits<FnT>::template arg_t<1>>
+  void addSourceMaterialization(FnT &&callback) {
+    sourceMaterializations.emplace_back(
+        wrapMaterialization<T>(std::forward<FnT>(callback)));
+  }
+  /// This method registers a materialization that will be called when
+  /// converting type from an illegal, or source, type to a legal type.
+  template <typename FnT,
+            typename T = typename llvm::function_traits<FnT>::template arg_t<1>>
+  void addTargetMaterialization(FnT &&callback) {
+    targetMaterializations.emplace_back(
+        wrapMaterialization<T>(std::forward<FnT>(callback)));
+  }
 };
 ```
 
-These patterns have the same [restrictions](#restrictions) as the basic rewrite
-patterns used in dialect conversion.
-
 ### Region Signature Conversion
 
 From the perspective of type conversion, the types of block arguments are a bit
@@ -268,15 +366,16 @@ 
diff erent operations. Given this, the conversion of the types for blocks must be
 done explicitly via a conversion pattern. To convert the types of block
 arguments within a Region, a custom hook on the `ConversionPatternRewriter` must
 be invoked; `convertRegionTypes`. This hook uses a provided type converter to
-apply type conversions to all blocks within the region, and all blocks that move
-into that region. This hook also takes an optional
-`TypeConverter::SignatureConversion` parameter that applies a custom conversion
-to the entry block of the region. The types of the entry block arguments are
-often tied semantically to details on the operation, e.g. FuncOp, AffineForOp,
-etc. To convert the signature of just the region entry block, and not any other
-blocks within the region, the `applySignatureConversion` hook may be used
-instead. A signature conversion, `TypeConverter::SignatureConversion`, can be
-built programmatically:
+apply type conversions to all blocks within a given region, and all blocks that
+move into that region. As noted above, the conversions performed by this method
+use the argument materialization hook on the `TypeConverter`. This hook also
+takes an optional `TypeConverter::SignatureConversion` parameter that applies a
+custom conversion to the entry block of the region. The types of the entry block
+arguments are often tied semantically to details on the operation, e.g. FuncOp,
+AffineForOp, etc. To convert the signature of just the region entry block, and
+not any other blocks within the region, the `applySignatureConversion` hook may
+be used instead. A signature conversion, `TypeConverter::SignatureConversion`,
+can be built programmatically:
 
 ```c++
 class SignatureConversion {
@@ -303,3 +402,43 @@ public:
 The `TypeConverter` provides several default utilities for signature conversion
 and legality checking:
 `convertSignatureArgs`/`convertBlockSignature`/`isLegal(Region *|Type)`.
+
+## Debugging
+
+To debug the execution of the dialect conversion framework,
+`-debug-only=dialect-conversion` may be used. This command line flag activates
+LLVM's debug logging infrastructure solely for the conversion framework. The
+output is formatted as a tree structure, mirroring the structure of the
+conversion process. This output contains all of the actions performed by the
+rewriter, how generated operations get legalized, and why they fail.
+
+Example output is shown below:
+
+```
+//===-------------------------------------------===//
+Legalizing operation : 'std.return'(0x608000002e20) {
+  "std.return"() : () -> ()
+
+  * Fold {
+  } -> FAILURE : unable to fold
+
+  * Pattern : 'std.return -> ()' {
+    ** Insert  : 'spv.Return'(0x6070000453e0)
+    ** Replace : 'std.return'(0x608000002e20)
+
+    //===-------------------------------------------===//
+    Legalizing operation : 'spv.Return'(0x6070000453e0) {
+      "spv.Return"() : () -> ()
+
+    } -> SUCCESS : operation marked legal by the target
+    //===-------------------------------------------===//
+  } -> SUCCESS : pattern applied successfully
+} -> SUCCESS
+//===-------------------------------------------===//
+```
+
+This output is describing the legalization of an `std.return` operation. We
+first try to legalize by folding the operation, but that is unsuccessful for
+`std.return`. From there, a pattern is applied that replaces the `std.return`
+with a `spv.Return`. The newly generated `spv.Return` is then processed for
+legalization, but is found to already legal as per the target.


        


More information about the Mlir-commits mailing list