[llvm-branch-commits] [clang] [clang][CodeGen] Emit callee_type metadata for all indirect calls (PR #230824)
Prabhu Rajasekaran via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Oct 10 11:57:02 PDT 2026
https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/230824
>From d987f8b048ac44f11b08b27d92121ae3e8880ba1 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Sat, 10 Oct 2026 11:56:47 -0700
Subject: [PATCH] [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.
Stacked on #230831.
Assisted-by: Claude
---
clang/lib/CodeGen/CGCall.cpp | 138 +++++++++---------
.../call-graph-section-virtual-dtor.cpp | 32 ++++
2 files changed, 100 insertions(+), 70 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 8c8aa079b53a4..d417c681755ae 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6402,80 +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 (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);
- }
- 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/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]]}
More information about the llvm-branch-commits
mailing list