https://github.com/Prabhuk updated 
https://github.com/llvm/llvm-project/pull/230732

>From 24ba4305c3f4685401e30b4727aa6e6ff2917442 Mon Sep 17 00:00:00 2001
From: prabhukr <[email protected]>
Date: Sat, 10 Oct 2026 10:44:05 -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:

- Attach `!callee_type` metadata to indirect calls regardless of whether
  the caller of `EmitCall` asked for the emitted call instruction. Virtual
  destructor calls (`delete b;`) and other indirect calls emitted without
  a `callOrInvoke` out parameter did not get any callee type.
- 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                  | 136 +++++++++---------
 clang/lib/CodeGen/CGVTables.cpp               |   8 ++
 clang/lib/CodeGen/CodeGenModule.cpp           |  33 ++++-
 clang/lib/CodeGen/CodeGenModule.h             |   8 ++
 .../call-graph-section-covariant-return.cpp   |  96 +++++++++++++
 5 files changed, 209 insertions(+), 72 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..d417c681755ae 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6402,78 +6402,78 @@ 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 (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())
+      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/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..a737f6596cb7c
--- /dev/null
+++ b/clang/test/CodeGen/call-graph-section-covariant-return.cpp
@@ -0,0 +1,96 @@
+// 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, and that virtual
+// destructor calls get callee_type metadata.
+
+// 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(); }
+
+// Virtual destructor calls are indirect calls too.
+// CHECK-LABEL: define {{.*}} @_Z9call_dtorP4Base(
+// CHECK: call void %{{.*}}, !callee_type [[DTOR_CT:![0-9]+]]
+void call_dtor(Base *b) { delete b; }
+
+// 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 {{.*}} @_ZN7DerivedD0Ev({{.*}} !callgraph 
[[DTOR:![0-9]+]] {
+// 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: [[DTOR]] = !{!"_ZTSFvvE"}
+// CHECK-DAG: [[DTOR_CT]] = !{[[DTOR]]}
+// 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