Author: Prabhu Rajasekaran Date: 2026-09-16T12:11:43-07:00 New Revision: a63dc3288d2b8e178f55590797732dba540fac7d
URL: https://github.com/llvm/llvm-project/commit/a63dc3288d2b8e178f55590797732dba540fac7d DIFF: https://github.com/llvm/llvm-project/commit/a63dc3288d2b8e178f55590797732dba540fac7d.diff LOG: [clang][CodeGen] Construct function type for callgraph from function definition (#212863) When -fexperimental-call-graph-section is enabled, for unprototyped function definitions (such as C89 parameterless declarations or K&R definitions) reconstruct their prototype from the parameter declarations in the definition AST (applying default argument promotions to parameters). Assisted by: Gemini Added: clang/test/CodeGen/call-graph-section-definition-noprototype.c llvm/test/Linker/callgraph-section-noprototype.ll Modified: clang/lib/CodeGen/CGCall.cpp clang/lib/CodeGen/CodeGenModule.cpp clang/lib/CodeGen/CodeGenModule.h Removed: ################################################################################ diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 13036b4cdd58c..fc069492c8092 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6450,9 +6450,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, // unprototyped calls. for (const CallArg &Arg : CallArgs) ParamTypes.push_back(Arg.getType()); - FunctionProtoType::ExtProtoInfo EPI; - CST = getContext().getFunctionType(FNPT->getReturnType(), - ParamTypes, EPI); + CST = CGM.ReconstructCallGraphPrototype(FNPT, ParamTypes); } llvm::Metadata *MD = diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 7bb0e9d8ff560..6fd2c8533eb06 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3617,11 +3617,33 @@ void CodeGenModule::createIndirectFunctionTypeMD(const FunctionDecl *FD, F->getFunction().hasAddressTaken(nullptr, /*IgnoreCallbackUses=*/true, /*IgnoreAssumeLikeCalls=*/true, /*IgnoreLLVMUsed=*/false)) { + const FunctionDecl *Def = nullptr; + bool HasBody = FD->hasBody(Def); + if (!HasBody || !Def) + Def = FD; + + QualType QT = Def->getType(); + if (const auto *FNPT = QT->getAs<FunctionNoProtoType>()) { + // If there is no definition available in this TU for an unprototyped + // function declaration, skip generating incomplete callgraph metadata. + if (!HasBody && !Def->isThisDeclarationADefinition()) + return; + + // Reconstruct a prototype from the parameter declarations in the + // definition AST, applying C default argument promotions (e.g., + // short -> int, float -> double). This ensures definition-side + // type metadata matches the promoted signatures computed at indirect + // call sites in CGCall.cpp (see CodeGenFunction::EmitCall). + SmallVector<QualType, 8> ParamTypes; + for (const ParmVarDecl *P : Def->parameters()) + ParamTypes.push_back(P->getType()); + QT = ReconstructCallGraphPrototype(FNPT, ParamTypes); + } + F->addMetadata( llvm::LLVMContext::MD_callgraph, - *llvm::MDTuple::get( - getLLVMContext(), - {CreateMetadataIdentifierForCallGraphType(FD->getType())})); + *llvm::MDTuple::get(getLLVMContext(), + {CreateMetadataIdentifierForCallGraphType(QT)})); } } @@ -8796,8 +8818,41 @@ llvm::Metadata *CodeGenModule::CreateMetadataIdentifierGeneralized(QualType T) { ".generalized", /*ForceString=*/false); } +// Applies C default argument promotions to a parameter type. +// +// FIXME: The canonical source of truth for C default argument promotion is +// Sema::DefaultArgumentPromotion (SemaExpr.cpp), which operates on Expr*. +// Because CodeGen only has type information (QualType from ParmVarDecl or +// CallArg) and cannot invoke Sema without dummy expressions, we mirror the +// promotion rules here. In the long term, this type-based logic should be +// unified with Sema (for example, by extracting a shared type-level promotion +// helper in ASTContext). +QualType CodeGenModule::GetCallGraphPromotedType(QualType Ty) const { + if (Context.isPromotableIntegerType(Ty)) + return Context.getPromotedIntegerType(Ty); + if (const auto *BT = Ty->getAs<BuiltinType>()) { + if (BT->getKind() == BuiltinType::Float || + BT->getKind() == BuiltinType::Half) + return Context.DoubleTy; + } + return Ty; +} + +QualType CodeGenModule::ReconstructCallGraphPrototype( + const FunctionNoProtoType *FNPT, ArrayRef<QualType> ParamTypes) const { + SmallVector<QualType, 8> PromotedParamTypes; + PromotedParamTypes.reserve(ParamTypes.size()); + for (QualType PT : ParamTypes) + PromotedParamTypes.push_back(GetCallGraphPromotedType(PT)); + FunctionProtoType::ExtProtoInfo EPI; + return Context.getFunctionType(FNPT->getReturnType(), PromotedParamTypes, + EPI); +} + llvm::Metadata * CodeGenModule::CreateMetadataIdentifierForCallGraphType(QualType T) { + if (auto *FNPT = T->getAs<FunctionNoProtoType>()) + T = ReconstructCallGraphPrototype(FNPT, {}); return CreateMetadataIdentifierImpl(T, CallGraphMetadataIdMap, "", /*ForceString=*/true); } diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 1f5ecf734c528..bba46696c5801 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1768,6 +1768,17 @@ class CodeGenModule : public CodeGenTypeCache { /// MDString. llvm::Metadata *CreateMetadataIdentifierForCallGraphType(QualType T); + /// Applies C default argument promotions to a parameter type for Call Graph + /// Section type reconstruction. + QualType GetCallGraphPromotedType(QualType Ty) const; + + /// Reconstructs a FunctionProtoType for an unprototyped function type + /// (FunctionNoProtoType) using the given parameter/argument types, applying + /// default argument promotions to ensure call-site and definition-site type + /// signatures match. + QualType ReconstructCallGraphPrototype(const FunctionNoProtoType *FNPT, + ArrayRef<QualType> ParamTypes) const; + /// Create a metadata identifier that is intended to be used to check virtual /// calls via a member function pointer. llvm::Metadata *CreateMetadataIdentifierForVirtualMemPtrType(QualType T); diff --git a/clang/test/CodeGen/call-graph-section-definition-noprototype.c b/clang/test/CodeGen/call-graph-section-definition-noprototype.c new file mode 100644 index 0000000000000..9cf93e4892199 --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-definition-noprototype.c @@ -0,0 +1,100 @@ +/// Tests that function definitions without a prototype (C89 empty parameter list or K&R declarations) +/// reconstruct parameter types with default argument promotions and produce type identifiers that +/// match both: +/// - Their prototyped equivalents on the definition side. +/// - Indirect callsites using unprototyped function pointers. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-graph-section \ +// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,ITANIUM %s + +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fexperimental-call-graph-section \ +// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,MS %s + +/// Forward declaration without prototype (pure declaration in this TU). +/// Because there is no definition or prototype in this TU, no !callgraph metadata is attached. +// CHECK-LABEL: declare {{.*}}void @decl_only(...) +void decl_only(); + +void use_decl(void) { + void (*fp)() = decl_only; +} + +/// Void parameter list: C89 parameterless definition and C prototyped (void) definition +/// must produce the same type identifier. +// CHECK-LABEL: define {{(dso_local)?}} void @proto_void( +// CHECK-SAME: {{.*}} !callgraph [[F_TVOID:![0-9]+]] +void proto_void(void) {} + +// CHECK-LABEL: define {{(dso_local)?}} void @c89_void( +// CHECK-SAME: {{.*}} !callgraph [[F_TVOID]] +void c89_void() {} + +/// Single argument promotion: K&R int definition and K&R short definition (promoted to int) +/// must produce the same type identifier. +// CHECK-LABEL: define {{(dso_local)?}} void @knr_int( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT_ONE:![0-9]+]] +void knr_int(i) + int i; +{} + +// CHECK-LABEL: define {{(dso_local)?}} void @knr_promoted_int( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT_ONE]] +void knr_promoted_int(i) + short i; +{} + +/// Multi-argument promotions: prototyped (int, double), K&R (int, double), and K&R (short, float) +/// (promoted to int, double) must all produce the same type identifier. +// CHECK-LABEL: define {{(dso_local)?}} void @proto_int_double( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT_DOUBLE:![0-9]+]] +void proto_int_double(int a, double b) {} + +// CHECK-LABEL: define {{(dso_local)?}} void @knr_int_double( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT_DOUBLE]] +void knr_int_double(a, b) + int a; + double b; +{} + +// CHECK-LABEL: define {{(dso_local)?}} void @knr_promoted_multi( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT_DOUBLE]] +void knr_promoted_multi(a, b) + short a; + float b; +{} + +/// Struct pointer return: C89 parameterless and prototyped (void) return types must match. +struct my_struct; + +// CHECK-LABEL: define {{(dso_local)?}} ptr @proto_my_struct( +// CHECK-SAME: {{.*}} !callgraph [[F_TMY_STRUCT:![0-9]+]] +struct my_struct *proto_my_struct(void) { return 0; } + +// CHECK-LABEL: define {{(dso_local)?}} ptr @c89_my_struct( +// CHECK-SAME: {{.*}} !callgraph [[F_TMY_STRUCT]] +struct my_struct *c89_my_struct() { return 0; } + +/// Callsite-to-definition equivalence: indirect calls using unprototyped function pointers +/// must generate !callee_type metadata referencing the normalized definition type identifiers. +void test_indirect_calls(void) { + // CHECK: call void {{.*}}, !callee_type [[F_TVOID_CT:![0-9]+]] + void (*fp_void)() = c89_void; + fp_void(); + + // CHECK: call void {{.*}}, !callee_type [[F_TINT_DOUBLE_CT:![0-9]+]] + void (*fp_multi)() = knr_promoted_multi; + fp_multi((short)1, (float)2.0); +} + +// ITANIUM: [[F_TVOID]] = !{!"_ZTSFvvE"} +// ITANIUM: [[F_TINT_ONE]] = !{!"_ZTSFviE"} +// ITANIUM: [[F_TINT_DOUBLE]] = !{!"_ZTSFvidE"} +// ITANIUM: [[F_TMY_STRUCT]] = !{!"_ZTSFP9my_structvE"} + +// MS: [[F_TVOID]] = !{!"?6AXXZ"} +// MS: [[F_TINT_ONE]] = !{!"?6AXH@Z"} +// MS: [[F_TINT_DOUBLE]] = !{!"?6AXHN@Z"} +// MS: [[F_TMY_STRUCT]] = !{!"?6APEAUmy_struct@@XZ"} + +// CHECK: [[F_TVOID_CT]] = !{[[F_TVOID]]} +// CHECK: [[F_TINT_DOUBLE_CT]] = !{[[F_TINT_DOUBLE]]} diff --git a/llvm/test/Linker/callgraph-section-noprototype.ll b/llvm/test/Linker/callgraph-section-noprototype.ll new file mode 100644 index 0000000000000..1b2e77f121ffe --- /dev/null +++ b/llvm/test/Linker/callgraph-section-noprototype.ll @@ -0,0 +1,42 @@ +; RUN: rm -rf %t && split-file %s %t +; RUN: llvm-link %t/decl.ll %t/def.ll -S | FileCheck %s + +; Tests that when linking a declaration module (without !callgraph metadata for unprototyped decl) +; and a definition module (with full reconstructed !callgraph metadata), +; the merged definition replaces the declaration and retains the definition's !callgraph metadata. + +; CHECK: define dso_local void @bar() +; CHECK: call void (i32, i32, ...) %0(i32 noundef 1, i32 noundef 2), !callee_type [[F_CT:![0-9]+]] +; CHECK: define dso_local void @foo(i32 noundef %a, i32 noundef %0) !callgraph [[F_DEF:![0-9]+]] + +; CHECK: [[F_CT]] = !{[[F_DEF]]} +; CHECK: [[F_DEF]] = !{!"_ZTSFviiE"} + +;--- decl.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux" + +define dso_local void @bar() { +entry: + %fp = alloca ptr, align 8 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + call void (i32, i32, ...) %0(i32 noundef 1, i32 noundef 2), !callee_type !1 + ret void +} + +declare void @foo(...) + +!1 = !{!2} +!2 = !{!"_ZTSFviiE"} + +;--- def.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux" + +define dso_local void @foo(i32 noundef %a, i32 noundef %0) !callgraph !1 { +entry: + ret void +} + +!1 = !{!"_ZTSFviiE"} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
