[clang] [clang][ClangIR]: Fixes no_prototype C functions CIR lowering. (PR #213478)

Lucas Ribeiro via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 2 11:48:01 PDT 2026


https://github.com/lucaslive974 updated https://github.com/llvm/llvm-project/pull/213478

>From b3da3f276bfad57c7627e13826c8a57a36b58f98 Mon Sep 17 00:00:00 2001
From: lucaslive974 <lucasribeirolima974 at gmail.com>
Date: Thu, 30 Jul 2026 21:30:20 -0300
Subject: [PATCH] [clang][ClangIR]:  Fixes no_prototype C functions CIR
 lowering.

---
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 42 ++++++++++++++++---
 clang/test/CIR/CodeGen/attr-alias-no-proto.c  | 14 +++----
 2 files changed, 42 insertions(+), 14 deletions(-)

diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index c7e8f97f3a68f..98cdaf21afcc2 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1975,9 +1975,9 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
   // callee is an alias.
   SmallVector<mlir::Value> adjustedCallOperands;
 
+  mlir::Operation *callee = nullptr;
   if (calleeAttr) { // direct call
-    mlir::Operation *callee =
-        symbolTables.lookupNearestSymbolFrom(op, calleeAttr);
+    callee = symbolTables.lookupNearestSymbolFrom(op, calleeAttr);
     if (auto fn = mlir::dyn_cast<mlir::FunctionOpInterface>(callee)) {
       llvmFnTy = converter->convertType<mlir::LLVM::LLVMFunctionType>(
           fn.getFunctionType());
@@ -2012,12 +2012,34 @@ 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));
+
+    while (auto castOp = calleeVal.getDefiningOp<cir::CastOp>())
+      calleeVal = castOp.getSrc();
+
+    if (auto getGlobal = calleeVal.getDefiningOp<cir::GetGlobalOp>())
+      if (mlir::Operation *globalOp =
+              symbolTables.lookupNearestSymbolFrom(op, getGlobal.getNameAttr()))
+        callee = globalOp;
+  }
+
+  // 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 originaly have a no_proto flag.
+  if (auto funcOp = mlir::dyn_cast_or_null<cir::FuncOp>(callee)) {
+    bool isDeclOrAlias =
+        funcOp.isDeclaration() || (funcOp.getAliaseeAttr() != nullptr);
+    bool isNoProto(funcOp.getNoProto() && isDeclOrAlias);
+    bool isVarArg = llvmFnTy.isVarArg() || isNoProto;
+    llvmFnTy = mlir::LLVM::LLVMFunctionType::get(
+        llvmFnTy.getReturnType(), llvmFnTy.getParams(), isVarArg);
   }
 
   assert(!cir::MissingFeatures::opCallCallConv());
@@ -2510,11 +2532,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 isDeclOrAlias = op.isDeclaration() || (op.getAliaseeAttr() != nullptr);
+  bool isNoProto =
+      (op.getNoProto() && isDeclOrAlias) && (fnType.getNumInputs() == 0);
+  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)
 



More information about the cfe-commits mailing list