-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[MLIR][LLVMIR] Import calls with mismatching signature as indirect call #135895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[MLIR][LLVMIR] Import calls with mismatching signature as indirect call #135895
Conversation
@llvm/pr-subscribers-mlir-llvm @llvm/pr-subscribers-mlir Author: Bruno Cardoso Lopes (bcardosolopes) ChangesLLVM IR currently accepts:
This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file. The error is now a warning, the tests in Full diff: https://github.com/llvm/llvm-project/pull/135895.diff 3 Files Affected:
diff --git a/mlir/include/mlir/Target/LLVMIR/ModuleImport.h b/mlir/include/mlir/Target/LLVMIR/ModuleImport.h
index 3dc848c413905..7b01a96026413 100644
--- a/mlir/include/mlir/Target/LLVMIR/ModuleImport.h
+++ b/mlir/include/mlir/Target/LLVMIR/ModuleImport.h
@@ -362,9 +362,12 @@ class ModuleImport {
/// Converts the callee's function type. For direct calls, it converts the
/// actual function type, which may differ from the called operand type in
/// variadic functions. For indirect calls, it converts the function type
- /// associated with the call instruction. Returns failure when the call and
- /// the callee are not compatible or when nested type conversions failed.
- FailureOr<LLVMFunctionType> convertFunctionType(llvm::CallBase *callInst);
+ /// associated with the call instruction. When the call and the callee are not
+ /// compatible (or when nested type conversions failed), emit a warning but
+ /// attempt translation using a bitcast and an indirect call (in order
+ /// represent valid and verified LLVM IR).
+ FailureOr<LLVMFunctionType> convertFunctionType(llvm::CallBase *callInst,
+ Value &castResult);
/// Returns the callee name, or an empty symbol if the call is not direct.
FlatSymbolRefAttr convertCalleeName(llvm::CallBase *callInst);
/// Converts the parameter and result attributes attached to `func` and adds
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 187f1bdf7af6e..c1f2f378c87fe 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1668,8 +1668,8 @@ ModuleImport::convertCallOperands(llvm::CallBase *callInst,
/// Checks if `callType` and `calleeType` are compatible and can be represented
/// in MLIR.
static LogicalResult
-verifyFunctionTypeCompatibility(LLVMFunctionType callType,
- LLVMFunctionType calleeType) {
+checkFunctionTypeCompatibility(LLVMFunctionType callType,
+ LLVMFunctionType calleeType) {
if (callType.getReturnType() != calleeType.getReturnType())
return failure();
@@ -1695,7 +1695,7 @@ verifyFunctionTypeCompatibility(LLVMFunctionType callType,
}
FailureOr<LLVMFunctionType>
-ModuleImport::convertFunctionType(llvm::CallBase *callInst) {
+ModuleImport::convertFunctionType(llvm::CallBase *callInst, Value &castResult) {
auto castOrFailure = [](Type convertedType) -> FailureOr<LLVMFunctionType> {
auto funcTy = dyn_cast_or_null<LLVMFunctionType>(convertedType);
if (!funcTy)
@@ -1718,11 +1718,17 @@ ModuleImport::convertFunctionType(llvm::CallBase *callInst) {
if (failed(calleeType))
return failure();
- // Compare the types to avoid constructing illegal call/invoke operations.
- if (failed(verifyFunctionTypeCompatibility(*callType, *calleeType))) {
+ // Compare the types, if they are not compatible, avoid illegal call/invoke
+ // operations by casting to the callsite type and issuing an indirect call.
+ // LLVM IR currently supports this usage.
+ if (failed(checkFunctionTypeCompatibility(*callType, *calleeType))) {
Location loc = translateLoc(callInst->getDebugLoc());
- return emitError(loc) << "incompatible call and callee types: " << *callType
- << " and " << *calleeType;
+ FlatSymbolRefAttr calleeSym = convertCalleeName(callInst);
+ castResult = builder.create<LLVM::AddressOfOp>(
+ loc, LLVM::LLVMPointerType::get(context), calleeSym);
+ emitWarning(loc) << "incompatible call and callee types: " << *callType
+ << " and " << *calleeType;
+ return callType;
}
return calleeType;
@@ -1839,16 +1845,29 @@ LogicalResult ModuleImport::convertInstruction(llvm::Instruction *inst) {
/*operand_attrs=*/nullptr)
.getOperation();
}
- FailureOr<LLVMFunctionType> funcTy = convertFunctionType(callInst);
+ Value castResult;
+ FailureOr<LLVMFunctionType> funcTy =
+ convertFunctionType(callInst, castResult);
if (failed(funcTy))
return failure();
- FlatSymbolRefAttr callee = convertCalleeName(callInst);
- auto callOp = builder.create<CallOp>(loc, *funcTy, callee, *operands);
+ FlatSymbolRefAttr callee = nullptr;
+ // If no cast is needed, use the original callee name. Otherwise patch
+ // operands to include the indirect call target. Build indirect call by
+ // passing using a nullptr `callee`.
+ if (!castResult)
+ callee = convertCalleeName(callInst);
+ else
+ operands->insert(operands->begin(), castResult);
+ CallOp callOp = builder.create<CallOp>(loc, *funcTy, callee, *operands);
+
if (failed(convertCallAttributes(callInst, callOp)))
return failure();
- // Handle parameter and result attributes.
- convertParameterAttributes(callInst, callOp, builder);
+
+ // Handle parameter and result attributes. Don't bother if there's a
+ // type mismatch.
+ if (!castResult)
+ convertParameterAttributes(callInst, callOp, builder);
return callOp.getOperation();
}();
@@ -1913,11 +1932,20 @@ LogicalResult ModuleImport::convertInstruction(llvm::Instruction *inst) {
unwindArgs)))
return failure();
- FailureOr<LLVMFunctionType> funcTy = convertFunctionType(invokeInst);
+ Value castResult;
+ FailureOr<LLVMFunctionType> funcTy =
+ convertFunctionType(invokeInst, castResult);
if (failed(funcTy))
return failure();
- FlatSymbolRefAttr calleeName = convertCalleeName(invokeInst);
+ FlatSymbolRefAttr calleeName = nullptr;
+ // If no cast is needed, use the original callee name. Otherwise patch
+ // operands to include the indirect call target. Build indirect call by
+ // passing using a nullptr `callee`.
+ if (!castResult)
+ calleeName = convertCalleeName(invokeInst);
+ else
+ operands->insert(operands->begin(), castResult);
// Create the invoke operation. Normal destination block arguments will be
// added later on to handle the case in which the operation result is
@@ -1929,8 +1957,10 @@ LogicalResult ModuleImport::convertInstruction(llvm::Instruction *inst) {
if (failed(convertInvokeAttributes(invokeInst, invokeOp)))
return failure();
- // Handle parameter and result attributes.
- convertParameterAttributes(invokeInst, invokeOp, builder);
+ // Handle parameter and result attributes. Don't bother if there's a
+ // type mismatch.
+ if (!castResult)
+ convertParameterAttributes(invokeInst, invokeOp, builder);
if (!invokeInst->getType()->isVoidTy())
mapValue(inst, invokeOp.getResults().front());
diff --git a/mlir/test/Target/LLVMIR/Import/instructions.ll b/mlir/test/Target/LLVMIR/Import/instructions.ll
index c294e1b34f9bb..ef3c6430b7152 100644
--- a/mlir/test/Target/LLVMIR/Import/instructions.ll
+++ b/mlir/test/Target/LLVMIR/Import/instructions.ll
@@ -720,3 +720,19 @@ bb2:
declare void @g(...)
declare i32 @__gxx_personality_v0(...)
+
+; // -----
+
+; CHECK-LABEL: llvm.func @incompatible_call_and_callee_types
+define void @incompatible_call_and_callee_types() {
+ ; CHECK: %[[CST:.*]] = llvm.mlir.constant(0 : i64) : i64
+ ; CHECK: %[[TARGET:.*]] = llvm.mlir.addressof @callee : !llvm.ptr
+ ; CHECK: llvm.call %[[TARGET]](%[[CST]]) : !llvm.ptr, (i64) -> ()
+ call void @callee(i64 0)
+ ; CHECK: llvm.return
+ ret void
+}
+
+define void @callee({ptr, i64}, i32) {
+ ret void
+}
|
dad274b
to
4992f78
Compare
I'm somewhat certain that LLVM specifies this is illegal, but the verifier doesn't trigger. I'm currently at EuroLLVM, so I can only give more context tomorrow. |
If you can share some extra context that'd be great, couldn't find much discussions around it. It does looks like a bug, and seems to be around long enough that several people might be "relying" on it, which is unfortunate. Before writing this PR I ran the LLVM's verifier under the debugger and note that at the place LLVM verifier does this check, |
I actually tried to fix the LLVM verifier (see this draft PR: #136052) but got around 124 IMO the llvm importer should support this until LLVM proper is fixed (if ever). Best we can do right now is to issue a warning (moral equivalent of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider this a verifier bug in LLVM.
This is actually a regression from a migration to opaque pointer: #63484
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Mehdi points out, this is UB and causes various issues. How to properly deal with this on the LLVM side is unclear to me, though.
Note that supporting this leads to various invariant violations and verifier problems in MLIR, thus supporting this broken input is undesired for many users. If users somehow want to support this, they can implement an LLVM pass that pre-processes the IR accordingly, for example constructing a wrapper function to bypass the incompatibility.
Thanks for the context and feedback guys! |
For better or worse, LangRef now reflects the status quo: #136189. My understanding is that this PR now is valid w.r.t LLVM rules - @joker-eph considers this a verifier bug, but that's not what the LLVM maintainers consider (see discussions in the PR above). It might be in the best interest of MLIR LLVM dialect to follow the langref - If this PR should take a different approach, happy to make it happen. |
What is blocked on this right now? Where do we actually need to support this? |
From what I understood, this is blocking the import of some "valid" LLVM IR. In my experience this IR always stems from illegal input, but given that this kind of IR was now incorporated into the LangRef, we probably should support or at least handle it. That being said, we should first consider what this implies for existing code, as things like inlining strongly assumes sane types. IIRC, the original import converted incompatible calls into indirect calls to circumvent this, but that is also a hack IMO. |
Right there is an LLVM helper function for this that was used by the import and that is also used by LLVM itself to avoid inlining / optimizations for such calls. |
The resolution from the LLVM IR side was to consider that "if the types of the arguments mismatch the type of the callee, then it is an indirect call". Our documentation for our calls states:
|
My experience differs, we have production code that contains such IR - whether that's good or bad it's orthogonal to the point here.
This PR implements a very similar approach.
What do you suggest instead if we are to do this upstream?
Interesting, mind sharing the name? Reviewers: it's not clear from the comments whether there's consensus to move ahead with this PR (like state above, I'm happy to implement any specific solution if this isn't good enough). I'd love a clear answer - I don't want to waste anyone's time if this is leading nowhere. |
Oh that's fine with me, I thought from the title you were just relaxing the IR verifier instead. |
Good point, the title doesn't help with the intent. Thanks for the fast feedback Mehdi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo some nits (if they make sense).
LLVM IR currently [accepts](https://godbolt.org/z/nqnEsW1ja): ``` define void @incompatible_call_and_callee_types() { call void @callee(i64 0) ret void } define void @callee({ptr, i64}, i32) { ret void } ``` This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file. The error is now a warning, the tests in `mlir/test/Target/LLVMIR/Import/import-failure.ll` already use `CHECK` lines, so no need to add extra diagnostic tests.
4992f78
to
b72c3e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM % final location nits. Thanks for pushing on this 🙏
…ll (llvm#135895) LLVM IR currently [accepts](https://godbolt.org/z/nqnEsW1ja): ``` define void @incompatible_call_and_callee_types() { call void @callee(i64 0) ret void } define void @callee({ptr, i64}, i32) { ret void } ``` This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file. The error is now a warning, the tests in `mlir/test/Target/LLVMIR/Import/import-failure.ll` already use `CHECK` lines, so no need to add extra diagnostic tests.
…ll (llvm#135895) LLVM IR currently [accepts](https://godbolt.org/z/nqnEsW1ja): ``` define void @incompatible_call_and_callee_types() { call void @callee(i64 0) ret void } define void @callee({ptr, i64}, i32) { ret void } ``` This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file. The error is now a warning, the tests in `mlir/test/Target/LLVMIR/Import/import-failure.ll` already use `CHECK` lines, so no need to add extra diagnostic tests.
…ll (llvm#135895) (#40) LLVM IR currently [accepts](https://godbolt.org/z/nqnEsW1ja): ``` define void @incompatible_call_and_callee_types() { call void @callee(i64 0) ret void } define void @callee({ptr, i64}, i32) { ret void } ``` This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file. The error is now a warning, the tests in `mlir/test/Target/LLVMIR/Import/import-failure.ll` already use `CHECK` lines, so no need to add extra diagnostic tests. Co-authored-by: Bruno Cardoso Lopes <[email protected]>
LLVM IR currently accepts:
This currently fails to import. Even though these constructs are dangerous and probably indicate some ODR violation (or optimization bug), they are "valid" and should be imported into LLVM IR dialect. This PR implements that by using an indirect call to represent it. Translation already works nicely and outputs the same source llvm IR file.
The error is now a warning, the tests in
mlir/test/Target/LLVMIR/Import/import-failure.ll
already useCHECK
lines, so no need to add extra diagnostic tests.