[Mlir-commits] [mlir] [mlir] Fix crash in dialect conversion for detached root ops (PR #185068)

Mehdi Amini llvmlistbot at llvm.org
Fri Mar 13 08:22:30 PDT 2026


https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/185068

>From 195c6abed3d9ade49317acd890a464f8684f7e16 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Thu, 5 Mar 2026 09:15:19 -0800
Subject: [PATCH] [mlir] Fix crash in dialect conversion for detached root ops

When running dialect conversion with --no-implicit-module, the root op is
parsed without a wrapping module and then detached from its temporary parsing
block (block == nullptr). If a conversion pattern replaces this detached root
op, ReplaceOperationRewrite::commit() would crash with a null pointer
dereference when calling op->getBlock()->getOperations().remove(op).

Fix this with two complementary changes:

1. In ReplaceOperationRewrite::commit(), add a guard that calls
   reportFatalInternalError when op->getBlock() is null. This turns the
   opaque null-pointer crash into a clear diagnostic pointing at the API
   misuse.

2. Make --convert-func-to-spirv explicitly reject detached top-level ops at
   pass startup with a clear diagnostic rather than letting the conversion
   framework abort with a fatal error.

Add a regression test in mlir/test/Conversion/ConvertToSPIRV/ that verifies
the diagnostic is emitted instead of crashing.

Fixes #60491
Assisted-by: Claude Code
---
 mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRVPass.cpp   | 11 +++++++++++
 mlir/lib/Transforms/Utils/DialectConversion.cpp       |  5 +++++
 .../test/Conversion/ConvertToSPIRV/detached-root.mlir | 10 ++++++++++
 3 files changed, 26 insertions(+)
 create mode 100644 mlir/test/Conversion/ConvertToSPIRV/detached-root.mlir

diff --git a/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRVPass.cpp b/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRVPass.cpp
index c0439a4033eac..ab2c7fed23303 100644
--- a/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRVPass.cpp
+++ b/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRVPass.cpp
@@ -36,6 +36,17 @@ void ConvertFuncToSPIRVPass::runOnOperation() {
   MLIRContext *context = &getContext();
   Operation *op = getOperation();
 
+  // This pass requires the target operation to be nested inside a block so
+  // that the dialect conversion framework can properly replace or move ops.
+  // Running it on a detached top-level op (e.g., via --no-implicit-module) is
+  // unsupported; wrap the input in a module op first.
+  if (!op->getBlock()) {
+    op->emitError("'") << getArgument()
+                       << "' pass requires the target operation to be nested "
+                          "in a block; consider wrapping the input in a module";
+    return signalPassFailure();
+  }
+
   auto targetAttr = spirv::lookupTargetEnvOrDefault(op);
   std::unique_ptr<ConversionTarget> target =
       SPIRVConversionTarget::get(targetAttr);
diff --git a/mlir/lib/Transforms/Utils/DialectConversion.cpp b/mlir/lib/Transforms/Utils/DialectConversion.cpp
index ad3ee5d20a534..9ab21f90d5553 100644
--- a/mlir/lib/Transforms/Utils/DialectConversion.cpp
+++ b/mlir/lib/Transforms/Utils/DialectConversion.cpp
@@ -1306,6 +1306,10 @@ void ReplaceOperationRewrite::commit(RewriterBase &rewriter) {
 
   // Do not erase the operation yet. It may still be referenced in `mapping`.
   // Just unlink it for now and erase it during cleanup.
+  if (!op->getBlock())
+    llvm::reportFatalInternalError(
+        "dialect conversion attempted to replace a root operation that has no "
+        "parent block; the pass must ensure its target op is nested in a block");
   op->getBlock()->getOperations().remove(op);
 }
 
@@ -3459,6 +3463,7 @@ LogicalResult ConversionPatternRewriter::legalize(Region *r) {
 LogicalResult OperationConverter::applyConversion(ArrayRef<Operation *> ops) {
   // Convert each operation and discard rewrites on failure.
   ConversionPatternRewriterImpl &rewriterImpl = rewriter.getImpl();
+
   LogicalResult status = legalizeOperations(ops, /*onFailure=*/[&]() {
     // Dialect conversion failed.
     if (rewriterImpl.config.allowPatternRollback) {
diff --git a/mlir/test/Conversion/ConvertToSPIRV/detached-root.mlir b/mlir/test/Conversion/ConvertToSPIRV/detached-root.mlir
new file mode 100644
index 0000000000000..87bae9b777a62
--- /dev/null
+++ b/mlir/test/Conversion/ConvertToSPIRV/detached-root.mlir
@@ -0,0 +1,10 @@
+// RUN: mlir-opt --no-implicit-module --convert-func-to-spirv -verify-diagnostics %s
+
+// Verify that applying --convert-func-to-spirv to a detached top-level op
+// (via --no-implicit-module) produces a clear diagnostic rather than crashing.
+// The pass should refuse to operate on an op that has no parent block.
+//
+// Regression test for https://github.com/llvm/llvm-project/issues/60491
+
+// expected-error at below {{'convert-func-to-spirv' pass requires the target operation to be nested in a block}}
+func.func private @foo()



More information about the Mlir-commits mailing list