Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
08d0dc1
[𝘀𝗽𝗿] initial version
Prabhuk Nov 20, 2024
feeeeff
Reorder commits. Fix clang codegen tests.
Prabhuk Dec 10, 2024
2ece26f
Update OB name from `type` to `callee_type`.
Prabhuk Mar 12, 2025
96c1d4e
Fix EOF newlines.
Prabhuk Mar 12, 2025
bfb4b1a
Add requested tests part 1.
Prabhuk Mar 13, 2025
5bfc9de
Update comments in tests.
Prabhuk Mar 13, 2025
ccf13f2
Updated the test as reviewers suggested.
Prabhuk Mar 13, 2025
158babd
Scoped enum. Simplify test.
Prabhuk Mar 13, 2025
fc860ab
Remove unnecessary cast.
Prabhuk Mar 13, 2025
7847eb0
Remove unnecessary asserts. Remove autos for better readability.
Prabhuk Mar 13, 2025
ba49de6
Reorder IR metadata and rename temporary var names in test.
Prabhuk Mar 13, 2025
1aba048
Add RISC-V support. Clean up test files.
Prabhuk Mar 14, 2025
c6d8515
Clean up test files.
Prabhuk Mar 15, 2025
c8154ed
Address review comments.
Prabhuk Mar 19, 2025
3e00a85
Emit callee_type metadata instead of operand bundle.
Prabhuk Apr 19, 2025
7228076
Address review comments.
Prabhuk Apr 19, 2025
2cb23b7
Rebase on top of llvm stack.
Prabhuk Apr 23, 2025
11c0913
Address review comments.
Prabhuk Apr 23, 2025
b7fbe09
Address review comments.
Prabhuk Apr 23, 2025
3eb7a45
Address review comments.
Prabhuk Apr 23, 2025
ffa1779
Address review comments.
Prabhuk Apr 24, 2025
f10586e
Rebase on parent.
Prabhuk Apr 24, 2025
5ddaf26
Rebase on parent.
Prabhuk Apr 24, 2025
346e5b3
Rebase on parent.
Prabhuk Apr 28, 2025
2d30d64
Rebase on parent.
Prabhuk Apr 29, 2025
bdc76a9
Rebase on parent.
Prabhuk May 1, 2025
e5157f6
Rebase on parent.
Prabhuk May 5, 2025
02b2b3f
Rebase on parent change.
Prabhuk May 10, 2025
67eab8a
Rebase on parent changes.
Prabhuk May 13, 2025
c183666
Rebase on parent.
Prabhuk May 13, 2025
083270f
Rebase on parent
Prabhuk May 14, 2025
fac07fd
Rebase on main.
Prabhuk May 14, 2025
e193a40
Rebase on parent.
Prabhuk May 27, 2025
599b585
Rebase change.
Prabhuk May 27, 2025
e41d689
Rebase.
Prabhuk Jun 11, 2025
397fd64
Rebase on parent
Prabhuk Jun 11, 2025
842f976
Rebase on top of main.
Prabhuk Jul 10, 2025
13c0ffa
Rebase on parent.
Prabhuk Jul 18, 2025
8ee6932
Rebase on parent
Prabhuk Jul 18, 2025
e179dc9
Rebase on top of parent change.
Prabhuk Jul 18, 2025
cb81b8a
Rebase on parent.
Prabhuk Jul 18, 2025
ad6905d
Rebase on parent.
Prabhuk Jul 18, 2025
d892b83
Address review comments.
Prabhuk Jul 18, 2025
fcb1497
Rebase on parent.
Prabhuk Jul 21, 2025
dfb1dc4
Simplify MD exists check.
Prabhuk Jul 21, 2025
b902d6e
Rebase.
Prabhuk Jul 22, 2025
7c4b302
Rebase on parent change.
Prabhuk Jul 23, 2025
aa0c1d1
Rebase on main.
Prabhuk Jul 23, 2025
985522c
Rebase.
Prabhuk Jul 23, 2025
0382f0f
Rebase on parent.
Prabhuk Jul 28, 2025
e4c653f
Rebase on llvm changes.
Prabhuk Jul 28, 2025
6195f97
Rebase on main.
Prabhuk Jul 30, 2025
6b8eb94
Rebase on parent.
Prabhuk Jul 31, 2025
3788ce7
Rebase on parent.
Prabhuk Jul 31, 2025
0ca9184
Rebase on main.
Prabhuk Jul 31, 2025
1f16b6a
Make Driver flag experimental.
Prabhuk Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5076,6 +5076,11 @@ static unsigned getMaxVectorWidth(const llvm::Type *Ty) {
return MaxVectorWidth;
}

