Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 17 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5912,8 +5912,24 @@ 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
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())
CGM.createCalleeTypeMetadataForIcall(CST, *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
38 changes: 34 additions & 4 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2775,8 +2775,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 @@ -2988,9 +2989,21 @@ static void setLinkageForGV(llvm::GlobalValue *GV, const NamedDecl *ND) {
GV->setLinkage(llvm::GlobalValue::ExternalWeakLinkage);
}

static bool hasExistingGeneralizedTypeMD(llvm::Function *F) {
llvm::MDNode *MD = F->getMetadata(llvm::LLVMContext::MD_type);
return MD && MD->hasGeneralizedMDString();
}

void CodeGenModule::createFunctionTypeMetadataForIcall(const FunctionDecl *FD,
llvm::Function *F) {
// Only if we are checking indirect calls.
if (CodeGenOpts.CallGraphSection && !hasExistingGeneralizedTypeMD(F) &&
(!F->hasLocalLinkage() ||
F->getFunction().hasAddressTaken(nullptr, /*IgnoreCallbackUses=*/true,
/*IgnoreAssumeLikeCalls=*/true,
/*IgnoreLLVMUsed=*/false)))
F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType()));
Comment on lines +3000 to +3004
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relying on the IR function properties feels wrong here, can you detect whether this is indirect from the source context? I'm also not sure IgnoreCallbackUses is correct here (IgnoreAssumeLikeCalls is also potentially questionable)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this function just emit the type metadata if the function could be used as an indirect call target? The function name doesn't really communicate that though (at least to me).

Based on that reading, I'd say limiting it to any non-local function (e.g. could be addr taken in another TU) and any local function that has its address taken makes sense for the call-graph use-case.

Or was there some other aspect that concerns you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also agree about the IgnoreCallbacks and IgnoreAssumeLIkeCalls.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If clang is emitting the initial IR, then won't any IR based use query be unreliable? Later uses could yet to be determined. But also it shouldn't be fundamentally problematic to be conservatively correct and emit the annotation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, yeah, I guess that would be true, and you're right that just always doing it would be correct. Its also closer to what's done for CFI, so we should probably just do that to start, and we can try to limit the amount of type metadata later if we find its unnecessary/safe to do so.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this still needs to be addressed...


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

Expand All @@ -3001,14 +3014,31 @@ 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 (!hasExistingGeneralizedTypeMD(F))
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::createCalleeTypeMetadataForIcall(const QualType &QT,
llvm::CallBase *CB) {
// Only if needed for call graph section and only for indirect calls.
if (!CodeGenOpts.CallGraphSection || !CB->isIndirectCall())
return;

llvm::Metadata *TypeIdMD = CreateMetadataIdentifierGeneralized(QT);
llvm::MDTuple *TypeTuple = llvm::MDTuple::get(
getLLVMContext(), {llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
llvm::Type::getInt64Ty(getLLVMContext()), 0)),
TypeIdMD});
llvm::MDTuple *MDN = llvm::MDNode::get(getLLVMContext(), {TypeTuple});
CB->setMetadata(llvm::LLVMContext::MD_callee_type, MDN);
}

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

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

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

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

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-graph-section \
// RUN: -emit-llvm -o %t %s
// RUN: FileCheck --check-prefix=FT %s < %t
// RUN: FileCheck --check-prefix=CST %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;

Obj.fp = T_func<Cls1>;
Cls1 Cls1Obj;

// CST: call noundef ptr %{{.*}}, !callee_type [[F_TFUNC_CLS1_CT:![0-9]+]]
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 [[F_TCLS2F1_CT:![0-9]+]]
(Obj2Ptr->*fp_f1)();

// CST: call void %{{.*}}, !callee_type [[F_TCLS2F2_CT:![0-9]+]]
(Obj2Ptr->*fp_f2)(Cls1Obj);

// CST: call void %{{.*}}, !callee_type [[F_TCLS2F3_CT:![0-9]+]]
(Obj2Ptr->*fp_f3)(&Cls1Obj);

