[Mlir-commits] [mlir] [mlir][gpu] Fix crash in RemoveDeadValues pass with gpu.launch ops (PR #182711)

Fedor Nikolaev llvmlistbot at llvm.org
Sun Feb 22 05:38:03 PST 2026


https://github.com/felichita updated https://github.com/llvm/llvm-project/pull/182711

>From 657b85448ca1ad186e645a2020101e9c198cfaf2 Mon Sep 17 00:00:00 2001
From: Fedor Nikolaev <fridrixnm at gmail.com>
Date: Sun, 22 Feb 2026 00:01:54 +0100
Subject: [PATCH] [mlir][gpu] Fix crash in RemoveDeadValues pass with
 gpu.launch ops

The RemoveDeadValues pass was crashing with an assertion failure when
processing IR containing gpu.launch operations. The root cause was that
gpu.launch was missing an explicit Write memory effect, causing
wouldOpBeTriviallyDead to incorrectly consider it dead via recursive
analysis of its region.

Fixes #182263
---
 mlir/include/mlir/Dialect/GPU/IR/GPUOps.td    |  1 +
 .../Analysis/DataFlow/LivenessAnalysis.cpp    |  7 +++
 mlir/lib/Dialect/GPU/IR/GPUDialect.cpp        |  6 +++
 mlir/lib/Transforms/RemoveDeadValues.cpp      | 48 ++++++++++++++++++-
 mlir/test/Dialect/GPU/canonicalize.mlir       | 12 ++---
 5 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td b/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
index 48de1a8bf118e..3515da360e129 100644
--- a/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
+++ b/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
@@ -808,6 +808,7 @@ def GPU_LaunchFuncOp :GPU_Op<"launch_func", [
 def GPU_LaunchOp : GPU_Op<"launch", [
       AffineScope, AutomaticAllocationScope, AttrSizedOperandSegments,
       DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>,
+      DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
       GPU_AsyncOpInterface, RecursiveMemoryEffects]>,
     Arguments<(ins Variadic<GPU_AsyncToken>:$asyncDependencies,
                Index:$gridSizeX, Index:$gridSizeY, Index:$gridSizeZ,
diff --git a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
index 4afc35d23fafa..ec9827b38be39 100644
--- a/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/LivenessAnalysis.cpp
@@ -17,6 +17,7 @@
 #include <mlir/IR/Operation.h>
 #include <mlir/IR/Value.h>
 #include <mlir/Interfaces/CallInterfaces.h>
+#include <mlir/Interfaces/FunctionInterfaces.h>
 #include <mlir/Interfaces/SideEffectInterfaces.h>
 #include <mlir/Support/LLVM.h>
 
@@ -237,6 +238,12 @@ RunLivenessAnalysis::RunLivenessAnalysis(Operation *op) {
         for (auto blockArg : llvm::enumerate(block.getArguments())) {
           if (getLiveness(blockArg.value()))
             continue;
+          // Skip block args of ops with regions that are not
+          // RegionBranchOpInterface or FunctionOpInterface
+          // (e.g. gpu.launch) - solver doesn't analyze their regions
+          if (!isa<RegionBranchOpInterface>(op) &&
+              !isa<FunctionOpInterface>(op))
+            continue;
           LDBG() << "Block argument: " << blockArg.index() << " of "
                  << OpWithFlags(op, OpPrintingFlags().skipRegions())
                  << " has no liveness info, mark dead";
diff --git a/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp b/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
index a66a83b7e3ca1..9822fa29ffe8a 100644
--- a/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
+++ b/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
@@ -942,6 +942,12 @@ static void printSizeAssignment(OpAsmPrinter &p, KernelDim3 size,
   p << size.z << " = " << operands.z << ')';
 }
 
+void LaunchOp::getEffects(
+    SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
+        &effects) {
+  effects.emplace_back(MemoryEffects::Write::get());
+}
+
 void LaunchOp::print(OpAsmPrinter &p) {
   if (getAsyncToken()) {
     p << " async";
diff --git a/mlir/lib/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index 12a47ba2fb65a..c853635f1c2d7 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -235,6 +235,7 @@ static void processSimpleOp(Operation *op, RunLivenessAnalysis &la,
   // "dead" if it had a side-effecting user that is reachable.
   bool hasDeadOperand =
       markLives(op->getOperands(), nonLiveSet, la).flip().any();
+
   if (hasDeadOperand) {
     LDBG() << "Simple op has dead operands, so the op must be dead: "
            << OpWithFlags(op,
@@ -511,8 +512,10 @@ static void processBranchOp(BranchOpInterface branchOp, RunLivenessAnalysis &la,
     // Do (2)
     BitVector successorNonLive =
         markLives(operandValues, nonLiveSet, la).flip();
-    collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
-                         successorNonLive);
+    if (std::distance(successorBlock->pred_begin(),
+                      successorBlock->pred_end()) <= 1)
+      collectNonLiveValues(nonLiveSet, successorBlock->getArguments(),
+                           successorNonLive);
 
     // Do (3)
     cl.blocks.push_back({successorBlock, successorNonLive});
@@ -561,6 +564,7 @@ static void cleanUpDeadVals(MLIRContext *ctx, RDVFinalCleanupList &list) {
   // 1. Blocks, We must remove the block arguments and successor operands before
   // deleting the operation, as they may reside in the region operation.
   LDBG() << "Cleaning up " << list.blocks.size() << " block argument lists";
+  DenseSet<Block *> processedBlocks;
   for (auto &b : list.blocks) {
     // blocks that are accessed via multiple codepaths processed once
     if (b.b->getNumArguments() != b.nonLiveArgs.size())
@@ -573,6 +577,29 @@ static void cleanUpDeadVals(MLIRContext *ctx, RDVFinalCleanupList &list) {
          << OpWithFlags(b.b->getParent()->getParentOp(),
                         OpPrintingFlags().skipRegions().printGenericOpForm());
     });
+    // Skip if already processed
+    if (processedBlocks.count(b.b))
+      continue;
+    // Skip entry blocks of functions - handled by processFuncOp
+    if (b.b->isEntryBlock() && isa<FunctionOpInterface>(b.b->getParentOp()))
+      continue;
+    // Only protect blocks with multiple predecessors
+    bool hasMultiplePreds = !b.b->hasNoPredecessors() &&
+                            std::next(b.b->pred_begin()) != b.b->pred_end();
+
+    if (hasMultiplePreds) {
+      // Check if any entry has this block with live args
+      bool hasLiveFromAnyPred = false;
+      for (auto &other : list.blocks) {
+        if (other.b == b.b && other.nonLiveArgs.none()) {
+          hasLiveFromAnyPred = true;
+          break;
+        }
+      }
+      if (hasLiveFromAnyPred)
+        continue;
+    }
+    processedBlocks.insert(b.b);
     // Note: Iterate from the end to make sure that that indices of not yet
     // processes arguments do not change.
     for (int i = b.nonLiveArgs.size() - 1; i >= 0; --i) {
@@ -599,6 +626,23 @@ static void cleanUpDeadVals(MLIRContext *ctx, RDVFinalCleanupList &list) {
          << OpWithFlags(op.branch.getOperation(),
                         OpPrintingFlags().skipRegions().printGenericOpForm());
     });
+
+    // Only protect blocks with multiple predecessors
+    Block *succBlock = op.branch->getSuccessor(op.successorIndex);
+    bool hasMultiplePreds =
+        std::next(succBlock->pred_begin()) != succBlock->pred_end();
+    if (hasMultiplePreds) {
+      bool otherLivePred = false;
+      for (auto &other : list.successorOperands) {
+        Block *otherSucc = other.branch->getSuccessor(other.successorIndex);
+        if (otherSucc == succBlock && other.nonLiveOperands.none()) {
+          otherLivePred = true;
+          break;
+        }
+      }
+      if (otherLivePred)
+        continue;
+    }
     // it iterates backwards because erase invalidates all successor indexes
     for (int i = successorOperands.size() - 1; i >= 0; --i) {
       if (!op.nonLiveOperands[i])
diff --git a/mlir/test/Dialect/GPU/canonicalize.mlir b/mlir/test/Dialect/GPU/canonicalize.mlir
index 1283c1465ca47..3541b64ed597f 100644
--- a/mlir/test/Dialect/GPU/canonicalize.mlir
+++ b/mlir/test/Dialect/GPU/canonicalize.mlir
@@ -319,12 +319,12 @@ func.func @subgroup_reduce_cluster_size_1() {
 
 // -----
 
-// The GPU kernel does not have any side effecting ops, so the entire
-// gpu.launch op can fold away.
-
-// CHECK-LABEL: func @gpu_launch_without_side_effects
-//   CHECK-NOT:   gpu.launch
-func.func @gpu_launch_without_side_effects() {
+// The GPU kernel launch always has side effects (it launches code on the
+// GPU device), so it cannot be folded away even if the kernel body
+// contains no side-effecting ops.
+// CHECK-LABEL: func @gpu_launch_without_body_side_effects
+//   CHECK:   gpu.launch
+func.func @gpu_launch_without_body_side_effects() {
   %0:6 = "test.test1"() : () -> (index, index, index, index, index, index)
   gpu.launch blocks(%arg0, %arg1, %arg2) in (%arg6 = %0#0, %arg7 = %0#1, %arg8 = %0#2)
     threads(%arg3, %arg4, %arg5) in (%arg9 = %0#3, %arg10 = %0#4, %arg11 = %0#5) {



More information about the Mlir-commits mailing list