[clang] a63dc32 - [clang][CodeGen] Construct function type for callgraph from function definition (#212863)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 16 12:11:50 PDT 2026
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 at Z"}
+// MS: [[F_TINT_DOUBLE]] = !{!"?6AXHN at 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"}
More information about the cfe-commits
mailing list