static bool isCXXDeclType(const FunctionDecl *FD) {
return isa<CXXConstructorDecl>(FD) || isa<CXXMethodDecl>(FD) ||
isa<CXXDestructorDecl>(FD);
}

RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
const CGCallee &Callee,
ReturnValueSlot ReturnValue,
Expand Down Expand Up @@ -5763,6 +5768,25 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
AllocAlignAttrEmitter AllocAlignAttrEmitter(*this, TargetDecl, CallArgs);
Attrs = AllocAlignAttrEmitter.TryEmitAsCallSiteAttribute(Attrs);

if (CGM.getCodeGenOpts().CallGraphSection) {
assert((TargetDecl && TargetDecl->getFunctionType() ||
Callee.getAbstractInfo().getCalleeFunctionProtoType()) &&
"cannot find callsite type");
QualType CST;
if (TargetDecl && TargetDecl->getFunctionType())
CST = QualType(TargetDecl->getFunctionType(), 0);
else if (const auto *FPT =
Callee.getAbstractInfo().getCalleeFunctionProtoType())
CST = QualType(FPT, 0);

if (!CST.isNull()) {
auto *TypeIdMD = CGM.CreateMetadataIdentifierGeneralized(CST);
auto *TypeIdMDVal =
llvm::MetadataAsValue::get(getLLVMContext(), TypeIdMD);
BundleList.emplace_back("callee_type", TypeIdMDVal);
}
}

// Emit the actual call/invoke instruction.
llvm::CallBase *CI;
if (!InvokeDest) {
Expand All @@ -5777,8 +5801,18 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
CI->getCalledFunction()->getName().starts_with("_Z4sqrt")) {
SetSqrtFPAccuracy(CI);
}
if (callOrInvoke)
if (callOrInvoke) {
*callOrInvoke = CI;
if (CGM.getCodeGenOpts().CallGraphSection) {
// Set type identifier metadata of indirect calls for call graph section.
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(TargetDecl)) {
// Type id metadata is set only for C/C++ contexts.
if (isCXXDeclType(FD)) {
CGM.CreateFunctionTypeMetadataForIcall(FD->getType(), *callOrInvoke);
}
}
}
}

// If this is within a function that has the guard(nocf) attribute and is an
// indirect call, add the "guard_nocf" attribute to this call to indicate that
Expand Down
32 changes: 28 additions & 4 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2647,8 +2647,9 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D,

// In the cross-dso CFI mode with canonical jump tables, we want !type
// attributes on definitions only.
if (CodeGenOpts.SanitizeCfiCrossDso &&
CodeGenOpts.SanitizeCfiCanonicalJumpTables) {
if ((CodeGenOpts.SanitizeCfiCrossDso &&
CodeGenOpts.SanitizeCfiCanonicalJumpTables) ||
CodeGenOpts.CallGraphSection) {
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
// Skip available_externally functions. They won't be codegen'ed in the
// current module anyway.
Expand Down Expand Up @@ -2862,7 +2863,17 @@ static void setLinkageForGV(llvm::GlobalValue *GV, const NamedDecl *ND) {

void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD,
llvm::Function *F) {
// Only if we are checking indirect calls.
bool EmittedMDIdGeneralized = false;
if (CodeGenOpts.CallGraphSection &&
(!F->hasLocalLinkage() ||
F->getFunction().hasAddressTaken(nullptr, /*IgnoreCallbackUses=*/true,
/*IgnoreAssumeLikeCalls=*/true,
/*IgnoreLLVMUsed=*/false))) {
F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType()));
EmittedMDIdGeneralized = true;
}

// Add additional metadata only if we are checking indirect calls with CFI.
if (!LangOpts.Sanitize.has(SanitizerKind::CFIICall))
return;

Expand All @@ -2873,14 +2884,27 @@ void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD,

llvm::Metadata *MD = CreateMetadataIdentifierForType(FD->getType());
F->addTypeMetadata(0, MD);
F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType()));
// Add the generalized identifier if not added already.
if (!EmittedMDIdGeneralized)
F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType()));

// Emit a hash-based bit set entry for cross-DSO calls.
if (CodeGenOpts.SanitizeCfiCrossDso)
if (auto CrossDsoTypeId = CreateCrossDsoCfiTypeId(MD))
F->addTypeMetadata(0, llvm::ConstantAsMetadata::get(CrossDsoTypeId));
}

