diff --git a/llvm/docs/CalleeTypeMetadata.rst b/llvm/docs/CalleeTypeMetadata.rst new file mode 100644 index 0000000000000..45d0657966a8c --- /dev/null +++ b/llvm/docs/CalleeTypeMetadata.rst @@ -0,0 +1,33 @@ +==================== +Callee Type Metadata +==================== + +Introduction +============ +This ``!callee_type`` metadata is introduced to support the generation of a call graph +section in the object file. The ``!callee_type`` metadata is used +to identify the types of the intended callees of indirect call instructions. The ``!callee_type`` metadata is a +list of one or more generalized ``!type`` metadata objects (See :doc:`TypeMetadata`) with each ``!type`` +metadata pointing to a callee's :ref:`type identifier `. +LLVM's `Control Flow Integrity (CFI)`_ also uses the ``!type`` metadata in its implementation. + +.. _Control Flow Integrity (CFI): https://clang.llvm.org/docs/ControlFlowIntegrity.html + +.. _calleetype-type-identifier: + +Type identifier +================ + +The type for an indirect call target is the callee's function signature. +Mapping from a type to an identifier is an ABI detail. +In the current implementation, an identifier of type T is +computed as follows: + + - Obtain the generalized mangled name for “typeinfo name for T”. + - Compute MD5 hash of the name as a string. + - Reinterpret the first 8 bytes of the hash as a little-endian 64-bit integer. + +To avoid mismatched pointer types, generalizations are applied. +Pointers in return and argument types are treated as equivalent as long as the qualifiers for the +type they point to match. For example, ``char*``, ``char**``, and ``int*`` are considered equivalent +types. However, ``char*`` and ``const char*`` are considered distinct types. diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index d2a1821efd698..0f6c71faee36f 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -8159,6 +8159,11 @@ change in the future. See :doc:`TypeMetadata`. +'``callee_type``' Metadata +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +See :doc:`CalleeTypeMetadata`. + '``associated``' Metadata ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/llvm/docs/Reference.rst b/llvm/docs/Reference.rst index cb9576b15d701..35a6f59ecbf35 100644 --- a/llvm/docs/Reference.rst +++ b/llvm/docs/Reference.rst @@ -14,6 +14,7 @@ LLVM and API reference documentation. BlockFrequencyTerminology BranchWeightMetadata Bugpoint + CalleeTypeMetadata CIBestPractices CommandGuide/index ConvergenceAndUniformity diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def index df572e8791e13..90276eae13e4b 100644 --- a/llvm/include/llvm/IR/FixedMetadataKinds.def +++ b/llvm/include/llvm/IR/FixedMetadataKinds.def @@ -53,3 +53,4 @@ LLVM_FIXED_MD_KIND(MD_DIAssignID, "DIAssignID", 38) LLVM_FIXED_MD_KIND(MD_coro_outside_frame, "coro.outside.frame", 39) LLVM_FIXED_MD_KIND(MD_mmra, "mmra", 40) LLVM_FIXED_MD_KIND(MD_noalias_addrspace, "noalias.addrspace", 41) +LLVM_FIXED_MD_KIND(MD_callee_type, "callee_type", 42) diff --git a/llvm/include/llvm/IR/Metadata.h b/llvm/include/llvm/IR/Metadata.h index 2de26c0c1f7c7..b26072960600c 100644 --- a/llvm/include/llvm/IR/Metadata.h +++ b/llvm/include/llvm/IR/Metadata.h @@ -1467,6 +1467,8 @@ class MDNode : public Metadata { const Instruction *BInstr); LLVM_ABI static MDNode *getMergedMemProfMetadata(MDNode *A, MDNode *B); LLVM_ABI static MDNode *getMergedCallsiteMetadata(MDNode *A, MDNode *B); + LLVM_ABI static MDNode *getMergedCalleeTypeMetadata(LLVMContext &Ctx, + MDNode *A, MDNode *B); }; /// Tuple of metadata. @@ -1840,6 +1842,15 @@ class NamedMDNode : public ilist_node { // Create wrappers for C Binding types (see CBindingWrapping.h). DEFINE_ISA_CONVERSION_FUNCTIONS(NamedMDNode, LLVMNamedMDNodeRef) +// Check if a given MDNode is a valid genaralized type metadata node. +inline bool hasGeneralizedMDString(const MDNode *MD) { + if (MD->getNumOperands() < 2 || !isa(MD->getOperand(1))) + return false; + return cast(MD->getOperand(1)) + ->getString() + .ends_with(".generalized"); +} + } // end namespace llvm #endif // LLVM_IR_METADATA_H diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp index f0448b06e7e82..07d7c30b402e7 100644 --- a/llvm/lib/IR/Metadata.cpp +++ b/llvm/lib/IR/Metadata.cpp @@ -1303,6 +1303,24 @@ static void addRange(SmallVectorImpl &EndPoints, EndPoints.push_back(High); } +MDNode *MDNode::getMergedCalleeTypeMetadata(LLVMContext &Ctx, MDNode *A, + MDNode *B) { + SmallVector AB; + SmallPtrSet MergedCallees; + auto AddUniqueCallees = [&AB, &MergedCallees](MDNode *N) { + if (!N) + return; + for (const MDOperand &Op : N->operands()) { + Metadata *MD = Op.get(); + if (MergedCallees.insert(MD).second) + AB.push_back(MD); + } + }; + AddUniqueCallees(A); + AddUniqueCallees(B); + return MDNode::get(Ctx, AB); +} + MDNode *MDNode::getMostGenericRange(MDNode *A, MDNode *B) { // Given two ranges, we want to compute the union of the ranges. This // is slightly complicated by having to combine the intervals and merge diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index eb747bc48a8a5..f708d3858bd63 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -531,6 +531,7 @@ class Verifier : public InstVisitor, VerifierSupport { void visitCallStackMetadata(MDNode *MD); void visitMemProfMetadata(Instruction &I, MDNode *MD); void visitCallsiteMetadata(Instruction &I, MDNode *MD); + void visitCalleeTypeMetadata(Instruction &I, MDNode *MD); void visitDIAssignIDMetadata(Instruction &I, MDNode *MD); void visitMMRAMetadata(Instruction &I, MDNode *MD); void visitAnnotationMetadata(MDNode *Annotation); @@ -5203,6 +5204,19 @@ void Verifier::visitCallsiteMetadata(Instruction &I, MDNode *MD) { visitCallStackMetadata(MD); } +void Verifier::visitCalleeTypeMetadata(Instruction &I, MDNode *MD) { + Check(isa(I), "!callee_type metadata should only exist on calls", + &I); + for (const MDOperand &Op : MD->operands()) { + Check(isa(Op.get()), + "The callee_type metadata must be a list of type metadata nodes"); + auto *TypeMD = cast(Op.get()); + Check(hasGeneralizedMDString(TypeMD), + "Only generalized type metadata can be part of the callee_type " + "metadata list"); + } +} + void Verifier::visitAnnotationMetadata(MDNode *Annotation) { Check(isa(Annotation), "annotation must be a tuple"); Check(Annotation->getNumOperands() >= 1, @@ -5480,6 +5494,9 @@ void Verifier::visitInstruction(Instruction &I) { if (MDNode *MD = I.getMetadata(LLVMContext::MD_callsite)) visitCallsiteMetadata(I, MD); + if (MDNode *MD = I.getMetadata(LLVMContext::MD_callee_type)) + visitCalleeTypeMetadata(I, MD); + if (MDNode *MD = I.getMetadata(LLVMContext::MD_DIAssignID)) visitDIAssignIDMetadata(I, MD); diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp index e521c9d7001ac..1de447409a250 100644 --- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp +++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp @@ -4239,6 +4239,13 @@ Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) { Call, Builder.CreateBitOrPointerCast(ReturnedArg, CallTy)); } + // Drop unnecessary callee_type metadata from calls that were converted + // into direct calls. + if (Call.getMetadata(LLVMContext::MD_callee_type) && !Call.isIndirectCall()) { + Call.setMetadata(LLVMContext::MD_callee_type, nullptr); + Changed = true; + } + // Drop unnecessary kcfi operand bundles from calls that were converted // into direct calls. auto Bundle = Call.getOperandBundle(LLVMContext::OB_kcfi); diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp index f5208d50c6aae..40213a748ca12 100644 --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -3351,6 +3351,7 @@ static void combineMetadata(Instruction *K, const Instruction *J, case LLVMContext::MD_mmra: case LLVMContext::MD_memprof: case LLVMContext::MD_callsite: + case LLVMContext::MD_callee_type: break; case LLVMContext::MD_align: if (!AAOnly && (DoesKMove || !K->hasMetadata(LLVMContext::MD_noundef))) @@ -3423,6 +3424,17 @@ static void combineMetadata(Instruction *K, const Instruction *J, MDNode::getMergedCallsiteMetadata(KCallSite, JCallSite)); } + // Merge callee_type metadata. + // Handle separately to support cases where only one instruction has the + // metadata. + auto *JCalleeType = J->getMetadata(LLVMContext::MD_callee_type); + auto *KCalleeType = K->getMetadata(LLVMContext::MD_callee_type); + if (!AAOnly && (JCalleeType || KCalleeType)) { + K->setMetadata(LLVMContext::MD_callee_type, + MDNode::getMergedCalleeTypeMetadata( + K->getContext(), KCalleeType, JCalleeType)); + } + // Merge prof metadata. // Handle separately to support cases where only one instruction has the // metadata. diff --git a/llvm/lib/Transforms/Utils/ValueMapper.cpp b/llvm/lib/Transforms/Utils/ValueMapper.cpp index 7ba95e299c1b1..8d8a60b6918fe 100644 --- a/llvm/lib/Transforms/Utils/ValueMapper.cpp +++ b/llvm/lib/Transforms/Utils/ValueMapper.cpp @@ -987,6 +987,13 @@ void Mapper::remapInstruction(Instruction *I) { "Referenced value not in value map!"); } + // Drop callee_type metadata from calls that were remapped + // into a direct call from an indirect one. + if (auto *CB = dyn_cast(I)) { + if (CB->getMetadata(LLVMContext::MD_callee_type) && !CB->isIndirectCall()) + CB->setMetadata(LLVMContext::MD_callee_type, nullptr); + } + // Remap phi nodes' incoming blocks. if (PHINode *PN = dyn_cast(I)) { for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { diff --git a/llvm/test/Assembler/callee-type-metadata.ll b/llvm/test/Assembler/callee-type-metadata.ll new file mode 100644 index 0000000000000..9c3cfbe82fc13 --- /dev/null +++ b/llvm/test/Assembler/callee-type-metadata.ll @@ -0,0 +1,21 @@ +;; Test if the callee_type metadata attached to indirect call sites adhere to the expected format. + +; RUN: llvm-as < %s | llvm-dis | FileCheck %s +define i32 @_Z13call_indirectPFicEc(ptr %func, i8 signext %x) !type !0 { +entry: + %func.addr = alloca ptr, align 8 + %x.addr = alloca i8, align 1 + store ptr %func, ptr %func.addr, align 8 + store i8 %x, ptr %x.addr, align 1 + %fptr = load ptr, ptr %func.addr, align 8 + %x_val = load i8, ptr %x.addr, align 1 + ; CHECK: %call = call i32 %fptr(i8 signext %x_val), !callee_type !1 + %call = call i32 %fptr(i8 signext %x_val), !callee_type !1 + ret i32 %call +} + +declare !type !2 i32 @_Z3barc(i8 signext) + +!0 = !{i64 0, !"_ZTSFiPvcE.generalized"} +!1 = !{!2} +!2 = !{i64 0, !"_ZTSFicE.generalized"} diff --git a/llvm/test/Transforms/Inline/drop-callee-type-metadata.ll b/llvm/test/Transforms/Inline/drop-callee-type-metadata.ll new file mode 100644 index 0000000000000..547588089c5b0 --- /dev/null +++ b/llvm/test/Transforms/Inline/drop-callee-type-metadata.ll @@ -0,0 +1,41 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 +;; Test if the callee_type metadata is dropped when it is +;; is mapped to a direct function call from an indirect call during inlining. + +; RUN: opt -passes=inline -S < %s | FileCheck %s + +define i32 @_Z13call_indirectPFicEc(ptr %func, i8 %x) !type !0 { +; CHECK-LABEL: define i32 @_Z13call_indirectPFicEc( +; CHECK-SAME: ptr [[FUNC:%.*]], i8 [[X:%.*]]) !type [[META0:![0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call i32 [[FUNC]](i8 [[X]]), !callee_type [[META1:![0-9]+]] +; CHECK-NEXT: ret i32 [[CALL]] +; +entry: + %call = call i32 %func(i8 %x), !callee_type !1 + ret i32 %call +} + +define i32 @_Z3barv() !type !3 { +; CHECK-LABEL: define i32 @_Z3barv( +; CHECK-SAME: ) !type [[META3:![0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL_I:%.*]] = call i32 @_Z3fooc(i8 97) +; CHECK-NEXT: ret i32 [[CALL_I]] +; +entry: + %call = call i32 @_Z13call_indirectPFicEc(ptr nonnull @_Z3fooc, i8 97) + ret i32 %call +} +declare !type !2 i32 @_Z3fooc(i8 signext) + +!0 = !{i64 0, !"_ZTSFiPvcE.generalized"} +!1 = !{!2} +!2 = !{i64 0, !"_ZTSFicE.generalized"} +!3 = !{i64 0, !"_ZTSFivE.generalized"} +;. +; CHECK: [[META0]] = !{i64 0, !"_ZTSFiPvcE.generalized"} +; CHECK: [[META1]] = !{[[META2:![0-9]+]]} +; CHECK: [[META2]] = !{i64 0, !"_ZTSFicE.generalized"} +; CHECK: [[META3]] = !{i64 0, !"_ZTSFivE.generalized"} +;. diff --git a/llvm/test/Transforms/InstCombine/drop-callee-type-metadata.ll b/llvm/test/Transforms/InstCombine/drop-callee-type-metadata.ll new file mode 100644 index 0000000000000..83215f78be1b0 --- /dev/null +++ b/llvm/test/Transforms/InstCombine/drop-callee-type-metadata.ll @@ -0,0 +1,25 @@ +;; Test if the callee_type metadata is dropped when it is attached +;; to a direct function call during instcombine. + +; RUN: opt -passes=instcombine -S < %s | FileCheck %s + +define i32 @_Z3barv() !type !0 { +; CHECK-LABEL: define i32 @_Z3barv( +; CHECK-SAME: ) !type [[META0:![0-9]+]] { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call i32 @_Z3fooc(i8 97){{$}} +; CHECK-NEXT: ret i32 [[CALL]] +; +entry: + %call = call i32 @_Z3fooc(i8 97), !callee_type !1 + ret i32 %call +} + +declare !type !2 i32 @_Z3fooc(i8 signext) + +!0 = !{i64 0, !"_ZTSFivE.generalized"} +!1 = !{!2} +!2 = !{i64 0, !"_ZTSFicE.generalized"} +;. +; CHECK: [[META0]] = !{i64 0, !"_ZTSFivE.generalized"} +;. diff --git a/llvm/test/Transforms/SimplifyCFG/merge-callee-type-metadata.ll b/llvm/test/Transforms/SimplifyCFG/merge-callee-type-metadata.ll new file mode 100644 index 0000000000000..42bc9b6732176 --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/merge-callee-type-metadata.ll @@ -0,0 +1,148 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 5 +;; Test if the callee_type metadata is merged correctly. + +; RUN: opt -passes=simplifycfg -S < %s | FileCheck %s + +;; Test if the callee_type metadata is merged correctly when +;; the instructions carry differring callee_type metadata. +define ptr @_Z10test_diffb(i1 zeroext %b) { +; CHECK-LABEL: define ptr @_Z10test_diffb( +; CHECK-SAME: i1 zeroext [[B:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call ptr @_Znwm(i64 4), !callee_type [[META0:![0-9]+]] +; CHECK-NEXT: ret ptr [[CALL]] +; +entry: + br i1 %b, label %if.then, label %if.else + +if.then: ; preds = %entry + %call = call ptr @_Znwm(i64 4), !callee_type !4 + br label %if.end + +if.else: ; preds = %entry + %call1 = call ptr @_Znwm(i64 4), !callee_type !3 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ] + ret ptr %x.0 +} + +;; Test if the callee_type metadata is merged correctly when +;; the instructions carry same callee_type metadata. +define ptr @_Z10test_sameb(i1 zeroext %b) { +; CHECK-LABEL: define ptr @_Z10test_sameb( +; CHECK-SAME: i1 zeroext [[B:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call ptr @_Znwm(i64 4), !callee_type [[META3:![0-9]+]] +; CHECK-NEXT: ret ptr [[CALL]] +; +entry: + br i1 %b, label %if.then, label %if.else + +if.then: ; preds = %entry + %call = call ptr @_Znwm(i64 4), !callee_type !3 + br label %if.end + +if.else: ; preds = %entry + %call1 = call ptr @_Znwm(i64 4), !callee_type !3 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ] + ret ptr %x.0 +} + +;; Test if the callee_type metadata is merged correctly when +;; only the left instruction has callee_type metadata. +define ptr @_Z10test_leftb(i1 zeroext %b) { +; CHECK-LABEL: define ptr @_Z10test_leftb( +; CHECK-SAME: i1 zeroext [[B:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call ptr @_Znwm(i64 4), !callee_type [[META4:![0-9]+]] +; CHECK-NEXT: ret ptr [[CALL]] +; +entry: + br i1 %b, label %if.then, label %if.else + +if.then: ; preds = %entry + %call = call ptr @_Znwm(i64 4), !callee_type !4 + br label %if.end + +if.else: ; preds = %entry + %call1 = call ptr @_Znwm(i64 4) + br label %if.end + +if.end: ; preds = %if.else, %if.then + %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ] + ret ptr %x.0 +} + +;; Test if the callee_type metadata is merged correctly when +;; only the right instruction has callee_type metadata. +define ptr @_Z10test_rightb(i1 zeroext %b) { +; CHECK-LABEL: define ptr @_Z10test_rightb( +; CHECK-SAME: i1 zeroext [[B:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call ptr @_Znwm(i64 4), !callee_type [[META3]] +; CHECK-NEXT: ret ptr [[CALL]] +; +entry: + br i1 %b, label %if.then, label %if.else + +if.then: ; preds = %entry + %call = call ptr @_Znwm(i64 4) + br label %if.end + +if.else: ; preds = %entry + %call1 = call ptr @_Znwm(i64 4), !callee_type !3 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ] + ret ptr %x.0 +} + +;; Test if the callee_type metadata is merged correctly when +;; each of the callee_type metadata are lists. +define ptr @_Z10test_listb(i1 zeroext %b) { +; CHECK-LABEL: define ptr @_Z10test_listb( +; CHECK-SAME: i1 zeroext [[B:%.*]]) { +; CHECK-NEXT: [[ENTRY:.*:]] +; CHECK-NEXT: [[CALL:%.*]] = call ptr @_Znwm(i64 4), !callee_type [[META5:![0-9]+]] +; CHECK-NEXT: ret ptr [[CALL]] +; +entry: + br i1 %b, label %if.then, label %if.else + +if.then: ; preds = %entry + %call = call ptr @_Znwm(i64 4), !callee_type !6 + br label %if.end + +if.else: ; preds = %entry + %call1 = call ptr @_Znwm(i64 4), !callee_type !5 + br label %if.end + +if.end: ; preds = %if.else, %if.then + %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ] + ret ptr %x.0 +} + +declare ptr @_Znwm(i64) + +!0 = !{i64 0, !"callee_type0.generalized"} +!1 = !{i64 0, !"callee_type1.generalized"} +!2 = !{i64 0, !"callee_type2.generalized"} +!3 = !{!0} +!4 = !{!2} +!5 = !{!1, !2} +!6 = !{!0, !2} +;. +; CHECK: [[META0]] = !{[[META1:![0-9]+]], [[META2:![0-9]+]]} +; CHECK: [[META1]] = !{i64 0, !"callee_type2.generalized"} +; CHECK: [[META2]] = !{i64 0, !"callee_type0.generalized"} +; CHECK: [[META3]] = !{[[META2]]} +; CHECK: [[META4]] = !{[[META1]]} +; CHECK: [[META5]] = !{[[META2]], [[META1]], [[META6:![0-9]+]]} +; CHECK: [[META6]] = !{i64 0, !"callee_type1.generalized"} +;. diff --git a/llvm/test/Verifier/callee-type-metadata.ll b/llvm/test/Verifier/callee-type-metadata.ll new file mode 100644 index 0000000000000..694daab14c784 --- /dev/null +++ b/llvm/test/Verifier/callee-type-metadata.ll @@ -0,0 +1,23 @@ +;; Test if the callee_type metadata attached to indirect call sites adhere to the expected format. + +; RUN: not llvm-as -disable-output < %s 2>&1 | FileCheck %s +define i32 @_Z13call_indirectPFicEc(ptr %func, i8 signext %x) !type !0 { +entry: + %func.addr = alloca ptr, align 8 + %x.addr = alloca i8, align 1 + store ptr %func, ptr %func.addr, align 8 + store i8 %x, ptr %x.addr, align 1 + %fptr = load ptr, ptr %func.addr, align 8 + %x_val = load i8, ptr %x.addr, align 1 + ;; callee_type metdata is a type metadata instead of a list of type metadata nodes. + ; CHECK: The callee_type metadata must be a list of type metadata nodes + %call = call i32 %fptr(i8 signext %x_val), !callee_type !0 + ;; callee_type metdata must be a list of "generalized" type metadata. + ; CHECK: Only generalized type metadata can be part of the callee_type metadata list + %call2 = call i32 %fptr(i8 signext %x_val), !callee_type !2 + ret i32 %call +} + +!0 = !{i64 0, !"_ZTSFiPvcE.generalized"} +!1 = !{i64 0, !"_ZTSFicE"} +!2 = !{!1}