https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/212863
>From cde7374ccfeae9003d0d81228b3fbf47ed61c01d Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Wed, 29 Jul 2026 13:11:36 -0700 Subject: [PATCH 1/2] [clang][CodeGen] Construct function type for callgraph from function definition 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). --- clang/lib/CodeGen/CodeGenModule.cpp | 37 +++++++++++- ...all-graph-section-definition-noprototype.c | 57 +++++++++++++++++++ clang/test/CodeGen/call-graph-section.c | 4 +- .../Linker/callgraph-section-noprototype.ll | 42 ++++++++++++++ 4 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 clang/test/CodeGen/call-graph-section-definition-noprototype.c create mode 100644 llvm/test/Linker/callgraph-section-noprototype.ll diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index e7c1d182fd20d..f19cfd6f07157 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3464,11 +3464,38 @@ 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; + + SmallVector<QualType, 8> ParamTypes; + for (const ParmVarDecl *P : Def->parameters()) { + QualType ParamTy = P->getType(); + if (Context.isPromotableIntegerType(ParamTy)) + ParamTy = Context.getPromotedIntegerType(ParamTy); + else if (const auto *BT = ParamTy->getAs<BuiltinType>()) { + if (BT->getKind() == BuiltinType::Float || + BT->getKind() == BuiltinType::Half) + ParamTy = Context.DoubleTy; + } + ParamTypes.push_back(ParamTy); + } + FunctionProtoType::ExtProtoInfo EPI; + QT = Context.getFunctionType(FNPT->getReturnType(), ParamTypes, EPI); + } + F->addMetadata( llvm::LLVMContext::MD_callgraph, - *llvm::MDTuple::get( - getLLVMContext(), - {CreateMetadataIdentifierForCallGraphType(FD->getType())})); + *llvm::MDTuple::get(getLLVMContext(), + {CreateMetadataIdentifierForCallGraphType(QT)})); } } @@ -8636,6 +8663,10 @@ llvm::Metadata *CodeGenModule::CreateMetadataIdentifierGeneralized(QualType T) { llvm::Metadata * CodeGenModule::CreateMetadataIdentifierForCallGraphType(QualType T) { + if (auto *FNPT = T->getAs<FunctionNoProtoType>()) { + FunctionProtoType::ExtProtoInfo EPI; + T = getContext().getFunctionType(FNPT->getReturnType(), {}, EPI); + } return CreateMetadataIdentifierImpl(T, CallGraphMetadataIdMap, "", /*ForceString=*/true); } 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..21193ff8aca92 --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-definition-noprototype.c @@ -0,0 +1,57 @@ +// 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 + +// Tests that function definitions specified without a prototype (C89 empty parameter list or K&R declarations) +// generate !callgraph metadata based on reconstructed parameter types (with default argument promotions). + +// Forward declaration without prototype (pure declaration in this TU). +// Because there is no definition or prototype in this TU, no !callgraph metadata is attached to @decl_only declaration. +// CHECK-LABEL: declare {{.*}}void @decl_only(...) +void decl_only(); + +void use_decl() { + void (*fp)() = decl_only; +} + +// C89 definition with no parameters: reconstructed prototype void (void). +// CHECK-LABEL: define {{(dso_local)?}} void @foo( +// CHECK-SAME: {{.*}} !callgraph [[F_TVOID:![0-9]+]] +void foo() { +} + +// Function returning struct pointer with no parameters: reconstructed prototype struct my_struct *(void). +struct my_struct; +// CHECK-LABEL: define {{(dso_local)?}} ptr @create_my_struct( +// CHECK-SAME: {{.*}} !callgraph [[F_TMY_STRUCT:![0-9]+]] +struct my_struct *create_my_struct() { + return 0; +} + +// Function returning int with no parameters: reconstructed prototype int (void). +// CHECK-LABEL: define {{(dso_local)?}} i32 @baz( +// CHECK-SAME: {{.*}} !callgraph [[F_TINT:![0-9]+]] +int baz() { + return 1; +} + +// K&R function definition: reconstructed prototype void (int, int) with promoted short -> int. +// CHECK-LABEL: define {{(dso_local)?}} void @knr_func( +// CHECK-SAME: {{.*}} !callgraph [[F_TKNR:![0-9]+]] +void knr_func(a, b) + int a; + short b; +{ +} + +// ITANIUM: [[F_TVOID]] = !{!"_ZTSFvvE"} +// ITANIUM: [[F_TMY_STRUCT]] = !{!"_ZTSFP9my_structvE"} +// ITANIUM: [[F_TINT]] = !{!"_ZTSFivE"} +// ITANIUM: [[F_TKNR]] = !{!"_ZTSFviiE"} + +// MS: [[F_TVOID]] = !{!"?6AXXZ"} +// MS: [[F_TMY_STRUCT]] = !{!"?6APEAUmy_struct@@XZ"} +// MS: [[F_TINT]] = !{!"?6AHXZ"} +// MS: [[F_TKNR]] = !{!"?6AXHH@Z"} diff --git a/clang/test/CodeGen/call-graph-section.c b/clang/test/CodeGen/call-graph-section.c index cb2f9015b7ff5..1543f30aa2b40 100644 --- a/clang/test/CodeGen/call-graph-section.c +++ b/clang/test/CodeGen/call-graph-section.c @@ -74,7 +74,7 @@ void stf() { fp_stparam(St2, &St2); } -// ITANIUM: [[F_TVOID]] = !{!"_ZTSFvE"} +// ITANIUM: [[F_TVOID]] = !{!"_ZTSFvvE"} // ITANIUM: [[F_TVOID_CT]] = !{[[F_TVOID:![0-9]+]]} // ITANIUM: [[F_TPRIMITIVE]] = !{!"_ZTSFicfdE"} // ITANIUM: [[F_TPTR]] = !{!"_ZTSFPiPcPfPdE"} @@ -83,7 +83,7 @@ void stf() { // ITANIUM: [[F_TSTRUCT]] = !{!"_ZTSFv3st2PS_E"} // ITANIUM: [[F_TSTRUCT_CT]] = !{[[F_TSTRUCT:![0-9]+]]} -// MS: [[F_TVOID]] = !{!"?6AX@Z"} +// MS: [[F_TVOID]] = !{!"?6AXXZ"} // MS: [[F_TVOID_CT]] = !{[[F_TVOID:![0-9]+]]} // MS: [[F_TPRIMITIVE]] = !{!"?6AHDMN@Z"} // MS: [[F_TPTR]] = !{!"?6APEAHPEADPEAMPEAN@Z"} 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"} >From 390d54aefe5e1e24574347acfa3c242ccf2af88d Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Wed, 19 Aug 2026 22:41:09 +0000 Subject: [PATCH 2/2] Fix the test. Test for promotion normalization expectations. Test for call site and definitions side reconstructed prototype matching. --- ...all-graph-section-definition-noprototype.c | 107 ++++++++++++------ 1 file changed, 75 insertions(+), 32 deletions(-) diff --git a/clang/test/CodeGen/call-graph-section-definition-noprototype.c b/clang/test/CodeGen/call-graph-section-definition-noprototype.c index 21193ff8aca92..9cf93e4892199 100644 --- a/clang/test/CodeGen/call-graph-section-definition-noprototype.c +++ b/clang/test/CodeGen/call-graph-section-definition-noprototype.c @@ -1,57 +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 -// Tests that function definitions specified without a prototype (C89 empty parameter list or K&R declarations) -// generate !callgraph metadata based on reconstructed parameter types (with default argument promotions). - -// Forward declaration without prototype (pure declaration in this TU). -// Because there is no definition or prototype in this TU, no !callgraph metadata is attached to @decl_only declaration. +/// 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 use_decl(void) { void (*fp)() = decl_only; } -// C89 definition with no parameters: reconstructed prototype void (void). -// CHECK-LABEL: define {{(dso_local)?}} void @foo( +/// 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 foo() { -} +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; +{} -// Function returning struct pointer with no parameters: reconstructed prototype struct my_struct *(void). +/// Struct pointer return: C89 parameterless and prototyped (void) return types must match. struct my_struct; -// CHECK-LABEL: define {{(dso_local)?}} ptr @create_my_struct( + +// CHECK-LABEL: define {{(dso_local)?}} ptr @proto_my_struct( // CHECK-SAME: {{.*}} !callgraph [[F_TMY_STRUCT:![0-9]+]] -struct my_struct *create_my_struct() { - return 0; -} +struct my_struct *proto_my_struct(void) { return 0; } -// Function returning int with no parameters: reconstructed prototype int (void). -// CHECK-LABEL: define {{(dso_local)?}} i32 @baz( -// CHECK-SAME: {{.*}} !callgraph [[F_TINT:![0-9]+]] -int baz() { - return 1; -} +// CHECK-LABEL: define {{(dso_local)?}} ptr @c89_my_struct( +// CHECK-SAME: {{.*}} !callgraph [[F_TMY_STRUCT]] +struct my_struct *c89_my_struct() { return 0; } -// K&R function definition: reconstructed prototype void (int, int) with promoted short -> int. -// CHECK-LABEL: define {{(dso_local)?}} void @knr_func( -// CHECK-SAME: {{.*}} !callgraph [[F_TKNR:![0-9]+]] -void knr_func(a, b) - int a; - short b; -{ +/// 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"} -// ITANIUM: [[F_TINT]] = !{!"_ZTSFivE"} -// ITANIUM: [[F_TKNR]] = !{!"_ZTSFviiE"} // MS: [[F_TVOID]] = !{!"?6AXXZ"} +// MS: [[F_TINT_ONE]] = !{!"?6AXH@Z"} +// MS: [[F_TINT_DOUBLE]] = !{!"?6AXHN@Z"} // MS: [[F_TMY_STRUCT]] = !{!"?6APEAUmy_struct@@XZ"} -// MS: [[F_TINT]] = !{!"?6AHXZ"} -// MS: [[F_TKNR]] = !{!"?6AXHH@Z"} + +// CHECK: [[F_TVOID_CT]] = !{[[F_TVOID]]} +// CHECK: [[F_TINT_DOUBLE_CT]] = !{[[F_TINT_DOUBLE]]} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
