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

Reply via email to