[Mlir-commits] [mlir] [mlir][llvm] Fix crash in LLVM inliner when callee has no recognized terminator (PR #183949)

Mehdi Amini llvmlistbot at llvm.org
Sat Feb 28 12:40:47 PST 2026


https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/183949

When the callee of an llvm.call has a body block ending with an unregistered op (rather than a recognized LLVM terminator like llvm.return), the LLVM inliner's handleTerminator method was called with that unregistered op and crashed via a cast<LLVM::ReturnOp>() assertion or use-after-erase due to unresolved call result uses.

The root cause is that the generic MLIR verifier conservatively treats unregistered ops as potential terminators (using mightHaveTrait), so malformed IR of this shape passes verification. The inliner, however, assumes that the callee's terminator is a recognized LLVM op.

Fix by adding a guard in LLVMInlinerInterface::isLegalToInline() that refuses to inline a callee containing any block whose last operation does not have the IsTerminator trait. This prevents the crash and leaves the call site intact without any IR mutation.

Fixes #118766

>From a17de8ce624d6bda604e99f906e81d577f34e749 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Sat, 28 Feb 2026 12:35:33 -0800
Subject: [PATCH] [mlir][llvm] Fix crash in LLVM inliner when callee has no
 recognized terminator

When the callee of an llvm.call has a body block ending with an
unregistered op (rather than a recognized LLVM terminator like
llvm.return), the LLVM inliner's handleTerminator method was called with
that unregistered op and crashed via a cast<LLVM::ReturnOp>() assertion
or use-after-erase due to unresolved call result uses.

The root cause is that the generic MLIR verifier conservatively treats
unregistered ops as potential terminators (using mightHaveTrait), so
malformed IR of this shape passes verification. The inliner, however,
assumes that the callee's terminator is a recognized LLVM op.

Fix by adding a guard in LLVMInlinerInterface::isLegalToInline() that
refuses to inline a callee containing any block whose last operation
does not have the IsTerminator trait. This prevents the crash and
leaves the call site intact without any IR mutation.

Fixes #118766
---
 .../LLVMIR/Transforms/InlinerInterfaceImpl.cpp  | 13 +++++++++++++
 mlir/test/Dialect/LLVMIR/inlining.mlir          | 17 +++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
index 06c0af5445d5d..4b36cbc8bcf00 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
@@ -725,6 +725,19 @@ struct LLVMInlinerInterface : public DialectInlinerInterface {
           }))
         return false;
     }
+    // Refuse to inline if any block in the callee ends with an op that does
+    // not have the terminator trait. The MLIR verifier conservatively accepts
+    // unregistered ops as potential terminators, but the LLVM inliner's
+    // handleTerminator relies on the terminator being a recognized LLVM op
+    // (e.g. llvm.return). Inlining a callee with an unrecognized terminator
+    // would crash.
+    for (Block &block : funcOp.getBody()) {
+      if (!block.empty() && !block.back().hasTrait<OpTrait::IsTerminator>()) {
+        LDBG() << "Cannot inline " << funcOp.getSymName()
+               << ": block ends with non-terminator op";
+        return false;
+      }
+    }
     return true;
   }
 
diff --git a/mlir/test/Dialect/LLVMIR/inlining.mlir b/mlir/test/Dialect/LLVMIR/inlining.mlir
index 9a77c5e223110..63cc0e9fb4461 100644
--- a/mlir/test/Dialect/LLVMIR/inlining.mlir
+++ b/mlir/test/Dialect/LLVMIR/inlining.mlir
@@ -709,3 +709,20 @@ func.func @llvm_ret(%arg0 : i32) -> i32 {
   // CHECK: return %[[R]]
   return %res : i32
 }
+
+// -----
+// Regression test for https://github.com/llvm/llvm-project/issues/118766
+// A callee whose body block ends with an unregistered (non-terminator) op used
+// to crash the inliner. The inliner should skip such callees instead.
+
+llvm.func @callee_malformed_terminator() -> i64 attributes {llvm.emit_c_interface} {
+  // Unregistered op acting as a fake terminator -- the function has no llvm.return.
+  "test.foo"() : () -> ()
+}
+
+// CHECK-LABEL: @caller_malformed_terminator
+llvm.func @caller_malformed_terminator() -> i64 attributes {llvm.emit_c_interface} {
+  // CHECK: llvm.call @callee_malformed_terminator
+  %0 = llvm.call @callee_malformed_terminator() : () -> i64
+  llvm.return %0 : i64
+}



More information about the Mlir-commits mailing list