[Mlir-commits] [mlir] aa3c31a - [MLIR] Prevent invalid IR from being passed outside of RemoveDeadValues (#121079)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Jan 20 06:48:36 PST 2025


Author: Renat Idrisov
Date: 2025-01-20T14:48:32Z
New Revision: aa3c31a86f39552d11f0d5bae8b50541d73aa442

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

LOG: [MLIR] Prevent invalid IR from being passed outside of RemoveDeadValues (#121079)

This is a follow-up for https://github.com/llvm/llvm-project/pull/119110
and a fix for https://github.com/llvm/llvm-project/issues/118450

RemoveDeadValues used to delete Values and analyzing the IR at the same
time, because of that, `isMemoryEffectFree` got invalid IR with
half-deleted linalg.generic operation. This PR separates analysis and
cleanup to prevent such situation.

Thank you!

---------

Co-authored-by: Renat Idrisov <parsifal-47 at users.noreply.github.com>
Co-authored-by: Andrzej WarzyƄski <andrzej.warzynski at gmail.com>

Added: 
    

Modified: 
    mlir/lib/Transforms/RemoveDeadValues.cpp
    mlir/test/Transforms/remove-dead-values.mlir

Removed: 
    


################################################################################
diff  --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index 3429008b2f241a..3e7a0cca31c772 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -72,15 +72,50 @@ using namespace mlir::dataflow;
 
 namespace {
 
+// Set of structures below to be filled with operations and arguments to erase.
+// This is done to separate analysis and tree modification phases,
+// otherwise analysis is operating on half-deleted tree which is incorrect.
+
+struct FunctionToCleanUp {
+  FunctionOpInterface funcOp;
+  BitVector nonLiveArgs;
+  BitVector nonLiveRets;
+};
+
+struct OperationToCleanup {
+  Operation *op;
+  BitVector nonLive;
+};
+
+struct BlockArgsToCleanup {
+  Block *b;
+  BitVector nonLiveArgs;
+};
+
+struct SuccessorOperandsToCleanup {
+  BranchOpInterface branch;
+  unsigned successorIndex;
+  BitVector nonLiveOperands;
+};
+
+struct RDVFinalCleanupList {
+  SmallVector<Operation *> operations;
+  SmallVector<Value> values;
+  SmallVector<FunctionToCleanUp> functions;
+  SmallVector<OperationToCleanup> operands;
+  SmallVector<OperationToCleanup> results;
+  SmallVector<BlockArgsToCleanup> blocks;
+  SmallVector<SuccessorOperandsToCleanup> successorOperands;
+};
+
 // Some helper functions...
 
 /// Return true iff at least one value in `values` is live, given the liveness
 /// information in `la`.
-static bool hasLive(ValueRange values, RunLivenessAnalysis &la) {
+static bool hasLive(ValueRange values, const DenseSet<Value> &nonLiveSet,
+                    RunLivenessAnalysis &la) {
   for (Value value : values) {
-    // If there is a null value, it implies that it was dropped during the
-    // execution of this pass, implying that it was non-live.
-    if (!value)
+    if (nonLiveSet.contains(value))
       continue;
 
     const Liveness *liveness = la.getLiveness(value);
@@ -92,11 +127,12 @@ static bool hasLive(ValueRange values, RunLivenessAnalysis &la) {
 
 /// Return a BitVector of size `values.size()` where its i-th bit is 1 iff the
 /// i-th value in `values` is live, given the liveness information in `la`.
-static BitVector markLives(ValueRange values, RunLivenessAnalysis &la) {
+static BitVector markLives(ValueRange values, const DenseSet<Value> &nonLiveSet,
+                           RunLivenessAnalysis &la) {
   BitVector lives(values.size(), true);
 
   for (auto [index, value] : llvm::enumerate(values)) {
-    if (!value) {
+    if (nonLiveSet.contains(value)) {
       lives.reset(index);
       continue;
     }
@@ -115,6 +151,18 @@ static BitVector markLives(ValueRange values, RunLivenessAnalysis &la) {
   return lives;
 }
 
+/// Collects values marked as "non-live" in the provided range and inserts them
+/// into the nonLiveSet. A value is considered "non-live" if the corresponding
+/// index in the `nonLive` bit vector is set.
+static void collectNonLiveValues(DenseSet<Value> &nonLiveSet, ValueRange range,
+                                 const BitVector &nonLive) {
+  for (auto [index, result] : llvm::enumerate(range)) {
+    if (!nonLive[index])
+      continue;
+    nonLiveSet.insert(result);
+  }
+}
+
 /// Drop the uses of the i-th result of `op` and then erase it iff toErase[i]
 /// is 1.
 static void dropUsesAndEraseResults(Operation *op, BitVector toErase) {
@@ -165,52 +213,59 @@ static SmallVector<OpOperand *> operandsToOpOperands(OperandRange operands) {
   return opOperands;
 }
 
-/// Clean a simple op `op`, given the liveness analysis information in `la`.
-/// Here, cleaning means:
-///   (1) Dropping all its uses, AND
-///   (2) Erasing it
-/// iff it has no memory effects and none of its results are live.
+/// Process a simple operation `op` using the liveness analysis `la`.
+/// If the operation has no memory effects and none of its results are live:
+///   1. Add the operation to a list for future removal, and
+///   2. Mark all its results as non-live values
 ///
-/// It is assumed that `op` is simple. Here, a simple op is one which isn't a
-/// function-like op, a call-like op, a region branch op, a branch op, a region
-/// branch terminator op, or return-like.
-static void cleanSimpleOp(Operation *op, RunLivenessAnalysis &la) {
-  if (!isMemoryEffectFree(op) || hasLive(op->getResults(), la))
+/// The operation `op` is assumed to be simple. A simple operation is one that
+/// is NOT:
+///   - Function-like
+///   - Call-like
+///   - A region branch operation
+///   - A branch operation
+///   - A region branch terminator
+///   - Return-like
+static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
+                            DenseSet<Value> &nonLiveSet,
+                            RDVFinalCleanupList &cl) {
+  if (!isMemoryEffectFree(op) || hasLive(op->getResults(), nonLiveSet, la))
     return;
 
-  op->dropAllUses();
-  op->erase();
+  cl.operations.push_back(op);
+  collectNonLiveValues(nonLiveSet, op->getResults(),
+                       BitVector(op->getNumResults(), true));
 }
 
-/// Clean a function-like op `funcOp`, given the liveness information in `la`
-/// and the IR in `module`. Here, cleaning means:
-///   (1) Dropping the uses of its unnecessary (non-live) arguments,
-///   (2) Erasing these arguments,
-///   (3) Erasing their corresponding operands from its callers,
-///   (4) Erasing its unnecessary terminator operands (return values that are
-///   non-live across all callers),
-///   (5) Dropping the uses of these return values from its callers, AND
-///   (6) Erasing these return values
-/// iff it is not public or external.
-static void cleanFuncOp(FunctionOpInterface funcOp, Operation *module,
-                        RunLivenessAnalysis &la) {
+/// Process a function-like operation `funcOp` using the liveness analysis `la`
+/// and the IR in `module`. If it is not public or external:
+///   (1) Adding its non-live arguments to a list for future removal.
+///   (2) Marking their corresponding operands in its callers for removal.
+///   (3) Identifying and enqueueing unnecessary terminator operands
+///       (return values that are non-live across all callers) for removal.
+///   (4) Enqueueing the non-live arguments and return values for removal.
+///   (5) Collecting the uses of these return values in its callers for future
+///       removal.
+///   (6) Marking all its results as non-live values.
+static void processFuncOp(FunctionOpInterface funcOp, Operation *module,
+                          RunLivenessAnalysis &la, DenseSet<Value> &nonLiveSet,
+                          RDVFinalCleanupList &cl) {
   if (funcOp.isPublic() || funcOp.isExternal())
     return;
 
   // Get the list of unnecessary (non-live) arguments in `nonLiveArgs`.
   SmallVector<Value> arguments(funcOp.getArguments());
-  BitVector nonLiveArgs = markLives(arguments, la);
+  BitVector nonLiveArgs = markLives(arguments, nonLiveSet, la);
   nonLiveArgs = nonLiveArgs.flip();
 
   // Do (1).
   for (auto [index, arg] : llvm::enumerate(arguments))
-    if (arg && nonLiveArgs[index])
-      arg.dropAllUses();
+    if (arg && nonLiveArgs[index]) {
+      cl.values.push_back(arg);
+      nonLiveSet.insert(arg);
+    }
 
   // Do (2).
-  funcOp.eraseArguments(nonLiveArgs);
-
-  // Do (3).
   SymbolTable::UseRange uses = *funcOp.getSymbolUses(module);
   for (SymbolTable::SymbolUse use : uses) {
     Operation *callOp = use.getUser();
@@ -222,9 +277,10 @@ static void cleanFuncOp(FunctionOpInterface funcOp, Operation *module,
         operandsToOpOperands(cast<CallOpInterface>(callOp).getArgOperands());
     for (int index : nonLiveArgs.set_bits())
       nonLiveCallOperands.set(callOpOperands[index]->getOperandNumber());
-    callOp->eraseOperands(nonLiveCallOperands);
+    cl.operands.push_back({callOp, nonLiveCallOperands});
   }
 
+  // Do (3).
   // Get the list of unnecessary terminator operands (return values that are
   // non-live across all callers) in `nonLiveRets`. There is a very important
   // subtlety here. Unnecessary terminator operands are NOT the operands of the
@@ -253,62 +309,74 @@ static void cleanFuncOp(FunctionOpInterface funcOp, Operation *module,
   for (SymbolTable::SymbolUse use : uses) {
     Operation *callOp = use.getUser();
     assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
-    BitVector liveCallRets = markLives(callOp->getResults(), la);
+    BitVector liveCallRets = markLives(callOp->getResults(), nonLiveSet, la);
     nonLiveRets &= liveCallRets.flip();
   }
 
-  // Do (4).
   // Note that in the absence of control flow ops forcing the control to go from
   // the entry (first) block to the other blocks, the control never reaches any
   // block other than the entry block, because every block has a terminator.
   for (Block &block : funcOp.getBlocks()) {
     Operation *returnOp = block.getTerminator();
     if (returnOp && returnOp->getNumOperands() == numReturns)
-      returnOp->eraseOperands(nonLiveRets);
+      cl.operands.push_back({returnOp, nonLiveRets});
   }
-  funcOp.eraseResults(nonLiveRets);
+
+  // Do (4).
+  cl.functions.push_back({funcOp, nonLiveArgs, nonLiveRets});
 
   // Do (5) and (6).
   for (SymbolTable::SymbolUse use : uses) {
     Operation *callOp = use.getUser();
     assert(isa<CallOpInterface>(callOp) && "expected a call-like user");
-    dropUsesAndEraseResults(callOp, nonLiveRets);
+    cl.results.push_back({callOp, nonLiveRets});
+    collectNonLiveValues(nonLiveSet, callOp->getResults(), nonLiveRets);
   }
 }
 
-/// Clean a region branch op `regionBranchOp`, given the liveness information in
-/// `la`. Here, cleaning means:
-///   (1') Dropping all its uses, AND
-///   (2') Erasing it
-/// if it has no memory effects and none of its results are live, AND
-///   (1) Erasing its unnecessary operands (operands that are forwarded to
-///   unneccesary results and arguments),
-///   (2) Cleaning each of its regions,
-///   (3) Dropping the uses of its unnecessary results (results that are
-///   forwarded from unnecessary operands and terminator operands), AND
-///   (4) Erasing these results
-/// otherwise.
-/// Note that here, cleaning a region means:
-///   (2.a) Dropping the uses of its unnecessary arguments (arguments that are
-///   forwarded from unneccesary operands and terminator operands),
-///   (2.b) Erasing these arguments, AND
-///   (2.c) Erasing its unnecessary terminator operands (terminator operands
-///   that are forwarded to unneccesary results and arguments).
-/// It is important to note that values in this op flow from operands and
-/// terminator operands (successor operands) to arguments and results (successor
-/// inputs).
-static void cleanRegionBranchOp(RegionBranchOpInterface regionBranchOp,
-                                RunLivenessAnalysis &la) {
+/// Process a region branch operation `regionBranchOp` using the liveness
+/// information in `la`. The processing involves two scenarios:
+///
+/// Scenario 1: If the operation has no memory effects and none of its results
+/// are live:
+///   (1') Enqueue all its uses for deletion.
+///   (2') Enqueue the branch itself for deletion.
+///
+/// Scenario 2: Otherwise:
+///   (1) Collect its unnecessary operands (operands forwarded to unnecessary
+///       results or arguments).
+///   (2) Process each of its regions.
+///   (3) Collect the uses of its unnecessary results (results forwarded from
+///       unnecessary operands
+///       or terminator operands).
+///   (4) Add these results to the deletion list.
+///
+/// Processing a region includes:
+///   (a) Collecting the uses of its unnecessary arguments (arguments forwarded
+///       from unnecessary operands
+///       or terminator operands).
+///   (b) Collecting these unnecessary arguments.
+///   (c) Collecting its unnecessary terminator operands (terminator operands
+///       forwarded to unnecessary results
+///       or arguments).
+///
+/// Value Flow Note: In this operation, values flow as follows:
+/// - From operands and terminator operands (successor operands)
+/// - To arguments and results (successor inputs).
+static void processRegionBranchOp(RegionBranchOpInterface regionBranchOp,
+                                  RunLivenessAnalysis &la,
+                                  DenseSet<Value> &nonLiveSet,
+                                  RDVFinalCleanupList &cl) {
   // Mark live results of `regionBranchOp` in `liveResults`.
   auto markLiveResults = [&](BitVector &liveResults) {
-    liveResults = markLives(regionBranchOp->getResults(), la);
+    liveResults = markLives(regionBranchOp->getResults(), nonLiveSet, la);
   };
 
   // Mark live arguments in the regions of `regionBranchOp` in `liveArgs`.
   auto markLiveArgs = [&](DenseMap<Region *, BitVector> &liveArgs) {
     for (Region &region : regionBranchOp->getRegions()) {
       SmallVector<Value> arguments(region.front().getArguments());
-      BitVector regionLiveArgs = markLives(arguments, la);
+      BitVector regionLiveArgs = markLives(arguments, nonLiveSet, la);
       liveArgs[&region] = regionLiveArgs;
     }
   };
@@ -491,18 +559,19 @@ static void cleanRegionBranchOp(RegionBranchOpInterface regionBranchOp,
         }
       };
 
-  // Do (1') and (2'). This is the only case where the entire `regionBranchOp`
+  // Scenario 1. This is the only case where the entire `regionBranchOp`
   // is removed. It will not happen in any other scenario. Note that in this
   // case, a non-forwarded operand of `regionBranchOp` could be live/non-live.
   // It could never be live because of this op but its liveness could have been
   // attributed to something else.
+  // Do (1') and (2').
   if (isMemoryEffectFree(regionBranchOp.getOperation()) &&
-      !hasLive(regionBranchOp->getResults(), la)) {
-    regionBranchOp->dropAllUses();
-    regionBranchOp->erase();
+      !hasLive(regionBranchOp->getResults(), nonLiveSet, la)) {
+    cl.operations.push_back(regionBranchOp.getOperation());
     return;
   }
 
+  // Scenario 2.
   // At this point, we know that every non-forwarded operand of `regionBranchOp`
   // is live.
 
@@ -538,48 +607,49 @@ static void cleanRegionBranchOp(RegionBranchOpInterface regionBranchOp,
                    terminatorOperandsToKeep);
 
   // Do (1).
-  regionBranchOp->eraseOperands(operandsToKeep.flip());
+  cl.operands.push_back({regionBranchOp, operandsToKeep.flip()});
 
   // Do (2.a) and (2.b).
   for (Region &region : regionBranchOp->getRegions()) {
     assert(!region.empty() && "expected a non-empty region in an op "
                               "implementing `RegionBranchOpInterface`");
-    for (auto [index, arg] : llvm::enumerate(region.front().getArguments())) {
-      if (argsToKeep[&region][index])
-        continue;
-      if (arg)
-        arg.dropAllUses();
-    }
-    region.front().eraseArguments(argsToKeep[&region].flip());
+    BitVector argsToRemove = argsToKeep[&region].flip();
+    cl.blocks.push_back({&region.front(), argsToRemove});
+    collectNonLiveValues(nonLiveSet, region.front().getArguments(),
+                         argsToRemove);
   }
 
   // Do (2.c).
   for (Region &region : regionBranchOp->getRegions()) {
     Operation *terminator = region.front().getTerminator();
-    terminator->eraseOperands(terminatorOperandsToKeep[terminator].flip());
+    cl.operands.push_back(
+        {terminator, terminatorOperandsToKeep[terminator].flip()});
   }
 
   // Do (3) and (4).
-  dropUsesAndEraseResults(regionBranchOp.getOperation(), resultsToKeep.flip());
+  BitVector resultsToRemove = resultsToKeep.flip();
+  collectNonLiveValues(nonLiveSet, regionBranchOp.getOperation()->getResults(),
+                       resultsToRemove);
+  cl.results.push_back({regionBranchOp.getOperation(), resultsToRemove});
 }
 
-// 1. Iterate over each successor block of the given BranchOpInterface
-//    operation.
-// 2. For each successor block:
-//    a. Retrieve the operands passed to the successor.
-//    b. Use the provided liveness analysis (`RunLivenessAnalysis`) to determine
-//       which operands are live in the successor block.
-//    c. Mark each operand as live or dead based on the analysis.
-// 3. Remove dead operands from the branch operation and arguments accordingly
-
-static void cleanBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la) {
+/// Steps to process a `BranchOpInterface` operation:
+/// Iterate through each successor block of `branchOp`.
+/// (1) For each successor block, gather all operands from all successors.
+/// (2) Fetch their associated liveness analysis data and collect for future
+///     removal.
+/// (3) Identify and collect the dead operands from the successor block
+///     as well as their corresponding arguments.
+
+static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
+                            DenseSet<Value> &nonLiveSet,
+                            RDVFinalCleanupList &cl) {
   unsigned numSuccessors = branchOp->getNumSuccessors();
 
-  // Do (1)
   for (unsigned succIdx = 0; succIdx < numSuccessors; ++succIdx) {
     Block *successorBlock = branchOp->getSuccessor(succIdx);
 
-    // Do (2)
+    // Do (1)
     SuccessorOperands successorOperands =
         branchOp.getSuccessorOperands(succIdx);
     SmallVector<Value> operandValues;
@@ -588,22 +658,74 @@ static void cleanBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la) {
       operandValues.push_back(successorOperands[operandIdx]);
     }
 
-    BitVector successorLiveOperands = markLives(operandValues, la);
+    // Do (2)
+    BitVector successorNonLive =
+        markLives(operandValues, nonLiveSet, la).flip();
+    collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
+                         successorNonLive);
 
     // Do (3)
-    for (int argIdx = successorLiveOperands.size() - 1; argIdx >= 0; --argIdx) {
-      if (!successorLiveOperands[argIdx]) {
-        if (successorBlock->getNumArguments() < successorOperands.size()) {
-          // if block was cleaned through a 
diff erent code path
-          // we only need to remove operands from the invokation
-          successorOperands.erase(argIdx);
-          continue;
-        }
+    cl.blocks.push_back({successorBlock, successorNonLive});
+    cl.successorOperands.push_back({branchOp, succIdx, successorNonLive});
+  }
+}
+
+/// Removes dead values collected in RDVFinalCleanupList.
+/// To be run once when all dead values have been collected.
+static void cleanUpDeadVals(RDVFinalCleanupList &list) {
+  // 1. Operations
+  for (auto &op : list.operations) {
+    op->dropAllUses();
+    op->erase();
+  }
+
+  // 2. Values
+  for (auto &v : list.values) {
+    v.dropAllUses();
+  }
+
+  // 3. Functions
+  for (auto &f : list.functions) {
+    f.funcOp.eraseArguments(f.nonLiveArgs);
+    f.funcOp.eraseResults(f.nonLiveRets);
+  }
+
+  // 4. Operands
+  for (auto &o : list.operands) {
+    o.op->eraseOperands(o.nonLive);
+  }
 
-        successorBlock->getArgument(argIdx).dropAllUses();
-        successorOperands.erase(argIdx);
-        successorBlock->eraseArgument(argIdx);
-      }
+  // 5. Results
+  for (auto &r : list.results) {
+    dropUsesAndEraseResults(r.op, r.nonLive);
+  }
+
+  // 6. Blocks
+  for (auto &b : list.blocks) {
+    // blocks that are accessed via multiple codepaths processed once
+    if (b.b->getNumArguments() != b.nonLiveArgs.size())
+      continue;
+    // it iterates backwards because erase invalidates all successor indexes
+    for (int i = b.nonLiveArgs.size() - 1; i >= 0; --i) {
+      if (!b.nonLiveArgs[i])
+        continue;
+      b.b->getArgument(i).dropAllUses();
+      b.b->eraseArgument(i);
+    }
+  }
+
+  // 7. Successor Operands
+  for (auto &op : list.successorOperands) {
+    SuccessorOperands successorOperands =
+        op.branch.getSuccessorOperands(op.successorIndex);
+    // blocks that are accessed via multiple codepaths processed once
+    if (successorOperands.size() != op.nonLiveOperands.size())
+      continue;
+    // it iterates backwards because erase invalidates all successor indexes
+    for (int i = successorOperands.size() - 1; i >= 0; --i) {
+      if (!op.nonLiveOperands[i])
+        continue;
+      successorOperands.erase(i);
     }
   }
 }
@@ -617,13 +739,21 @@ void RemoveDeadValues::runOnOperation() {
   auto &la = getAnalysis<RunLivenessAnalysis>();
   Operation *module = getOperation();
 
+  // Tracks values eligible for erasure - complements liveness analysis to
+  // identify "droppable" values.
+  DenseSet<Value> deadVals;
+
+  // Maintains a list of Ops, values, branches, etc., slated for cleanup at the
+  // end of this pass.
+  RDVFinalCleanupList finalCleanupList;
+
   module->walk([&](Operation *op) {
     if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
-      cleanFuncOp(funcOp, module, la);
+      processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
     } else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
-      cleanRegionBranchOp(regionBranchOp, la);
+      processRegionBranchOp(regionBranchOp, la, deadVals, finalCleanupList);
     } else if (auto branchOp = dyn_cast<BranchOpInterface>(op)) {
-      cleanBranchOp(branchOp, la);
+      processBranchOp(branchOp, la, deadVals, finalCleanupList);
     } else if (op->hasTrait<::mlir::OpTrait::IsTerminator>()) {
       // Nothing to do here because this is a terminator op and it should be
       // honored with respect to its parent
@@ -631,9 +761,11 @@ void RemoveDeadValues::runOnOperation() {
       // Nothing to do because this op is associated with a function op and gets
       // cleaned when the latter is cleaned.
     } else {
-      cleanSimpleOp(op, la);
+      processSimpleOp(op, la, deadVals, finalCleanupList);
     }
   });
+
+  cleanUpDeadVals(finalCleanupList);
 }
 
 std::unique_ptr<Pass> mlir::createRemoveDeadValuesPass() {

diff  --git a/mlir/test/Transforms/remove-dead-values.mlir b/mlir/test/Transforms/remove-dead-values.mlir
index 9273ac01e7ccec..fe7bcbc7c490b6 100644
--- a/mlir/test/Transforms/remove-dead-values.mlir
+++ b/mlir/test/Transforms/remove-dead-values.mlir
@@ -73,6 +73,32 @@ func.func @cleanable_loop_iter_args_value(%arg0: index) -> index {
 
 // -----
 
+// Checking that the arguments of linalg.generic are properly handled
+// All code below is removed as a result of the pass
+//
+#map = affine_map<(d0, d1, d2) -> (0, d1, d2)>
+#map1 = affine_map<(d0, d1, d2) -> (d0, d1, d2)>
+module {
+  func.func @main() {
+    %cst_3 = arith.constant dense<54> : tensor<1x25x13xi32>
+    %cst_7 = arith.constant dense<11> : tensor<1x25x13xi32>
+    // CHECK-NOT: arith.constant
+    %0 = tensor.empty() : tensor<1x25x13xi32>
+    // CHECK-NOT: tensor
+    %1 = linalg.generic {indexing_maps = [#map, #map, #map1], iterator_types = ["parallel", "parallel", "parallel"]} ins(%cst_3, %cst_7 : tensor<1x25x13xi32>, tensor<1x25x13xi32>) outs(%0 : tensor<1x25x13xi32>) {
+    // CHECK-NOT: linalg.generic
+    ^bb0(%in: i32, %in_15: i32, %out: i32):
+      %29 = arith.xori %in, %in_15 : i32
+      // CHECK-NOT: arith.xori
+      linalg.yield %29 : i32
+      // CHECK-NOT: linalg.yield
+    } -> tensor<1x25x13xi32>
+    return
+  }
+}
+
+// -----
+
 // Note that this cleanup cannot be done by the `canonicalize` pass.
 //
 // CHECK-LABEL: func.func private @clean_func_op_remove_argument_and_return_value() {


        


More information about the Mlir-commits mailing list