https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/230732
>From d30b6d0cbbda1b8a0f384d3aa0e0e3b0786f74f3 Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Sat, 10 Oct 2026 11:23:55 -0700 Subject: [PATCH 1/2] [clang][CodeGen] Emit callee_type metadata for all indirect calls CodeGenFunction::EmitCall only attached the call graph section callee_type metadata to an indirect call when its caller asked for the emitted call instruction through the callOrInvoke out parameter, because the emission was nested in the block that stores it. Indirect calls emitted by callers that do not pass callOrInvoke, such as the virtual destructor call of a delete expression (emitVirtualObjectDelete -> EmitCXXDestructorCall), got no callee_type metadata and therefore no indirect callee type identifiers in the .llvm.callgraph section, so the call graph missed those edges. Attach the metadata to the emitted call instruction regardless of whether the caller asked for it. Assisted-by: Claude --- clang/lib/CodeGen/CGCall.cpp | 134 +++++++++--------- .../call-graph-section-virtual-dtor.cpp | 32 +++++ 2 files changed, 98 insertions(+), 68 deletions(-) create mode 100644 clang/test/CodeGen/call-graph-section-virtual-dtor.cpp diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 1f4fd55060d39..fcb6304389a67 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6402,78 +6402,76 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, CI->getCalledFunction()->getName().starts_with("_Z4sqrt")) { SetSqrtFPAccuracy(CI); } - if (callOrInvoke) { + if (callOrInvoke) *callOrInvoke = CI; - if (CGM.getCodeGenOpts().CallGraphSection) { - QualType CST; - if (TargetDecl && TargetDecl->getFunctionType()) - CST = QualType(TargetDecl->getFunctionType(), 0); - else if (const auto *FPT = - Callee.getAbstractInfo().getCalleeFunctionProtoType()) - CST = QualType(FPT, 0); - else if (const auto *FT = - Callee.getAbstractInfo().getCalleeFunctionType()) - CST = QualType(FT, 0); - else - llvm_unreachable( - "Cannot find the callee type to generate callee_type metadata."); - - // Set type identifier metadata of indirect calls for call graph section. - if (!CST.isNull()) { - if (!CST->isFunctionProtoType()) { - // Reconstruct a prototype for unprototyped callees from the argument - // types passed at the call site (after default argument promotion). - // - // Basic Rationale & K&R-Style Definitions: - // The argument types in CallArgs have already undergone C default - // argument promotion (e.g., char/short -> int, float -> double). - // Furthermore, for a K&R-style - // definition (e.g., void foo(x) short x; { ... }), canonical C ABI - // semantics expect the promoted type (int) at the call boundary - // and implicitly cast down to the declared type (short) inside the - // function. Therefore, signature computation at K&R definition - // sites must also apply default argument promotion (yielding - // void(int), not void(short)) so definition and call sites match. - // - // Signature Strictness & Normalization: - // Since type identifier matching relies on exact hash equality, any - // tolerance for C compatibility rules must be done by normalizing - // types before hashing. - // - Standard C allows certain exceptions for unprototyped calls (and - // variadic va_arg), such as differences in signedness (e.g., - // passing an int to an unsigned int parameter) or - // interchangeability of enum types with their underlying integer - // types. - // - Existing CFI normalization (e.g., - // -fsanitize-cfi-icall-experimental-normalize-integers) normalizes - // types by bit-width and signedness (e.g., int vs long on LP64, - // which C does not treat as compatible), but does not normalize - // away signedness or enum mismatches. - // - In the future, whether to normalize away signedness, enums, or - // integer bit-widths depends on whether call graph analysis should - // err on the side of inclusion (admitting any C-valid call) or - // strictness (like CFI). Any normalization applied here at the call - // site must remain strictly matched with definition-site - // type signature computation. - if (const auto *FNPT = CST->getAs<FunctionNoProtoType>()) { - SmallVector<QualType, 8> ParamTypes; - // CallArgs already contains default-promoted argument types for - // unprototyped calls. - for (const CallArg &Arg : CallArgs) - ParamTypes.push_back(Arg.getType()); - CST = CGM.ReconstructCallGraphPrototype(FNPT, ParamTypes); - } - llvm::Metadata *MD = - CGM.CreateMetadataIdentifierForCallGraphType(CST); - StringRef TypeStr; - if (auto *MDS = dyn_cast_or_null<llvm::MDString>(MD)) - TypeStr = MDS->getString(); - - CGM.getDiags().Report(Loc, diag::warn_cgs_no_proto) << CST << TypeStr; + if (CGM.getCodeGenOpts().CallGraphSection) { + QualType CST; + if (TargetDecl && TargetDecl->getFunctionType()) + CST = QualType(TargetDecl->getFunctionType(), 0); + else if (const auto *FPT = + Callee.getAbstractInfo().getCalleeFunctionProtoType()) + CST = QualType(FPT, 0); + else if (const auto *FT = Callee.getAbstractInfo().getCalleeFunctionType()) + CST = QualType(FT, 0); + else + llvm_unreachable( + "Cannot find the callee type to generate callee_type metadata."); + + // Set type identifier metadata of indirect calls for call graph section. + if (!CST.isNull()) { + if (!CST->isFunctionProtoType()) { + // Reconstruct a prototype for unprototyped callees from the argument + // types passed at the call site (after default argument promotion). + // + // Basic Rationale & K&R-Style Definitions: + // The argument types in CallArgs have already undergone C default + // argument promotion (e.g., char/short -> int, float -> double). + // Furthermore, for a K&R-style + // definition (e.g., void foo(x) short x; { ... }), canonical C ABI + // semantics expect the promoted type (int) at the call boundary + // and implicitly cast down to the declared type (short) inside the + // function. Therefore, signature computation at K&R definition + // sites must also apply default argument promotion (yielding + // void(int), not void(short)) so definition and call sites match. + // + // Signature Strictness & Normalization: + // Since type identifier matching relies on exact hash equality, any + // tolerance for C compatibility rules must be done by normalizing + // types before hashing. + // - Standard C allows certain exceptions for unprototyped calls (and + // variadic va_arg), such as differences in signedness (e.g., + // passing an int to an unsigned int parameter) or + // interchangeability of enum types with their underlying integer + // types. + // - Existing CFI normalization (e.g., + // -fsanitize-cfi-icall-experimental-normalize-integers) normalizes + // types by bit-width and signedness (e.g., int vs long on LP64, + // which C does not treat as compatible), but does not normalize + // away signedness or enum mismatches. + // - In the future, whether to normalize away signedness, enums, or + // integer bit-widths depends on whether call graph analysis should + // err on the side of inclusion (admitting any C-valid call) or + // strictness (like CFI). Any normalization applied here at the call + // site must remain strictly matched with definition-site + // type signature computation. + if (const auto *FNPT = CST->getAs<FunctionNoProtoType>()) { + SmallVector<QualType, 8> ParamTypes; + // CallArgs already contains default-promoted argument types for + // unprototyped calls. + for (const CallArg &Arg : CallArgs) + ParamTypes.push_back(Arg.getType()); + CST = CGM.ReconstructCallGraphPrototype(FNPT, ParamTypes); } - CGM.createCalleeTypeMetadataForIcall(CST, *callOrInvoke); + + llvm::Metadata *MD = CGM.CreateMetadataIdentifierForCallGraphType(CST); + StringRef TypeStr; + if (auto *MDS = dyn_cast_or_null<llvm::MDString>(MD)) + TypeStr = MDS->getString(); + + CGM.getDiags().Report(Loc, diag::warn_cgs_no_proto) << CST << TypeStr; } + CGM.createCalleeTypeMetadataForIcall(CST, CI); } } diff --git a/clang/test/CodeGen/call-graph-section-virtual-dtor.cpp b/clang/test/CodeGen/call-graph-section-virtual-dtor.cpp new file mode 100644 index 0000000000000..23b4bf9144552 --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-virtual-dtor.cpp @@ -0,0 +1,32 @@ +// Tests that callee_type metadata is attached to indirect calls that are +// emitted without asking CodeGenFunction::EmitCall for the emitted call +// instruction, such as virtual destructor calls. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-graph-section \ +// RUN: -emit-llvm -o - %s | FileCheck %s + +struct Base { + virtual ~Base() {} + virtual void vf() {} +}; + +struct Derived : Base { + ~Derived() override {} + void vf() override {} +}; + +// CHECK-LABEL: define {{.*}} @_Z9call_dtorP4Base( +// CHECK: call void %{{.*}}, !callee_type [[DTOR_CT:![0-9]+]] +void call_dtor(Base *b) { delete b; } + +// CHECK-LABEL: define {{.*}} @_Z7call_vfP4Base( +// CHECK: call void %{{.*}}, !callee_type [[DTOR_CT]] +void call_vf(Base *b) { b->vf(); } + +Base *make() { return new Derived(); } + +// CHECK-DAG: define {{.*}} @_ZN7DerivedD0Ev({{.*}} !callgraph [[DTOR:![0-9]+]] { +// CHECK-DAG: define {{.*}} @_ZN7Derived2vfEv({{.*}} !callgraph [[DTOR]] { + +// CHECK-DAG: [[DTOR]] = !{!"_ZTSFvvE"} +// CHECK-DAG: [[DTOR_CT]] = !{[[DTOR]]} >From f461f3db203d7437d6a9512adfe936d7c8f6c0eb Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Sat, 10 Oct 2026 11:25:24 -0700 Subject: [PATCH 2/2] [clang][CodeGen] Identify virtual methods by their override chain root in the call graph section A virtual call site is annotated with the type of the method named at the call site (e.g. `Base::clone` for `b->clone()` with `b` of type `Base *`), but it can reach any overrider of that method. Overriders may differ from the overridden method only in their covariant return type, in which case their call graph section type identifier (`!callgraph`) did not match the type identifier of the call site (`!callee_type`), so the call graph missed the edge: `Derived *Derived::clone()` was identified as `_ZTSFP7DerivedvE`, while calls through `Base *` were typed `_ZTSFP4BasevE`. Identify every method of an override chain, at the definition and at the call site, by the type of the method that introduced the virtual function (the root of the override chain), so that virtual calls through a pointer to any class of the hierarchy match the overriders they can reach. A method that overrides several virtual functions with different covariant return types is identified after the first one; the return adjusting thunks that fill the vtable slots created by the other overridden methods are identified after those methods (recorded in `ThunkInfo::Method`), and they call the method directly. While here, use `setMetadata` rather than `addMetadata` for `!callgraph`, since a function is identified by a single type identifier. Functions that were declared before being defined used to get the same identifier attached twice. Calls through member function pointers are still typed after the member pointer type, so they only match overriders with a covariant return type when the member pointer is formed from the method that introduced the virtual function. Stacked on #230824, which makes `callee_type` metadata independent of the `callOrInvoke` out parameter of `EmitCall`. Assisted-by: Claude --- clang/lib/CodeGen/CGCall.cpp | 4 +- clang/lib/CodeGen/CGVTables.cpp | 8 ++ clang/lib/CodeGen/CodeGenModule.cpp | 33 ++++++- clang/lib/CodeGen/CodeGenModule.h | 8 ++ .../call-graph-section-covariant-return.cpp | 87 +++++++++++++++++++ 5 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 clang/test/CodeGen/call-graph-section-covariant-return.cpp diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index fcb6304389a67..d417c681755ae 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6407,7 +6407,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, if (CGM.getCodeGenOpts().CallGraphSection) { QualType CST; - if (TargetDecl && TargetDecl->getFunctionType()) + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(TargetDecl)) + CST = CGM.GetCallGraphFunctionType(FD); + else if (TargetDecl && TargetDecl->getFunctionType()) CST = QualType(TargetDecl->getFunctionType(), 0); else if (const auto *FPT = Callee.getAbstractInfo().getCalleeFunctionProtoType()) diff --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp index f0c48eba1f5c3..209d6aefa4429 100644 --- a/clang/lib/CodeGen/CGVTables.cpp +++ b/clang/lib/CodeGen/CGVTables.cpp @@ -646,6 +646,14 @@ llvm::Constant *CodeGenVTables::maybeEmitThunk(GlobalDecl GD, CodeGenFunction(CGM).generateThunk(ThunkFn, FnInfo, GD, TI, IsUnprototyped); } + // A return adjusting thunk fills the vtable slot created by the overridden + // method recorded in the thunk info, so it is reached by virtual calls typed + // after that method rather than after the thunk's target. Identify it in the + // call graph section accordingly (see + // CodeGenModule::GetCallGraphFunctionType). + if (CGM.getCodeGenOpts().CallGraphSection && TI.Method) + CGM.createIndirectFunctionTypeMD(TI.Method, ThunkFn); + setThunkProperties(CGM, TI, ThunkFn, ForVTable, GD); return ThunkFn; } diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 62ef11a697b1c..f1a35e2a6402b 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3633,6 +3633,29 @@ static void setLinkageForGV(llvm::GlobalValue *GV, const NamedDecl *ND) { GV->setLinkage(llvm::GlobalValue::ExternalWeakLinkage); } +/// Returns the method that introduced the virtual function that \p MD +/// overrides, i.e., the root of its (first) override chain. +static const CXXMethodDecl *GetRootOverriddenMethod(const CXXMethodDecl *MD) { + while (MD->size_overridden_methods() > 0) + MD = *MD->begin_overridden_methods(); + return MD; +} + +QualType CodeGenModule::GetCallGraphFunctionType(const FunctionDecl *FD) const { + // A virtual call is typed after the method named at the call site, but it + // can reach any overrider of that method, whose type may differ from it only + // in a covariant return type. Identify every method of an override chain by + // the type of the method that introduced the virtual function, so that calls + // through a pointer to any class of the hierarchy match the overriders they + // can reach. Methods that override several virtual functions with different + // (covariant) return types are identified after the first one; the thunks + // that fill the vtable slots of the others are identified after those (see + // CodeGenVTables::maybeEmitThunk). + if (const auto *MD = dyn_cast<CXXMethodDecl>(FD); MD && MD->isVirtual()) + return GetRootOverriddenMethod(MD)->getType(); + return FD->getType(); +} + void CodeGenModule::createIndirectFunctionTypeMD(const FunctionDecl *FD, llvm::Function *F) { // All functions which are not internal linkage could be indirect targets. @@ -3646,7 +3669,7 @@ void CodeGenModule::createIndirectFunctionTypeMD(const FunctionDecl *FD, if (!HasBody || !Def) Def = FD; - QualType QT = Def->getType(); + QualType QT = GetCallGraphFunctionType(Def); 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. @@ -3664,10 +3687,12 @@ void CodeGenModule::createIndirectFunctionTypeMD(const FunctionDecl *FD, QT = ReconstructCallGraphPrototype(FNPT, ParamTypes); } - F->addMetadata( + // A function is identified by a single type identifier: replace any + // callgraph metadata attached to a previous declaration of the function. + F->setMetadata( llvm::LLVMContext::MD_callgraph, - *llvm::MDTuple::get(getLLVMContext(), - {CreateMetadataIdentifierForCallGraphType(QT)})); + llvm::MDTuple::get(getLLVMContext(), + {CreateMetadataIdentifierForCallGraphType(QT)})); } } diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index cb1f910366829..bed378e3ebe91 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1793,6 +1793,14 @@ class CodeGenModule : public CodeGenTypeCache { void createFunctionTypeMetadataForIcall(const FunctionDecl *FD, llvm::Function *F); + /// Returns the function type that identifies \p FD as an indirect call + /// target or callee in the call graph section. Virtual methods are + /// identified by the type of the method that introduced the virtual function + /// (the root of their override chain) so that calls through pointers to any + /// class of the hierarchy match the overriders they can reach, even if those + /// have a covariant return type. + QualType GetCallGraphFunctionType(const FunctionDecl *FD) const; + /// Create and attach callgraph metadata if the function is a potential /// indirect call target to support call graph section. void createIndirectFunctionTypeMD(const FunctionDecl *FD, llvm::Function *F); diff --git a/clang/test/CodeGen/call-graph-section-covariant-return.cpp b/clang/test/CodeGen/call-graph-section-covariant-return.cpp new file mode 100644 index 0000000000000..27ebac8a71b14 --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-covariant-return.cpp @@ -0,0 +1,87 @@ +// Tests that virtual calls through a pointer to any class of a hierarchy get +// the same call graph section type identifier as the overriders they can reach, +// even when the overriders have a covariant return type. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-graph-section \ +// RUN: -std=c++17 -emit-llvm -o - %s | FileCheck %s + +//////////////////////////////////////////////////////////////////////////////// +// A chain of covariant overrides: all methods are identified by the type of +// Base::clone, the method that introduced the virtual function. + +struct Base { + virtual ~Base() {} + virtual Base *clone() { return this; } +}; + +struct Mid : Base { + Mid *clone() override { return this; } +}; + +struct Derived : Mid { + Derived *clone() override { return this; } +}; + +//////////////////////////////////////////////////////////////////////////////// +// A method overriding two virtual functions with different covariant return +// types: the method is identified after the first one (A::f), and the return +// adjusting thunk that fills the vtable slot of B::f after the second one. + +struct A { + virtual A *f() { return this; } +}; +struct B { + virtual B *f() { return this; } +}; +struct C : A, B { + C *f() override { return this; } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Call sites. + +// CHECK-LABEL: define {{.*}} @_Z12call_clone_bP4Base( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[CLONE_CT:![0-9]+]] +Base *call_clone_b(Base *b) { return b->clone(); } + +// CHECK-LABEL: define {{.*}} @_Z12call_clone_mP3Mid( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[CLONE_CT]] +Mid *call_clone_m(Mid *m) { return m->clone(); } + +// CHECK-LABEL: define {{.*}} @_Z12call_clone_dP7Derived( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[CLONE_CT]] +Derived *call_clone_d(Derived *d) { return d->clone(); } + +// CHECK-LABEL: define {{.*}} @_Z8call_f_aP1A( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[A_F_CT:![0-9]+]] +A *call_f_a(A *a) { return a->f(); } + +// CHECK-LABEL: define {{.*}} @_Z8call_f_bP1B( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[B_F_CT:![0-9]+]] +B *call_f_b(B *b) { return b->f(); } + +// CHECK-LABEL: define {{.*}} @_Z8call_f_cP1C( +// CHECK: call noundef ptr %{{.*}}, !callee_type [[A_F_CT]] +C *call_f_c(C *c) { return c->f(); } + +// Instantiate the vtables (and thereby the methods and thunks below). +Base *make() { return new Derived(); } +C *make_c() { return new C(); } + +//////////////////////////////////////////////////////////////////////////////// +// Indirect call targets (emitted after the functions above). + +// CHECK-DAG: define {{.*}} @_ZN7Derived5cloneEv({{.*}} !callgraph [[CLONE:![0-9]+]] { +// CHECK-DAG: define {{.*}} @_ZN3Mid5cloneEv({{.*}} !callgraph [[CLONE]] { +// CHECK-DAG: define {{.*}} @_ZN4Base5cloneEv({{.*}} !callgraph [[CLONE]] { +// CHECK-DAG: define {{.*}} @_ZN1C1fEv({{.*}} !callgraph [[A_F:![0-9]+]] { +// CHECK-DAG: define {{.*}} @_ZTchn8_h8_N1C1fEv({{.*}} !callgraph [[B_F:![0-9]+]] { +// CHECK-DAG: define {{.*}} @_ZN1A1fEv({{.*}} !callgraph [[A_F]] { +// CHECK-DAG: define {{.*}} @_ZN1B1fEv({{.*}} !callgraph [[B_F]] { + +// CHECK-DAG: [[CLONE]] = !{!"_ZTSFP4BasevE"} +// CHECK-DAG: [[CLONE_CT]] = !{[[CLONE]]} +// CHECK-DAG: [[A_F]] = !{!"_ZTSFP1AvE"} +// CHECK-DAG: [[A_F_CT]] = !{[[A_F]]} +// CHECK-DAG: [[B_F]] = !{!"_ZTSFP1BvE"} +// CHECK-DAG: [[B_F_CT]] = !{[[B_F]]} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
