[Mlir-commits] [mlir] [mlir][Transforms] Dialect conversion: Align handling of dropped values (PR #106760)

Matthias Springer llvmlistbot at llvm.org
Fri Aug 30 10:05:19 PDT 2024


https://github.com/matthias-springer created https://github.com/llvm/llvm-project/pull/106760

Handle dropped block arguments and dropped op results in the same way: build a source materialization (that may fold away if unused). This simplifies the code base a bit and makes it possible to merge `legalizeConvertedArgumentTypes` and `legalizeConvertedOpResultTypes` in a future commit. These two functions are almost doing the same thing now.

This commit also fixes a bug where circular materializations were built, e.g.:
```
%0 = "builtin.unrealized_conversion_cast"(%1) : (!a) -> !b
%1 = "builtin.unrealized_conversion_cast"(%0) : (!b) -> !a
// No further uses of %0, %1.
```

This happened when:
1. An op was erased. (No replacement values provided.)
2. A conversion pattern for another op builds a replacement value (first cast op) during `remapValues`, but that SSA value is not used during the pattern application.
3. During the finalization phase, `legalizeConvertedOpResultTypes` thinks that the erased op is alive because of the cast op that was built in Step 2. It builds a cast from that replacement value to the original type.
4. During the commit phase, all uses of the original op are repalced with the casted value produced in Step 3. We have generated circular IR.

>From 07c2361f4526c832313724f1c13f4183dbfc2ca1 Mon Sep 17 00:00:00 2001
From: Matthias Springer <mspringer at nvidia.com>
Date: Fri, 30 Aug 2024 18:54:27 +0200
Subject: [PATCH] [mlir][Transforms] Dialect conversion: Align handling of
 dropped values

Handle dropped block arguments and dropped op results in the same way: build a source materialization (that may fold away if unused). This simplifies the code base a bit and makes it possible to merge `legalizeConvertedArgumentTypes` and `legalizeConvertedOpResultTypes` in a future commit. These two functions are almost doing the same thing now.

This commit also fixes a bug where circular materializations were built, e.g.:
```
%0 = "builtin.unrealized_conversion_cast"(%1) : (!a) -> !b
%1 = "builtin.unrealized_conversion_cast"(%0) : (!b) -> !a
// No further uses of %0, %1.
```

This happened when:
1. An op was erased. (No replacement values provided.)
2. A conversion pattern for another op builds a replacement value (first cast op) during `remapValues`, but that SSA value is not used during the pattern application.
3. During the finalization phase, `legalizeConvertedOpResultTypes` thinks that the erased op is alive because of the cast op that was built in Step 2. It builds a cast from that replacement value to the original type.
4. During the commit phase, all uses of the original op are repalced with the casted value produced in Step 3. We have generated circular IR.
---
 .../Transforms/Utils/DialectConversion.cpp    | 42 ++++---------------
 .../test-legalize-erased-op-with-uses.mlir    |  4 +-
 2 files changed, 10 insertions(+), 36 deletions(-)

diff --git a/mlir/lib/Transforms/Utils/DialectConversion.cpp b/mlir/lib/Transforms/Utils/DialectConversion.cpp
index cc9c9495e5155c..ebcc9f948df35c 100644
--- a/mlir/lib/Transforms/Utils/DialectConversion.cpp
+++ b/mlir/lib/Transforms/Utils/DialectConversion.cpp
@@ -1385,9 +1385,14 @@ void ConversionPatternRewriterImpl::notifyOpReplaced(Operation *op,
   // Create mappings for each of the new result values.
   for (auto [newValue, result] : llvm::zip(newValues, op->getResults())) {
     if (!newValue) {
-      resultChanged = true;
-      continue;
+      // This result was dropped and no replacement value was provided.
+      // Materialize a replacement value "out of thin air".
+      newValue = buildUnresolvedMaterialization(
+          MaterializationKind::Source, computeInsertPoint(result),
+          result.getLoc(), /*inputs=*/ValueRange(),
+          /*outputType=*/result.getType(), currentTypeConverter);
     }
+
     // Remap, and check for any result type changes.
     mapping.map(result, newValue);
     resultChanged |= (newValue.getType() != result.getType());
@@ -2359,11 +2364,6 @@ struct OperationConverter {
       ConversionPatternRewriterImpl &rewriterImpl,
       DenseMap<Value, SmallVector<Value>> &inverseMapping);
 
-  /// Legalize an operation result that was marked as "erased".
-  LogicalResult
-  legalizeErasedResult(Operation *op, OpResult result,
-                       ConversionPatternRewriterImpl &rewriterImpl);
-
   /// Dialect conversion configuration.
   ConversionConfig config;
 
@@ -2579,14 +2579,7 @@ LogicalResult OperationConverter::legalizeConvertedOpResultTypes(
     Operation *op = opReplacement->getOperation();
     for (OpResult result : op->getResults()) {
       Value newValue = rewriterImpl.mapping.lookupOrNull(result);
-
-      // If the operation result was replaced with null, all of the uses of this
-      // value should be replaced.
-      if (!newValue) {
-        if (failed(legalizeErasedResult(op, result, rewriterImpl)))
-          return failure();
-        continue;
-      }
+      assert(newValue && "replacement value not found");
 
       // Otherwise, check to see if the type of the result changed.
       if (result.getType() == newValue.getType())
@@ -2655,25 +2648,6 @@ LogicalResult OperationConverter::legalizeConvertedArgumentTypes(
   return success();
 }
 
-LogicalResult OperationConverter::legalizeErasedResult(
-    Operation *op, OpResult result,
-    ConversionPatternRewriterImpl &rewriterImpl) {
-  // If the operation result was replaced with null, all of the uses of this
-  // value should be replaced.
-  auto liveUserIt = llvm::find_if_not(result.getUsers(), [&](Operation *user) {
-    return rewriterImpl.isOpIgnored(user);
-  });
-  if (liveUserIt != result.user_end()) {
-    InFlightDiagnostic diag = op->emitError("failed to legalize operation '")
-                              << op->getName() << "' marked as erased";
-    diag.attachNote(liveUserIt->getLoc())
-        << "found live user of result #" << result.getResultNumber() << ": "
-        << *liveUserIt;
-    return failure();
-  }
-  return success();
-}
-
 //===----------------------------------------------------------------------===//
 // Reconcile Unrealized Casts
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Transforms/test-legalize-erased-op-with-uses.mlir b/mlir/test/Transforms/test-legalize-erased-op-with-uses.mlir
index 49275e8008e749..b536b80ce58309 100644
--- a/mlir/test/Transforms/test-legalize-erased-op-with-uses.mlir
+++ b/mlir/test/Transforms/test-legalize-erased-op-with-uses.mlir
@@ -3,8 +3,8 @@
 // Test that an error is emitted when an operation is marked as "erased", but
 // has users that live across the conversion.
 func.func @remove_all_ops(%arg0: i32) -> i32 {
-  // expected-error at below {{failed to legalize operation 'test.illegal_op_a' marked as erased}}
+  // expected-error at below {{failed to legalize unresolved materialization from () to 'i32' that remained live after conversion}}
   %0 = "test.illegal_op_a"() : () -> i32
-  // expected-note at below {{found live user of result #0: func.return %0 : i32}}
+  // expected-note at below {{see existing live user here: func.return %0 : i32}}
   return %0 : i32
 }



More information about the Mlir-commits mailing list