[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 06:47:44 PST 2026
https://github.com/felichita updated https://github.com/llvm/llvm-project/pull/182711
>From 193924b0b5e30f202e36490290c4e951f1e18f79 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][RemoveDeadValues] Fix crash when processing ops with
regions that don't implement RegionBranchOpInterface
The RemoveDeadValues pass was crashing with an assertion failure when
processing IR containing gpu.launch operations. The root cause was that
the pass and liveness analysis do not handle ops with regions that don't
implement RegionBranchOpInterface - the DataFlow solver doesn't propagate
liveness through such regions, causing block arguments inside them to be
incorrectly marked as dead.
Fixes #182263
---
.../Analysis/DataFlow/LivenessAnalysis.cpp | 7 +++
mlir/lib/Transforms/RemoveDeadValues.cpp | 55 ++++++++++++++++++-
mlir/test/Transforms/remove-dead-values.mlir | 19 +++++++
3 files changed, 78 insertions(+), 3 deletions(-)
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/Transforms/RemoveDeadValues.cpp b/mlir/lib/Transforms/RemoveDeadValues.cpp
index 12a47ba2fb65a..ac1b6c0638a5c 100644
--- a/mlir/lib/Transforms/RemoveDeadValues.cpp
+++ b/mlir/lib/Transforms/RemoveDeadValues.cpp
@@ -511,8 +511,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 +563,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 +576,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 +625,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])
@@ -772,7 +815,12 @@ void RemoveDeadValues::runOnOperation() {
// end of this pass.
RDVFinalCleanupList finalCleanupList;
- module->walk([&](Operation *op) {
+ module->walk([&](Operation *op) -> WalkResult {
+ Operation *parent = op->getParentOp();
+ if (parent && !isa<RegionBranchOpInterface>(parent) &&
+ !isa<FunctionOpInterface>(parent) && !isa<ModuleOp>(parent) &&
+ parent->getNumRegions() > 0)
+ return WalkResult::advance();
if (auto funcOp = dyn_cast<FunctionOpInterface>(op)) {
processFuncOp(funcOp, module, la, deadVals, finalCleanupList);
} else if (auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op)) {
@@ -788,6 +836,7 @@ void RemoveDeadValues::runOnOperation() {
} else {
processSimpleOp(op, la, deadVals, finalCleanupList);
}
+ return WalkResult::advance();
});
MLIRContext *context = module->getContext();
diff --git a/mlir/test/Transforms/remove-dead-values.mlir b/mlir/test/Transforms/remove-dead-values.mlir
index 87e77b2eb700f..0c0412e2da51b 100644
--- a/mlir/test/Transforms/remove-dead-values.mlir
+++ b/mlir/test/Transforms/remove-dead-values.mlir
@@ -476,6 +476,25 @@ func.func @kernel(%arg0: memref<18xf32>) {
// -----
+// Verify that RemoveDeadValues does not crash on gpu.launch ops with
+// dead values inside (gpu.block_id, gpu.thread_id).
+// CHECK-LABEL: func.func @gpu_launch_dead_values_no_crash
+// CHECK-NOT: gpu.block_id
+// CHECK-NOT: gpu.thread_id
+func.func @gpu_launch_dead_values_no_crash() {
+ %c1 = arith.constant 1 : index
+ gpu.launch
+ blocks(%bx, %by, %bz) in (%gx = %c1, %gy = %c1, %gz = %c1)
+ threads(%tx, %ty, %tz) in (%bsx = %c1, %bsy = %c1, %bsz = %c1) {
+ %blk_x = gpu.block_id x
+ %thr_x = gpu.thread_id x
+ gpu.terminator
+ }
+ return
+}
+
+// -----
+
// CHECK-LABEL: llvm_unreachable
// CHECK-LABEL: @fn_with_llvm_unreachable
More information about the Mlir-commits
mailing list