[clang] [clang][ClangIR]: Fixes no_prototype C functions CIR lowering. (PR #213478)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 2 16:44:12 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Lucas Ribeiro (lucaslive974)
<details>
<summary>Changes</summary>
Fix CIR to LLVM lowering for no_proto C functions (Fixes #<!-- -->213024)
Since LLVM IR does not support an explicit no_proto representation, this change uses a variadic function signature to preserve the intended semantics.
---
Full diff: https://github.com/llvm/llvm-project/pull/213478.diff
2 Files Affected:
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+33-3)
- (modified) clang/test/CIR/CodeGen/attr-alias-no-proto.c (+5-9)
``````````diff
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index c7e8f97f3a68f..9f505fd20bd42 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2012,12 +2012,32 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
} else { // indirect call
assert(!op->getOperands().empty() &&
"operands list must no be empty for the indirect call");
- auto calleeTy = op->getOperands().front().getType();
+ mlir::Value calleeVal = op->getOperands().front();
+ auto calleeTy = calleeVal.getType();
auto calleePtrTy = cast<cir::PointerType>(calleeTy);
auto calleeFuncTy = cast<cir::FuncType>(calleePtrTy.getPointee());
llvm::append_range(adjustedCallOperands, callOperands);
llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>(
converter->convertType(calleeFuncTy));
+
+ // Def-Use-Chain
+ while (auto castOp = calleeVal.getDefiningOp<cir::CastOp>())
+ calleeVal = castOp.getSrc();
+
+ // To match OGCG, a 'no_proto' call must be lowered to a variadic
+ // LLVM function type '(...)' to safely handle unspecified arguments.
+ // However, this fallback only applies to pure declarations and
+ // aliases originally having a no_proto flag.
+ if (auto getGlobal = calleeVal.getDefiningOp<cir::GetGlobalOp>()) {
+ mlir::Operation *globalOp =
+ symbolTables.lookupNearestSymbolFrom(op, getGlobal.getNameAttr());
+ if (auto funcOp = mlir::dyn_cast_or_null<cir::FuncOp>(globalOp)) {
+ if (funcOp.getNoProto() && !llvmFnTy.isVarArg())
+ llvmFnTy = mlir::LLVM::LLVMFunctionType::get(llvmFnTy.getReturnType(),
+ llvmFnTy.getParams(),
+ /**isVarArg=*/true);
+ }
+ }
}
assert(!cir::MissingFeatures::opCallCallConv());
@@ -2510,11 +2530,21 @@ mlir::LogicalResult CIRToLLVMFuncOpLowering::matchAndRewrite(
mlir::Type resultType =
getTypeConverter()->convertType(fnType.getReturnType());
+ // To match OGCG, a 'no_proto' function must be lowered to a variadic LLVM
+ // function type '(...)' to safely handle unspecified arguments. However, this
+ // fallback only applies to pure declarations and aliases that lack explicit
+ // parameters. We skip this fallback and emit a strict non-variadic signature
+ // if the function has a body, or if an alias redefines the type with explicit
+ // arguments.
+ bool isAlias = op.getAliaseeAttr() && (fnType.getNumInputs() == 0);
+ bool isNoProto =
+ (op.getNoProto() && op.isDeclaration()) || (op.getNoProto() && isAlias);
+ bool isVarArg = fnType.isVarArg() || isNoProto;
+
// Create the LLVM function operation.
mlir::Type llvmFnTy = mlir::LLVM::LLVMFunctionType::get(
resultType ? resultType : mlir::LLVM::LLVMVoidType::get(getContext()),
- signatureConversion.getConvertedTypes(),
- /*isVarArg=*/fnType.isVarArg());
+ signatureConversion.getConvertedTypes(), isVarArg);
// If this is an alias, it needs to be lowered to llvm::AliasOp.
if (std::optional<llvm::StringRef> aliasee = op.getAliasee())
diff --git a/clang/test/CIR/CodeGen/attr-alias-no-proto.c b/clang/test/CIR/CodeGen/attr-alias-no-proto.c
index c6e61bdca668a..8304a65423817 100644
--- a/clang/test/CIR/CodeGen/attr-alias-no-proto.c
+++ b/clang/test/CIR/CodeGen/attr-alias-no-proto.c
@@ -5,14 +5,10 @@
// RUN: %clang_cc1 -std=c11 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
-// LLVM: @noproto_used = alias i32 (), ptr @noproto_used_target
-// LLVM: @noproto_args = alias i32 (), ptr @noproto_args_target
+// LLVM: @noproto_used = alias i32 (...), ptr @noproto_used_target
+// LLVM: @noproto_args = alias i32 (...), ptr @noproto_args_target
// LLVM: @noproto_args2 = alias i32 (i32, i32, i32), ptr @noproto_args_target2
-// FIXME(cir): we list no-proto for the alias in CIR, but perhaps lowering is
-// missing it? We should be able to combine LLVM/OGCG check lines in this file.
-// Filed: https://github.com/llvm/llvm-project/issues/213024
-//
// OGCG: @noproto_used = alias i32 (...), ptr @noproto_used_target
// OGCG: @noproto_args = alias i32 (...), ptr @noproto_args_target
// OGCG: @noproto_args2 = alias i32 (i32, i32, i32), ptr @noproto_args_target2
@@ -30,7 +26,7 @@ int noproto_used() __attribute__((alias("noproto_used_target")));
// CIR: cir.call %[[GET_USED]]() : (!cir.ptr<!cir.func<() -> !s32i>>) -> !s32i
// LLVM-LABEL: define dso_local i32 @noproto_use_it()
-// LLVM: call i32 @noproto_used()
+// LLVM: call i32 (...) @noproto_used()
// OGCG: define dso_local i32 @noproto_use_it()
// OGCG: call i32 (...) @noproto_used()
@@ -53,7 +49,7 @@ int noproto_args() __attribute__((alias("noproto_args_target")));
// CIR: cir.call %[[TO_TYPED]](%{{.*}}, %{{.*}}, %{{.*}}) : (!cir.ptr<!cir.func<(!s32i, !s32i, !s32i) -> !s32i>>, !s32i {llvm.noundef}, !s32i {llvm.noundef}, !s32i {llvm.noundef}) -> !s32i
//
// LLVM-LABEL: define dso_local i32 @noproto_args_use()
-// LLVM: call i32 @noproto_args(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+// LLVM: call i32 (i32, i32, i32, ...) @noproto_args(i32 noundef 1, i32 noundef 2, i32 noundef 3)
// OGCG-LABEL: define dso_local i32 @noproto_args_use()
// OGCG: call i32 (i32, i32, i32, ...) @noproto_args(i32 noundef 1, i32 noundef 2, i32 noundef 3)
@@ -76,7 +72,7 @@ int noproto_args2(int, int, int) __attribute__((alias("noproto_args_target2")));
// CIR: cir.call %4(%{{.*}}, %{{.*}}, %{{.*}}) : (!cir.ptr<!cir.func<(!s32i, !s32i, !s32i) -> !s32i>>, !s32i {llvm.noundef}, !s32i {llvm.noundef}, !s32i {llvm.noundef}) -> !s32i
//
// LLVM-LABEL: define dso_local i32 @noproto_args_use2()
-// LLVM: call i32 @noproto_args2(i32 noundef 1, i32 noundef 2, i32 noundef 3)
+// LLVM: call i32 (i32, i32, i32, ...) @noproto_args2(i32 noundef 1, i32 noundef 2, i32 noundef 3)
// OGCG-LABEL: define dso_local i32 @noproto_args_use2()
// OGCG: call i32 (i32, i32, i32, ...) @noproto_args2(i32 noundef 1, i32 noundef 2, i32 noundef 3)
``````````
</details>
https://github.com/llvm/llvm-project/pull/213478
More information about the cfe-commits
mailing list