// CST: call void %{{.*}}, !callee_type [[F_TCLS2F4_CT:![0-9]+]]
(Obj2Ptr->*fp_f4)(&Cls1Obj);

// CST: call void %{{.*}}, !callee_type [[F_TCLS2F5_CT:![0-9]+]]
(Obj2Ptr->*fp_f5)(Cls1Obj);

// CST: call void %{{.*}}, !callee_type [[F_TCLS2F6_CT:![0-9]+]]
(Obj2Ptr->*fp_f6)(Cls1Obj);
}

// CST: define {{.*}} @_Z6T_funcI4Cls1EPT_S1_S2_PKS1_RS1_RS3_({{.*}} !type [[F_TFUNC_CLS1:![0-9]+]]
// CST-DAG: [[F_TFUNC_CLS1_CT]] = !{[[F_TFUNC_CLS1:![0-9]+]]}
// CST-DAG: [[F_TFUNC_CLS1]] = !{i64 0, !"_ZTSFPv4Cls1S_PKvRS0_RKS0_E.generalized"}

// CST-DAG: [[F_TCLS2F1_CT]] = !{[[F_TCLS2F1:![0-9]+]]}
// CST-DAG: [[F_TCLS2F1]] = !{i64 0, !"_ZTSFvvE.generalized"}

// CST-DAG: [[F_TCLS2F2_CT]] = !{[[F_TCLS2F2:![0-9]+]]}
// CST-DAG: [[F_TCLS2F2]] = !{i64 0, !"_ZTSFv4Cls1E.generalized"}

// CST-DAG: [[F_TCLS2F3_CT]] = !{[[F_TCLS2F3:![0-9]+]]}
// CST-DAG: [[F_TCLS2F3]] = !{i64 0, !"_ZTSFvPvE.generalized"}

// CST-DAG: [[F_TCLS2F4_CT]] = !{[[F_TCLS2F4:![0-9]+]]}
// CST-DAG: [[F_TCLS2F4]] = !{i64 0, !"_ZTSFvPKvE.generalized"}

// CST-DAG: [[F_TCLS2F5_CT]] = !{[[F_TCLS2F5:![0-9]+]]}
// CST-DAG: [[F_TCLS2F5]] = !{i64 0, !"_ZTSFvR4Cls1E.generalized"}

// CST-DAG: [[F_TCLS2F6_CT]] = !{[[F_TCLS2F6:![0-9]+]]}
// CST-DAG: [[F_TCLS2F6]] = !{i64 0, !"_ZTSFvRK4Cls1E.generalized"}
55 changes: 55 additions & 0 deletions clang/test/CodeGen/call-graph-section-virtual-methods.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Tests that we assign appropriate identifiers to indirect calls and targets
// specifically for virtual methods.

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-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 [[F_TVF_CT:![0-9]+]]
(Bptr->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}}, !callee_type [[F_TVF_CT:![0-9]+]]
(BptrToD->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}}, !callee_type [[F_TVF_CT:![0-9]+]]
(Dptr->*FpBaseVf)(0);

// CST: call noundef i32 %{{.*}}, !callee_type [[F_TVF_CT:![0-9]+]]
(Dptr->*FpDerivedVf)(0);
}

// CST-DAG: [[F_TVF_CT]] = !{[[F_TVF:![0-9]+]]}
// CST-DAG: [[F_TVF]] = !{i64 0, !"_ZTSFiPvE.generalized"}
83 changes: 83 additions & 0 deletions clang/test/CodeGen/call-graph-section.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Tests that we assign appropriate identifiers to indirect calls and targets.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
REQUIRES: x86-registered-target

I think you need a requires line here, if you're gong to set the triple. Or this could be under CodeGen/X86. Maybe its OK because we stop at IR generation? I don't recall precisely, but there are at least some files that only emit LLVM IR and use REQUIRES in this folder, so it seems safe to do it that way.

// RUN: %clang_cc1 -triple x86_64-unknown-linux -fexperimental-call-graph-section \
// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,ITANIUM %s

// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fexperimental-call-graph-section \
// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,MS %s

// CHECK-DAG: define {{(dso_local)?}} void @foo({{.*}} !type [[F_TVOID:![0-9]+]]
void foo() {
}

// CHECK-DAG: define {{(dso_local)?}} void @bar({{.*}} !type [[F_TVOID]]
void bar() {
void (*fp)() = foo;
// CHECK: call {{.*}}, !callee_type [[F_TVOID_CT:![0-9]+]]
fp();
}

// CHECK-DAG: define {{(dso_local)?}} i32 @baz({{.*}} !type [[F_TPRIMITIVE:![0-9]+]]
int baz(char a, float b, double c) {
return 1;
}

// CHECK-DAG: define {{(dso_local)?}} ptr @qux({{.*}} !type [[F_TPTR:![0-9]+]]
int *qux(char *a, float *b, double *c) {
return 0;
}

// CHECK-DAG: define {{(dso_local)?}} void @corge({{.*}} !type [[F_TVOID]]
void corge() {
int (*fp_baz)(char, float, double) = baz;
// CHECK: call i32 {{.*}}, !callee_type [[F_TPRIMITIVE_CT:![0-9]+]]
fp_baz('a', .0f, .0);

int *(*fp_qux)(char *, float *, double *) = qux;
// CHECK: call ptr {{.*}}, !callee_type [[F_TPTR_CT:![0-9]+]]
fp_qux(0, 0, 0);
}

struct st1 {
int *(*fp)(char *, float *, double *);
};

struct st2 {
struct st1 m;
};

// CHECK-DAG: define {{(dso_local)?}} void @stparam({{.*}} !type [[F_TSTRUCT:![0-9]+]]
void stparam(struct st2 a, struct st2 *b) {}

// CHECK-DAG: define {{(dso_local)?}} void @stf({{.*}} !type [[F_TVOID]]
void stf() {
struct st1 St1;
St1.fp = qux;
// CHECK: call ptr {{.*}}, !callee_type [[F_TPTR_CT:![0-9]+]]
St1.fp(0, 0, 0);

struct st2 St2;
St2.m.fp = qux;
// CHECK: call ptr {{.*}}, !callee_type [[F_TPTR_CT:![0-9]+]]
St2.m.fp(0, 0, 0);

// CHECK: call void {{.*}}, !callee_type [[F_TSTRUCT_CT:![0-9]+]]
void (*fp_stparam)(struct st2, struct st2 *) = stparam;
fp_stparam(St2, &St2);
}

// CHECK-DAG: [[F_TVOID_CT]] = !{[[F_TVOID:![0-9]+]]}
// ITANIUM-DAG: [[F_TVOID]] = !{i64 0, !"_ZTSFvE.generalized"}
// MS-DAG: [[F_TVOID]] = !{i64 0, !"?6AX@Z.generalized"}

// CHECK-DAG: [[F_TPRIMITIVE_CT]] = !{[[F_TPRIMITIVE:![0-9]+]]}
// ITANIUM-DAG: [[F_TPRIMITIVE]] = !{i64 0, !"_ZTSFicfdE.generalized"}
// MS-DAG: [[F_TPRIMITIVE]] = !{i64 0, !"?6AHDMN@Z.generalized"}

// CHECK-DAG: [[F_TPTR_CT]] = !{[[F_TPTR:![0-9]+]]}
// ITANIUM-DAG: [[F_TPTR]] = !{i64 0, !"_ZTSFPvS_S_S_E.generalized"}
// MS-DAG: [[F_TPTR]] = !{i64 0, !"?6APEAXPEAX00@Z.generalized"}

// CHECK-DAG: [[F_TSTRUCT_CT]] = !{[[F_TSTRUCT:![0-9]+]]}
// ITANIUM-DAG: [[F_TSTRUCT]] = !{i64 0, !"_ZTSFv3st2PvE.generalized"}
// MS-DAG: [[F_TSTRUCT]] = !{i64 0, !"?6AXUst2@@PEAX@Z.generalized"}
Loading
Loading