[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