void CodeGenModule::CreateFunctionTypeMetadataForIcall(const QualType &QT,
llvm::CallBase *CB) {
// Only if needed for call graph section and only for indirect calls.
if (!CodeGenOpts.CallGraphSection || !CB || !CB->isIndirectCall())
return;

auto *MD = CreateMetadataIdentifierGeneralized(QT);
auto *MDN = llvm::MDNode::get(getLLVMContext(), MD);
CB->setMetadata(llvm::LLVMContext::MD_type, MDN);
}

void CodeGenModule::setKCFIType(const FunctionDecl *FD, llvm::Function *F) {
llvm::LLVMContext &Ctx = F->getContext();
llvm::MDBuilder MDB(Ctx);
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,10 @@ class CodeGenModule : public CodeGenTypeCache {
void CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD,
llvm::Function *F);

/// Create and attach type metadata to the given call.
void CreateFunctionTypeMetadataForIcall(const QualType &QT,
llvm::CallBase *CB);

/// Set type metadata to the given function.
void setKCFIType(const FunctionDecl *FD, llvm::Function *F);

Expand Down
110 changes: 110 additions & 0 deletions clang/test/CodeGen/call-graph-section-1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Tests that we assign appropriate identifiers to indirect calls and targets
// specifically for C++ class and instance methods.

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \
// RUN: -emit-llvm -o %t %s
// RUN: FileCheck --check-prefix=FT %s < %t
// RUN: FileCheck --check-prefix=CST %s < %t

////////////////////////////////////////////////////////////////////////////////
// Class definitions (check for indirect target metadata)

class Cls1 {
public:
// FT-DAG: define {{.*}} ptr @_ZN4Cls18receiverEPcPf({{.*}} !type [[F_TCLS1RECEIVER:![0-9]+]]
static int *receiver(char *a, float *b) { return 0; }
};

class Cls2 {
public:
int *(*fp)(char *, float *);

// FT-DAG: define {{.*}} i32 @_ZN4Cls22f1Ecfd({{.*}} !type [[F_TCLS2F1:![0-9]+]]
int f1(char a, float b, double c) { return 0; }

// FT-DAG: define {{.*}} ptr @_ZN4Cls22f2EPcPfPd({{.*}} !type [[F_TCLS2F2:![0-9]+]]
int *f2(char *a, float *b, double *c) { return 0; }

// FT-DAG: define {{.*}} void @_ZN4Cls22f3E4Cls1({{.*}} !type [[F_TCLS2F3F4:![0-9]+]]
void f3(Cls1 a) {}

// FT-DAG: define {{.*}} void @_ZN4Cls22f4E4Cls1({{.*}} !type [[F_TCLS2F3F4]]
void f4(const Cls1 a) {}

// FT-DAG: define {{.*}} void @_ZN4Cls22f5EP4Cls1({{.*}} !type [[F_TCLS2F5:![0-9]+]]
void f5(Cls1 *a) {}

// FT-DAG: define {{.*}} void @_ZN4Cls22f6EPK4Cls1({{.*}} !type [[F_TCLS2F6:![0-9]+]]
void f6(const Cls1 *a) {}

// FT-DAG: define {{.*}} void @_ZN4Cls22f7ER4Cls1({{.*}} !type [[F_TCLS2F7:![0-9]+]]
void f7(Cls1 &a) {}

// FT-DAG: define {{.*}} void @_ZN4Cls22f8ERK4Cls1({{.*}} !type [[F_TCLS2F8:![0-9]+]]
void f8(const Cls1 &a) {}

// FT-DAG: define {{.*}} void @_ZNK4Cls22f9Ev({{.*}} !type [[F_TCLS2F9:![0-9]+]]
void f9() const {}
};

// FT-DAG: [[F_TCLS1RECEIVER]] = !{i64 0, !"_ZTSFPvS_S_E.generalized"}
// FT-DAG: [[F_TCLS2F2]] = !{i64 0, !"_ZTSFPvS_S_S_E.generalized"}
// FT-DAG: [[F_TCLS2F1]] = !{i64 0, !"_ZTSFicfdE.generalized"}
// FT-DAG: [[F_TCLS2F3F4]] = !{i64 0, !"_ZTSFv4Cls1E.generalized"}
// FT-DAG: [[F_TCLS2F5]] = !{i64 0, !"_ZTSFvPvE.generalized"}
// FT-DAG: [[F_TCLS2F6]] = !{i64 0, !"_ZTSFvPKvE.generalized"}
// FT-DAG: [[F_TCLS2F7]] = !{i64 0, !"_ZTSFvR4Cls1E.generalized"}
// FT-DAG: [[F_TCLS2F8]] = !{i64 0, !"_ZTSFvRK4Cls1E.generalized"}
// FT-DAG: [[F_TCLS2F9]] = !{i64 0, !"_ZTSKFvvE.generalized"}

////////////////////////////////////////////////////////////////////////////////
// Callsites (check for indirect callsite operand bundles)

// CST-LABEL: define {{.*}} @_Z3foov
void foo() {
Cls2 ObjCls2;
ObjCls2.fp = &Cls1::receiver;

// CST: call noundef ptr %{{.*}} [ "callee_type"(metadata !"_ZTSFPvS_S_E.generalized") ]
ObjCls2.fp(0, 0);

auto fp_f1 = &Cls2::f1;
auto fp_f2 = &Cls2::f2;
auto fp_f3 = &Cls2::f3;
auto fp_f4 = &Cls2::f4;
auto fp_f5 = &Cls2::f5;
auto fp_f6 = &Cls2::f6;
auto fp_f7 = &Cls2::f7;
auto fp_f8 = &Cls2::f8;
auto fp_f9 = &Cls2::f9;

Cls2 *ObjCls2Ptr = &ObjCls2;
Cls1 Cls1Param;

// CST: call noundef i32 %{{.*}} [ "callee_type"(metadata !"_ZTSFicfdE.generalized") ]
(ObjCls2Ptr->*fp_f1)(0, 0, 0);

// CST: call noundef ptr %{{.*}} [ "callee_type"(metadata !"_ZTSFPvS_S_S_E.generalized") ]
(ObjCls2Ptr->*fp_f2)(0, 0, 0);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFv4Cls1E.generalized") ]
(ObjCls2Ptr->*fp_f3)(Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFv4Cls1E.generalized") ]
(ObjCls2Ptr->*fp_f4)(Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvPvE.generalized") ]
(ObjCls2Ptr->*fp_f5)(&Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvPKvE.generalized") ]
(ObjCls2Ptr->*fp_f6)(&Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvR4Cls1E.generalized") ]
(ObjCls2Ptr->*fp_f7)(Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvRK4Cls1E.generalized") ]
(ObjCls2Ptr->*fp_f8)(Cls1Param);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSKFvvE.generalized") ]
(ObjCls2Ptr->*fp_f9)();
}
95 changes: 95 additions & 0 deletions clang/test/CodeGen/call-graph-section-2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Tests that we assign appropriate identifiers to indirect calls and targets
// specifically for C++ templates.

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \
// RUN: -emit-llvm -o %t %s
// RUN: FileCheck --check-prefix=FT %s < %t
// RUN: FileCheck --check-prefix=CST %s < %t
// RUN: FileCheck --check-prefix=CHECK %s < %t

////////////////////////////////////////////////////////////////////////////////
// Class definitions and template classes (check for indirect target metadata)

class Cls1 {};

// Cls2 is instantiated with T=Cls1 in foo(). Following checks are for this
// instantiation.
template <class T>
class Cls2 {
public:
// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f1Ev({{.*}} !type [[F_TCLS2F1:![0-9]+]]
void f1() {}

// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f2ES0_({{.*}} !type [[F_TCLS2F2:![0-9]+]]
void f2(T a) {}

// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f3EPS0_({{.*}} !type [[F_TCLS2F3:![0-9]+]]
void f3(T *a) {}

// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f4EPKS0_({{.*}} !type [[F_TCLS2F4:![0-9]+]]
void f4(const T *a) {}

// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f5ERS0_({{.*}} !type [[F_TCLS2F5:![0-9]+]]
void f5(T &a) {}

// FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f6ERKS0_({{.*}} !type [[F_TCLS2F6:![0-9]+]]
void f6(const T &a) {}

// Mixed type function pointer member
T *(*fp)(T a, T *b, const T *c, T &d, const T &e);
};

// FT-DAG: [[F_TCLS2F1]] = !{i64 0, !"_ZTSFvvE.generalized"}
// FT-DAG: [[F_TCLS2F2]] = !{i64 0, !"_ZTSFv4Cls1E.generalized"}
// FT-DAG: [[F_TCLS2F3]] = !{i64 0, !"_ZTSFvPvE.generalized"}
// FT-DAG: [[F_TCLS2F4]] = !{i64 0, !"_ZTSFvPKvE.generalized"}
// FT-DAG: [[F_TCLS2F5]] = !{i64 0, !"_ZTSFvR4Cls1E.generalized"}
// FT-DAG: [[F_TCLS2F6]] = !{i64 0, !"_ZTSFvRK4Cls1E.generalized"}

////////////////////////////////////////////////////////////////////////////////
// Callsites (check for indirect callsite operand bundles)

template <class T>
T *T_func(T a, T *b, const T *c, T &d, const T &e) { return b; }

// CST-LABEL: define {{.*}} @_Z3foov
void foo() {
// Methods for Cls2<Cls1> is checked above within the template description.
Cls2<Cls1> Obj;

// CHECK-DAG: define {{.*}} @_Z6T_funcI4Cls1EPT_S1_S2_PKS1_RS1_RS3_({{.*}} !type [[F_TFUNC_CLS1:![0-9]+]]
// CHECK-DAG: [[F_TFUNC_CLS1]] = !{i64 0, !"_ZTSFPv4Cls1S_PKvRS0_RKS0_E.generalized"}
Obj.fp = T_func<Cls1>;
Cls1 Cls1Obj;

// CST: call noundef ptr %{{.*}} [ "callee_type"(metadata !"_ZTSFPv4Cls1S_PKvRS0_RKS0_E.generalized") ]
Obj.fp(Cls1Obj, &Cls1Obj, &Cls1Obj, Cls1Obj, Cls1Obj);

// Make indirect calls to Cls2's member methods
auto fp_f1 = &Cls2<Cls1>::f1;
auto fp_f2 = &Cls2<Cls1>::f2;
auto fp_f3 = &Cls2<Cls1>::f3;
auto fp_f4 = &Cls2<Cls1>::f4;
auto fp_f5 = &Cls2<Cls1>::f5;
auto fp_f6 = &Cls2<Cls1>::f6;

auto *Obj2Ptr = &Obj;

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvvE.generalized") ]
(Obj2Ptr->*fp_f1)();

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFv4Cls1E.generalized") ]
(Obj2Ptr->*fp_f2)(Cls1Obj);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvPvE.generalized") ]
(Obj2Ptr->*fp_f3)(&Cls1Obj);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvPKvE.generalized") ]
(Obj2Ptr->*fp_f4)(&Cls1Obj);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvR4Cls1E.generalized") ]
(Obj2Ptr->*fp_f5)(Cls1Obj);

// CST: call void %{{.*}} [ "callee_type"(metadata !"_ZTSFvRK4Cls1E.generalized") ]
(Obj2Ptr->*fp_f6)(Cls1Obj);
}
52 changes: 52 additions & 0 deletions clang/test/CodeGen/call-graph-section-3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Tests that we assign appropriate identifiers to indirect calls and targets
// specifically for virtual methods.

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \
// RUN: -emit-llvm -o %t %s
// RUN: FileCheck --check-prefix=FT %s < %t
// RUN: FileCheck --check-prefix=CST %s < %t

////////////////////////////////////////////////////////////////////////////////
// Class definitions (check for indirect target metadata)

class Base {
public:
// FT-DAG: define {{.*}} @_ZN4Base2vfEPc({{.*}} !type [[F_TVF:![0-9]+]]
virtual int vf(char *a) { return 0; };
};

class Derived : public Base {
public:
// FT-DAG: define {{.*}} @_ZN7Derived2vfEPc({{.*}} !type [[F_TVF]]
int vf(char *a) override { return 1; };
};

// FT-DAG: [[F_TVF]] = !{i64 0, !"_ZTSFiPvE.generalized"}

////////////////////////////////////////////////////////////////////////////////
// Callsites (check for indirect callsite operand bundles)

// CST-LABEL: define {{.*}} @_Z3foov
void foo() {
auto B = Base();
auto D = Derived();

Base *Bptr = &B;
Base *BptrToD = &D;
Derived *Dptr = &D;

auto FpBaseVf = &Base::vf;
auto FpDerivedVf = &Derived::vf;

// CST: call noundef i32 %{{.*}} [ "callee_type"(metadata !"_ZTSFiPvE.generalized") ]
(Bptr->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}} [ "callee_type"(metadata !"_ZTSFiPvE.generalized") ]
(BptrToD->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}} [ "callee_type"(metadata !"_ZTSFiPvE.generalized") ]
(Dptr->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}} [ "callee_type"(metadata !"_ZTSFiPvE.generalized") ]
(Dptr->*FpDerivedVf)(0);
}
Loading
Loading