https://github.com/Prabhuk created https://github.com/llvm/llvm-project/pull/230824
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 >From d30b6d0cbbda1b8a0f384d3aa0e0e3b0786f74f3 Mon Sep 17 00:00:00 2001 From: prabhukr <[email protected]> Date: Sat, 10 Oct 2026 11:23:55 -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. 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]]} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
