[Mlir-commits] [mlir] [mlir][LLVM] Verify llvm.invoke callees like llvm.call (PR #229954)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Wed Oct 7 16:52:53 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-llvm

Author: Bruno Cardoso Lopes (bcardosolopes)

<details>
<summary>Changes</summary>

Generalize CallOp's symbol-use verification into a template shared with InvokeOp, which now implements SymbolUserOpInterface. Direct invokes get the same checking calls have: callee resolution, operand and result types, vararg attributes, and the debug-location rule.

This newly rejects malformed direct invokes that used to be accepted and translate. `mlir/test/Dialect/LLVMIR/roundtrip.mlir`  had such an invoke and now uses a matching callee.

Assisted-by: Muse Code (Muse).

---
Full diff: https://github.com/llvm/llvm-project/pull/229954.diff


5 Files Affected:

- (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td (+1) 
- (modified) mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp (+46-29) 
- (modified) mlir/test/Dialect/LLVMIR/invalid-call-location.mlir (+39) 
- (modified) mlir/test/Dialect/LLVMIR/invalid.mlir (+111) 
- (modified) mlir/test/Dialect/LLVMIR/roundtrip.mlir (+4-2) 


``````````diff
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 39d2952912c51..8dbd6a75cbcd3 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -741,6 +741,7 @@ def LLVM_InvokeOp
     : LLVM_Op<"invoke", [AttrSizedOperandSegments,
                          DeclareOpInterfaceMethods<BranchOpInterface>,
                          DeclareOpInterfaceMethods<CallOpInterface>,
+                         DeclareOpInterfaceMethods<SymbolUserOpInterface>,
                          DeclareOpInterfaceMethods<WeightedBranchOpInterface>,
                          Terminator]> {
   let arguments = (ins
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 89bbb4c642112..f91cf70f5360e 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -1204,10 +1204,11 @@ MutableOperandRange CallOp::getArgOperandsMutable() {
 /// Verify that an inlinable callsite of a debug-info-bearing function in a
 /// debug-info-bearing function has a debug location attached to it. This
 /// mirrors an LLVM IR verifier.
-static LogicalResult verifyCallOpDebugInfo(CallOp callOp, LLVMFuncOp callee) {
+template <typename OpTy>
+static LogicalResult verifyCallOpDebugInfo(OpTy callOp, LLVMFuncOp callee) {
   if (callee.isExternal())
     return success();
-  auto parentFunc = callOp->getParentOfType<FunctionOpInterface>();
+  auto parentFunc = callOp->template getParentOfType<FunctionOpInterface>();
   if (!parentFunc)
     return success();
 
@@ -1298,35 +1299,37 @@ static LogicalResult verifyOperandBundles(OpType &op) {
 
 LogicalResult CallOp::verify() { return verifyOperandBundles(*this); }
 
-LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
-  if (failed(verifyCallOpVarCalleeType(*this)))
-    return failure();
-
+template <typename OpTy>
+static LogicalResult
+verifyCallOpSymbolUses(OpTy callOp, SymbolTableCollection &symbolTable) {
+  // Note: no variadic-callee-type check here. CallOp runs it in
+  // verifySymbolUses below; InvokeOp already runs it in verify().
   // Type for the callee, we'll get it differently depending if it is a direct
   // or indirect call.
   Type fnType;
 
   // If this is an indirect call, the callee attribute is missing.
-  FlatSymbolRefAttr calleeName = getCalleeAttr();
+  FlatSymbolRefAttr calleeName = callOp.getCalleeAttr();
   if (!calleeName) {
     // Note: `verifyCallOpVarCalleeType` has already checked that there is a
     // callee operand.
-    auto ptrType = llvm::dyn_cast<LLVMPointerType>(getOperand(0).getType());
+    auto ptrType =
+        llvm::dyn_cast<LLVMPointerType>(callOp.getOperand(0).getType());
     if (!ptrType)
-      return emitOpError("indirect call expects a pointer as callee: ")
-             << getOperand(0).getType();
+      return callOp.emitOpError("indirect call expects a pointer as callee: ")
+             << callOp.getOperand(0).getType();
 
     // Nothing else to verify: an indirect callee cannot be resolved.
     return success();
   } else {
     Operation *callee =
-        symbolTable.lookupNearestSymbolFrom(*this, calleeName.getAttr());
+        symbolTable.lookupNearestSymbolFrom(callOp, calleeName.getAttr());
     if (!callee)
-      return emitOpError()
+      return callOp.emitOpError()
              << "'" << calleeName.getValue()
              << "' does not reference a symbol in the current scope";
     if (auto fn = dyn_cast<LLVMFuncOp>(callee)) {
-      if (failed(verifyCallOpDebugInfo(*this, fn)))
+      if (failed(verifyCallOpDebugInfo(callOp, fn)))
         return failure();
       fnType = fn.getFunctionType();
     } else if (auto ifunc = dyn_cast<IFuncOp>(callee)) {
@@ -1335,9 +1338,9 @@ LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
       // Aliases can alias functions, so calling through an alias is valid.
       // The function type is determined by the call's operands and result
       // types.
-      fnType = getCalleeFunctionType();
+      fnType = callOp.getCalleeFunctionType();
     } else {
-      return emitOpError()
+      return callOp.emitOpError()
              << "'" << calleeName.getValue()
              << "' does not reference a valid LLVM function, IFunc, or alias";
     }
@@ -1345,29 +1348,33 @@ LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
 
   LLVMFunctionType funcType = llvm::dyn_cast<LLVMFunctionType>(fnType);
   if (!funcType)
-    return emitOpError("callee does not have a functional type: ") << fnType;
+    return callOp.emitOpError("callee does not have a functional type: ")
+           << fnType;
 
-  if (funcType.isVarArg() && !getVarCalleeType())
-    return emitOpError() << "missing var_callee_type attribute for vararg call";
+  if (funcType.isVarArg() && !callOp.getVarCalleeType())
+    return callOp.emitOpError()
+           << "missing var_callee_type attribute for vararg call";
 
   // Verify the result types. These checks are more specific than what
   // `verifyCallOpInterface` can report, so they are run first.
-  if (getNumResults() == 0 &&
+  if (callOp.getNumResults() == 0 &&
       !llvm::isa<LLVM::LLVMVoidType>(funcType.getReturnType()))
-    return emitOpError() << "expected function call to produce a value";
+    return callOp.emitOpError() << "expected function call to produce a value";
 
-  if (getNumResults() != 0 &&
+  if (callOp.getNumResults() != 0 &&
       llvm::isa<LLVM::LLVMVoidType>(funcType.getReturnType()))
-    return emitOpError()
+    return callOp.emitOpError()
            << "calling function with void result must not produce values";
 
-  if (getNumResults() > 1)
-    return emitOpError()
+  if (callOp.getNumResults() > 1)
+    return callOp.emitOpError()
            << "expected LLVM function call to produce 0 or 1 result";
 
-  if (getNumResults() && getResult().getType() != funcType.getReturnType())
-    return emitOpError() << "result type mismatch: " << getResult().getType()
-                         << " != " << funcType.getReturnType();
+  if (callOp.getNumResults() &&
+      callOp.getResult().getType() != funcType.getReturnType())
+    return callOp.emitOpError()
+           << "result type mismatch: " << callOp.getResult().getType()
+           << " != " << funcType.getReturnType();
 
   // Verify that the operand types match the callee. Note that this does not
   // need to special-case a variadic callee: the variadic arguments are not
@@ -1375,8 +1382,14 @@ LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   SmallVector<Type, 1> calleeResultTypes;
   if (!llvm::isa<LLVM::LLVMVoidType>(funcType.getReturnType()))
     calleeResultTypes.push_back(funcType.getReturnType());
-  return call_interface_impl::verifyCallOpInterface(*this, funcType.getParams(),
-                                                    calleeResultTypes);
+  return call_interface_impl::verifyCallOpInterface(
+      callOp, funcType.getParams(), calleeResultTypes);
+}
+
+LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  if (failed(verifyCallOpVarCalleeType(*this)))
+    return failure();
+  return verifyCallOpSymbolUses(*this, symbolTable);
 }
 
 void CallOp::print(OpAsmPrinter &p) {
@@ -1692,6 +1705,10 @@ MutableOperandRange InvokeOp::getArgOperandsMutable() {
                                           getArgOperandsImpl(*this).size());
 }
 
+LogicalResult InvokeOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  return verifyCallOpSymbolUses(*this, symbolTable);
+}
+
 LogicalResult InvokeOp::verify() {
   if (failed(verifyCallOpVarCalleeType(*this)))
     return failure();
diff --git a/mlir/test/Dialect/LLVMIR/invalid-call-location.mlir b/mlir/test/Dialect/LLVMIR/invalid-call-location.mlir
index 38b4ed9f6e83d..202261673081c 100644
--- a/mlir/test/Dialect/LLVMIR/invalid-call-location.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid-call-location.mlir
@@ -33,3 +33,42 @@ llvm.func @invalid_call_debug_locs() {
   llvm.call @missing_debug_loc() : () -> () loc(#loc)
   llvm.return
 } loc(#loc4)
+
+// -----
+
+#di_file = #llvm.di_file<"file.cpp" in "/folder/">
+#di_compile_unit = #llvm.di_compile_unit<
+  id = distinct[0]<>, sourceLanguage = DW_LANG_C_plus_plus_14,
+  file = #di_file, isOptimized = true, emissionKind = Full
+>
+#di_subprogram = #llvm.di_subprogram<
+  compileUnit = #di_compile_unit, scope = #di_file,
+  name = "missing_debug_loc", file = #di_file,
+  subprogramFlags = "Definition|Optimized"
+>
+#di_subprogram1 = #llvm.di_subprogram<
+  compileUnit = #di_compile_unit, scope = #di_file,
+  name = "invalid_invoke_debug_locs", file = #di_file,
+  subprogramFlags = "Definition|Optimized"
+>
+#loc = loc(unknown)
+#loc1 = loc("file.cpp":24:0)
+#loc2 = loc(fused<#di_subprogram>[#loc1])
+#loc3 = loc("file.cpp":42:0)
+#loc4 = loc(fused<#di_subprogram1>[#loc3])
+
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @missing_debug_loc() {
+  llvm.return
+} loc(#loc2)
+
+llvm.func @invalid_invoke_debug_locs() attributes { personality = @__gxx_personality_v0 } {
+// CHECK: <unknown>:0: error: inlinable function call in a function with a DISubprogram location must have a debug location
+  llvm.invoke @missing_debug_loc() to ^bb1 unwind ^bb2 : () -> () loc(#loc)
+^bb1:
+  llvm.return
+^bb2:
+  %0 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  llvm.return
+} loc(#loc4)
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index 8b652b04d88b2..bf51952ff475d 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -309,6 +309,117 @@ func.func @call_non_llvm() {
 
 // -----
 
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_unknown_symbol() -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op 'missing_callee' does not reference a symbol in the current scope}}
+  %0 = llvm.invoke @missing_callee() to ^bb1 unwind ^bb2 : () -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
+func.func private @standard_func_callee()
+
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_non_llvm() -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op 'standard_func_callee' does not reference a valid LLVM function, IFunc, or alias}}
+  %0 = llvm.invoke @standard_func_callee() to ^bb1 unwind ^bb2 : () -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
+llvm.func @foo(i32) -> i32
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_result_mismatch(%arg0: i32) -> i64 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op result type mismatch: 'i64' != 'i32'}}
+  %0 = llvm.invoke @foo(%arg0) to ^bb1 unwind ^bb2 : (i32) -> i64
+^bb1:
+  llvm.return %0 : i64
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i64) : i64
+  llvm.return %c : i64
+}
+
+// -----
+
+llvm.func @foo(i32) -> i32
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_arg_mismatch(%arg0: i64) -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op operand type mismatch: expected operand type 'i32', but provided 'i64' for operand number 0}}
+  %0 = llvm.invoke @foo(%arg0) to ^bb1 unwind ^bb2 : (i64) -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
+llvm.func @bar() -> ()
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_void_with_result() -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op calling function with void result must not produce values}}
+  %0 = llvm.invoke @bar() to ^bb1 unwind ^bb2 : () -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
+llvm.func @variadic(...)
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_vararg_missing_type(%arg0: i32) -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op missing var_callee_type attribute for vararg call}}
+  %0 = llvm.invoke @variadic(%arg0) to ^bb1 unwind ^bb2 : (i32) -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
+llvm.func @__gxx_personality_v0(...) -> i32
+
+llvm.func @invoke_indirect_non_ptr(%arg0: i32, %arg1: i32) -> i32 attributes { personality = @__gxx_personality_v0 } {
+  // expected-error at +1 {{'llvm.invoke' op indirect call expects a pointer as callee: 'i32'}}
+  %0 = llvm.invoke %arg0(%arg1) to ^bb1 unwind ^bb2 : i32, (i32) -> i32
+^bb1:
+  llvm.return %0 : i32
+^bb2:
+  %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
+  %c = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %c : i32
+}
+
+// -----
+
 func.func @call_non_llvm_arg(%arg0 : tensor<*xi32>) {
   // expected-error at +1 {{'llvm.call' op operand #0 must be variadic of LLVM dialect-compatible type}}
   "llvm.call"(%arg0) <{operandSegmentSizes = array<i32: 1, 0>, op_bundle_sizes = array<i32>}> : (tensor<*xi32>) -> ()
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 8485807e5c9c2..688dc37603ba5 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1140,11 +1140,13 @@ llvm.func @test_call_arg_attrs_indirect(%arg0: i16, %arg1: !llvm.ptr) -> i16 {
   llvm.return %0 : i16
 }
 
+llvm.func @somefunc_i16(i16)
+
 // CHECK-LABEL:   llvm.func @test_invoke_arg_attrs(
 // CHECK-SAME:      %[[VAL_0:.*]]: i16) attributes {personality = @__gxx_personality_v0} {
 llvm.func @test_invoke_arg_attrs(%arg0: i16) attributes { personality = @__gxx_personality_v0 } {
-  // CHECK:  llvm.invoke @somefunc(%[[VAL_0]]) to ^bb2 unwind ^bb1 : (i16 {llvm.noundef, llvm.signext}) -> ()
-  llvm.invoke @somefunc(%arg0) to ^bb2 unwind ^bb1 : (i16 {llvm.noundef, llvm.signext}) -> ()
+  // CHECK:  llvm.invoke @somefunc_i16(%[[VAL_0]]) to ^bb2 unwind ^bb1 : (i16 {llvm.noundef, llvm.signext}) -> ()
+  llvm.invoke @somefunc_i16(%arg0) to ^bb2 unwind ^bb1 : (i16 {llvm.noundef, llvm.signext}) -> ()
 ^bb1:
   %1 = llvm.landingpad cleanup : !llvm.struct<(ptr, i32)>
   llvm.return

``````````

</details>


https://github.com/llvm/llvm-project/pull/229954


More information about the Mlir-commits mailing list