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(calleeTy); auto calleeFuncTy = cast(calleePtrTy.getPointee()); llvm::append_range(adjustedCallOperands, callOperands); llvmFnTy = cast( converter->convertType(calleeFuncTy)); + + // Def-Use-Chain + while (auto castOp = calleeVal.getDefiningOp()) + 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()) { + mlir::Operation *globalOp = + symbolTables.lookupNearestSymbolFrom(op, getGlobal.getNameAttr()); + if (auto funcOp = mlir::dyn_cast_or_null(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 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 !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 !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 !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)