https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/230732
>From a27e88272725ca969fcc00a4825607c04446ec65 Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Sat, 10 Oct 2026 11:40:24 -0700 Subject: [PATCH] [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. 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 1f4fd55060d39..8c8aa079b53a4 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6406,7 +6406,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, *callOrInvoke = CI; 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
