Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -213,8 +213,29 @@ FS_COMBINED_ORIGINAL_NAME = 9, // VERSION of the summary, bumped when adding flags for instance. FS_VERSION = 10, - // The list of llvm.type.test type identifiers used by the following function. + // The list of llvm.type.test type identifiers used by the following function + // that are used other than by an llvm.assume. + // [n x typeid] FS_TYPE_TESTS = 11, + // The list of virtual calls made by this function using + // llvm.assume(llvm.type.test) intrinsics that do not have constant integer + // arguments. + // [n x (typeid, offset)] + FS_TYPE_TEST_ASSUME_VCALLS = 12, + // The list of virtual calls made by this function using + // llvm.type.checked.load intrinsics that do not have constant integer + // arguments. + // [n x (typeid, offset)] + FS_TYPE_CHECKED_LOAD_VCALLS = 13, + // Identifies a virtual call made by this function using a + // llvm.assume(llvm.type.test) intrinsic with constant integer + // arguments. + // [typeid, offset, n x arg] + FS_TYPE_TEST_ASSUME_CONST_VCALL = 14, + // Identifies a virtual call made by this function using a + // llvm.type.checked.load intrinsic with constant integer arguments. + // [typeid, offset, n x arg] + FS_TYPE_CHECKED_LOAD_CONST_VCALL = 15, }; enum MetadataCodes { Index: llvm/include/llvm/IR/ModuleSummaryIndex.h =================================================================== --- llvm/include/llvm/IR/ModuleSummaryIndex.h +++ llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -249,6 +249,22 @@ /// call edge pair. typedef std::pair EdgeTy; + /// An "identifier" for a virtual function. This contains the type identifier + /// represented as a GUID and the offset from the address point to the virtual + /// function pointer. + struct VFuncId { + GlobalValue::GUID GUID; + uint64_t Offset; + }; + + /// A specification for a virtual function call with all constant integer + /// arguments. This is used to perform virtual constant propagation on the + /// summary. + struct ConstVCall { + VFuncId VFunc; + std::vector Args; + }; + private: /// Number of instructions (ignoring debug instructions, e.g.) computed /// during the initial compile step when the summary index is first built. @@ -257,17 +273,36 @@ /// List of call edge pairs from this function. std::vector CallGraphEdgeList; - /// List of type identifiers used by this function, represented as GUIDs. - std::vector TypeIdList; + /// List of type identifiers used by this function in llvm.type.test + /// intrinsics other than by an llvm.assume intrinsic, represented as GUIDs. + std::vector TypeTests; + + /// List of virtual calls made by this function using (respectively) + /// llvm.assume(llvm.type.test) or llvm.type.checked.load intrinsics that do + /// not have all constant integer arguments. + std::vector TypeTestAssumeVCalls, TypeCheckedLoadVCalls; + + /// List of virtual calls made by this function using (respectively) + /// llvm.assume(llvm.type.test) or llvm.type.checked.load intrinsics with + /// all constant integer arguments. + std::vector TypeTestAssumeConstVCalls, TypeCheckedLoadConstVCalls; public: /// Summary constructors. FunctionSummary(GVFlags Flags, unsigned NumInsts, std::vector Refs, std::vector CGEdges, - std::vector TypeIds) + std::vector TypeTests, + std::vector TypeTestAssumeVCalls, + std::vector TypeCheckedLoadVCalls, + std::vector TypeTestAssumeConstVCalls, + std::vector TypeCheckedLoadConstVCalls) : GlobalValueSummary(FunctionKind, Flags, std::move(Refs)), InstCount(NumInsts), CallGraphEdgeList(std::move(CGEdges)), - TypeIdList(std::move(TypeIds)) {} + TypeTests(std::move(TypeTests)), + TypeTestAssumeVCalls(std::move(TypeTestAssumeVCalls)), + TypeCheckedLoadVCalls(std::move(TypeCheckedLoadVCalls)), + TypeTestAssumeConstVCalls(std::move(TypeTestAssumeConstVCalls)), + TypeCheckedLoadConstVCalls(std::move(TypeCheckedLoadConstVCalls)) {} /// Check if this is a function summary. static bool classof(const GlobalValueSummary *GVS) { @@ -280,8 +315,67 @@ /// Return the list of pairs. ArrayRef calls() const { return CallGraphEdgeList; } - /// Returns the list of type identifiers used by this function. - ArrayRef type_tests() const { return TypeIdList; } + /// Returns the list of type identifiers used by this function in + /// llvm.type.test intrinsics other than by an llvm.assume intrinsic, + /// represented as GUIDs. + ArrayRef type_tests() const { return TypeTests; } + + /// Returns the list of virtual calls made by this function using + /// llvm.assume(llvm.type.test) intrinsics that do not have all constant + /// integer arguments. + ArrayRef type_test_assume_vcalls() const { + return TypeTestAssumeVCalls; + } + + /// Returns the list of virtual calls made by this function using + /// llvm.type.checked.load intrinsics that do not have all constant integer + /// arguments. + ArrayRef type_checked_load_vcalls() const { + return TypeCheckedLoadVCalls; + } + + /// Returns the list of virtual calls made by this function using + /// llvm.assume(llvm.type.test) intrinsics with all constant integer + /// arguments. + ArrayRef type_test_assume_const_vcalls() const { + return TypeTestAssumeConstVCalls; + } + + /// Returns the list of virtual calls made by this function using + /// llvm.type.checked.load intrinsics with all constant integer arguments. + ArrayRef type_checked_load_const_vcalls() const { + return TypeCheckedLoadConstVCalls; + } +}; + +template <> struct DenseMapInfo { + static inline FunctionSummary::VFuncId getEmptyKey() { + return {0, uint64_t(-1)}; + } + static inline FunctionSummary::VFuncId getTombstoneKey() { + return {0, uint64_t(-2)}; + } + static bool isEqual(FunctionSummary::VFuncId L, FunctionSummary::VFuncId R) { + return L.GUID == R.GUID && L.Offset == R.Offset; + } + static unsigned getHashValue(FunctionSummary::VFuncId I) { return I.GUID; } +}; + +template <> struct DenseMapInfo { + static inline FunctionSummary::ConstVCall getEmptyKey() { + return {{0, uint64_t(-1)}, {}}; + } + static inline FunctionSummary::ConstVCall getTombstoneKey() { + return {{0, uint64_t(-2)}, {}}; + } + static bool isEqual(FunctionSummary::ConstVCall L, + FunctionSummary::ConstVCall R) { + return DenseMapInfo::isEqual(L.VFunc, R.VFunc) && + L.Args == R.Args; + } + static unsigned getHashValue(FunctionSummary::ConstVCall I) { + return I.VFunc.GUID; + } }; /// \brief Global variable summary information to aid decisions and Index: llvm/include/llvm/IR/ModuleSummaryIndexYAML.h =================================================================== --- llvm/include/llvm/IR/ModuleSummaryIndexYAML.h +++ llvm/include/llvm/IR/ModuleSummaryIndexYAML.h @@ -82,7 +82,11 @@ false); Elem.push_back(llvm::make_unique( GVFlags, 0, ArrayRef{}, - ArrayRef{}, std::move(FSum.TypeTests))); + ArrayRef{}, std::move(FSum.TypeTests), + ArrayRef{}, + ArrayRef{}, + ArrayRef{}, + ArrayRef{})); } } static void output(IO &io, GlobalValueSummaryMapTy &V) { Index: llvm/lib/Analysis/ModuleSummaryAnalysis.cpp =================================================================== --- llvm/lib/Analysis/ModuleSummaryAnalysis.cpp +++ llvm/lib/Analysis/ModuleSummaryAnalysis.cpp @@ -84,6 +84,92 @@ return GV.hasSection() && GV.hasLocalLinkage(); } +/// Determine whether this call has all constant integer arguments (excluding +/// "this") and summarize it to VCalls or ConstVCalls as appropriate. +static void addVCallToSet(DevirtCallSite Call, GlobalValue::GUID Guid, + SetVector &VCalls, + SetVector &ConstVCalls) { + std::vector Args; + // Start from the second argument to skip the "this" pointer. + for (auto &Arg : make_range(Call.CS.arg_begin() + 1, Call.CS.arg_end())) { + auto *CI = dyn_cast(Arg); + if (!CI || CI->getBitWidth() > 64) { + VCalls.insert({Guid, Call.Offset}); + return; + } + Args.push_back(CI->getZExtValue()); + } + ConstVCalls.insert({{Guid, Call.Offset}, std::move(Args)}); +} + +/// If this intrinsic call requires that we add information to the function +/// summary, do so via the non-constant reference arguments. +static void addIntrinsicToSummary( + const CallInst *CI, SetVector &TypeTests, + SetVector &TypeTestAssumeVCalls, + SetVector &TypeCheckedLoadVCalls, + SetVector &TypeTestAssumeConstVCalls, + SetVector &TypeCheckedLoadConstVCalls) { + switch (CI->getCalledFunction()->getIntrinsicID()) { + case Intrinsic::type_test: { + auto *TypeMDVal = cast(CI->getArgOperand(1)); + auto *TypeId = dyn_cast(TypeMDVal->getMetadata()); + if (!TypeId) + break; + GlobalValue::GUID Guid = GlobalValue::getGUID(TypeId->getString()); + + // Produce a summary from type.test intrinsics. We only summarize type.test + // intrinsics that are used other than by an llvm.assume intrinsic. + // Intrinsics that are assumed are relevant only to the devirtualization + // pass, not the type test lowering pass. + bool HasNonAssumeUses = llvm::any_of(CI->uses(), [](const Use &CIU) { + auto *AssumeCI = dyn_cast(CIU.getUser()); + if (!AssumeCI) + return true; + Function *F = AssumeCI->getCalledFunction(); + return !F || F->getIntrinsicID() != Intrinsic::assume; + }); + if (HasNonAssumeUses) + TypeTests.insert(Guid); + + SmallVector DevirtCalls; + SmallVector Assumes; + findDevirtualizableCallsForTypeTest(DevirtCalls, Assumes, CI); + for (auto &Call : DevirtCalls) + addVCallToSet(Call, Guid, TypeTestAssumeVCalls, + TypeTestAssumeConstVCalls); + + break; + } + + case Intrinsic::type_checked_load: { + auto *TypeMDVal = cast(CI->getArgOperand(2)); + auto *TypeId = dyn_cast(TypeMDVal->getMetadata()); + if (!TypeId) + break; + GlobalValue::GUID Guid = GlobalValue::getGUID(TypeId->getString()); + + SmallVector DevirtCalls; + SmallVector LoadedPtrs; + SmallVector Preds; + bool HasNonCallUses = false; + findDevirtualizableCallsForTypeCheckedLoad(DevirtCalls, LoadedPtrs, Preds, + HasNonCallUses, CI); + // Any non-call uses of the result of llvm.type.checked.load will + // prevent us from optimizing away the llvm.type.test. + if (HasNonCallUses) + TypeTests.insert(Guid); + for (auto &Call : DevirtCalls) + addVCallToSet(Call, Guid, TypeCheckedLoadVCalls, + TypeCheckedLoadConstVCalls); + + break; + } + default: + break; + } +} + static void computeFunctionSummary(ModuleSummaryIndex &Index, const Module &M, const Function &F, BlockFrequencyInfo *BFI, @@ -99,6 +185,10 @@ MapVector CallGraphEdges; SetVector RefEdges; SetVector TypeTests; + SetVector TypeTestAssumeVCalls, + TypeCheckedLoadVCalls; + SetVector TypeTestAssumeConstVCalls, + TypeCheckedLoadConstVCalls; ICallPromotionAnalysis ICallAnalysis; bool HasInlineAsmMaybeReferencingInternal = false; @@ -133,25 +223,11 @@ // Check if this is a direct call to a known function or a known // intrinsic, or an indirect call with profile data. if (CalledFunction) { - if (CalledFunction->isIntrinsic()) { - if (CalledFunction->getIntrinsicID() != Intrinsic::type_test) - continue; - // Produce a summary from type.test intrinsics. We only summarize - // type.test intrinsics that are used other than by an llvm.assume - // intrinsic. Intrinsics that are assumed are relevant only to the - // devirtualization pass, not the type test lowering pass. - bool HasNonAssumeUses = llvm::any_of(CI->uses(), [](const Use &CIU) { - auto *AssumeCI = dyn_cast(CIU.getUser()); - if (!AssumeCI) - return true; - Function *F = AssumeCI->getCalledFunction(); - return !F || F->getIntrinsicID() != Intrinsic::assume; - }); - if (HasNonAssumeUses) { - auto *TypeMDVal = cast(CI->getArgOperand(1)); - if (auto *TypeId = dyn_cast(TypeMDVal->getMetadata())) - TypeTests.insert(GlobalValue::getGUID(TypeId->getString())); - } + if (CI && CalledFunction->isIntrinsic()) { + addIntrinsicToSummary( + CI, TypeTests, TypeTestAssumeVCalls, TypeCheckedLoadVCalls, + TypeTestAssumeConstVCalls, TypeCheckedLoadConstVCalls); + continue; } // We should have named any anonymous globals assert(CalledFunction->hasName()); @@ -193,7 +269,10 @@ /* LiveRoot = */ false); auto FuncSummary = llvm::make_unique( Flags, NumInsts, RefEdges.takeVector(), CallGraphEdges.takeVector(), - TypeTests.takeVector()); + TypeTests.takeVector(), TypeTestAssumeVCalls.takeVector(), + TypeCheckedLoadVCalls.takeVector(), + TypeTestAssumeConstVCalls.takeVector(), + TypeCheckedLoadConstVCalls.takeVector()); if (NonRenamableLocal) CantBePromoted.insert(F.getGUID()); Index.addGlobalValueSummary(F.getName(), std::move(FuncSummary)); @@ -347,7 +426,11 @@ llvm::make_unique( GVFlags, 0, ArrayRef{}, ArrayRef{}, - ArrayRef{}); + ArrayRef{}, + ArrayRef{}, + ArrayRef{}, + ArrayRef{}, + ArrayRef{}); Index.addGlobalValueSummary(Name, std::move(Summary)); } else { std::unique_ptr Summary = Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -4848,6 +4848,10 @@ GlobalValueSummary *LastSeenSummary = nullptr; bool Combined = false; std::vector PendingTypeTests; + std::vector PendingTypeTestAssumeVCalls, + PendingTypeCheckedLoadVCalls; + std::vector PendingTypeTestAssumeConstVCalls, + PendingTypeCheckedLoadConstVCalls; while (true) { BitstreamEntry Entry = Stream.advanceSkippingSubblocks(); @@ -4914,8 +4918,15 @@ IsOldProfileFormat, HasProfile); auto FS = llvm::make_unique( Flags, InstCount, std::move(Refs), std::move(Calls), - std::move(PendingTypeTests)); + std::move(PendingTypeTests), std::move(PendingTypeTestAssumeVCalls), + std::move(PendingTypeCheckedLoadVCalls), + std::move(PendingTypeTestAssumeConstVCalls), + std::move(PendingTypeCheckedLoadConstVCalls)); PendingTypeTests.clear(); + PendingTypeTestAssumeVCalls.clear(); + PendingTypeCheckedLoadVCalls.clear(); + PendingTypeTestAssumeConstVCalls.clear(); + PendingTypeCheckedLoadConstVCalls.clear(); auto GUID = getGUIDFromValueId(ValueID); FS->setModulePath(TheIndex.addModulePath(ModulePath, 0)->first()); FS->setOriginalName(GUID.second); @@ -4989,8 +5000,15 @@ GlobalValue::GUID GUID = getGUIDFromValueId(ValueID).first; auto FS = llvm::make_unique( Flags, InstCount, std::move(Refs), std::move(Edges), - std::move(PendingTypeTests)); + std::move(PendingTypeTests), std::move(PendingTypeTestAssumeVCalls), + std::move(PendingTypeCheckedLoadVCalls), + std::move(PendingTypeTestAssumeConstVCalls), + std::move(PendingTypeCheckedLoadConstVCalls)); PendingTypeTests.clear(); + PendingTypeTestAssumeVCalls.clear(); + PendingTypeCheckedLoadVCalls.clear(); + PendingTypeTestAssumeConstVCalls.clear(); + PendingTypeCheckedLoadConstVCalls.clear(); LastSeenSummary = FS.get(); FS->setModulePath(ModuleIdMap[ModuleId]); TheIndex.addGlobalValueSummary(GUID, std::move(FS)); @@ -5054,6 +5072,28 @@ Record.end()); break; } + case bitc::FS_TYPE_TEST_ASSUME_VCALLS: { + assert(PendingTypeTestAssumeVCalls.empty()); + for (unsigned I = 0; I != Record.size(); I += 2) + PendingTypeTestAssumeVCalls.push_back({Record[I], Record[I+1]}); + break; + } + case bitc::FS_TYPE_CHECKED_LOAD_VCALLS: { + assert(PendingTypeCheckedLoadVCalls.empty()); + for (unsigned I = 0; I != Record.size(); I += 2) + PendingTypeCheckedLoadVCalls.push_back({Record[I], Record[I+1]}); + break; + } + case bitc::FS_TYPE_TEST_ASSUME_CONST_VCALL: { + PendingTypeTestAssumeConstVCalls.push_back( + {{Record[0], Record[1]}, {Record.begin() + 2, Record.end()}}); + break; + } + case bitc::FS_TYPE_CHECKED_LOAD_CONST_VCALL: { + PendingTypeCheckedLoadConstVCalls.push_back( + {{Record[0], Record[1]}, {Record.begin() + 2, Record.end()}}); + break; + } } } llvm_unreachable("Exit infinite loop"); Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -3368,6 +3368,49 @@ Stream.ExitBlock(); } +/// Write the function type metadata related records that need to appear before +/// a function summary entry (whether per-module or combined). +static void writeFunctionTypeMetadataRecords(BitstreamWriter &Stream, + FunctionSummary *FS) { + if (!FS->type_tests().empty()) + Stream.EmitRecord(bitc::FS_TYPE_TESTS, FS->type_tests()); + + SmallVector Record; + + auto WriteVFuncIdVec = [&](uint64_t Ty, + ArrayRef VFs) { + if (VFs.empty()) + return; + Record.clear(); + for (auto &VF : VFs) { + Record.push_back(VF.GUID); + Record.push_back(VF.Offset); + } + Stream.EmitRecord(Ty, Record); + }; + + WriteVFuncIdVec(bitc::FS_TYPE_TEST_ASSUME_VCALLS, + FS->type_test_assume_vcalls()); + WriteVFuncIdVec(bitc::FS_TYPE_CHECKED_LOAD_VCALLS, + FS->type_checked_load_vcalls()); + + auto WriteConstVCallVec = [&](uint64_t Ty, + ArrayRef VCs) { + for (auto &VC : VCs) { + Record.clear(); + Record.push_back(VC.VFunc.GUID); + Record.push_back(VC.VFunc.Offset); + Record.insert(Record.end(), VC.Args.begin(), VC.Args.end()); + Stream.EmitRecord(Ty, Record); + } + }; + + WriteConstVCallVec(bitc::FS_TYPE_TEST_ASSUME_CONST_VCALL, + FS->type_test_assume_const_vcalls()); + WriteConstVCallVec(bitc::FS_TYPE_CHECKED_LOAD_CONST_VCALL, + FS->type_checked_load_const_vcalls()); +} + // Helper to emit a single function summary record. void ModuleBitcodeWriter::writePerModuleFunctionSummaryRecord( SmallVector &NameVals, GlobalValueSummary *Summary, @@ -3376,8 +3419,7 @@ NameVals.push_back(ValueID); FunctionSummary *FS = cast(Summary); - if (!FS->type_tests().empty()) - Stream.EmitRecord(bitc::FS_TYPE_TESTS, FS->type_tests()); + writeFunctionTypeMetadataRecords(Stream, FS); NameVals.push_back(getEncodedGVSummaryFlags(FS->flags())); NameVals.push_back(FS->instCount()); @@ -3637,8 +3679,7 @@ } auto *FS = cast(S); - if (!FS->type_tests().empty()) - Stream.EmitRecord(bitc::FS_TYPE_TESTS, FS->type_tests()); + writeFunctionTypeMetadataRecords(Stream, FS); NameVals.push_back(ValueId); NameVals.push_back(Index.getModuleId(FS->modulePath())); Index: llvm/test/Bitcode/thinlto-type-vcalls.ll =================================================================== --- /dev/null +++ llvm/test/Bitcode/thinlto-type-vcalls.ll @@ -0,0 +1,105 @@ +; RUN: opt -module-summary %s -o %t.o +; RUN: llvm-bcanalyzer -dump %t.o | FileCheck %s +; RUN: llvm-lto -thinlto -o %t2 %t.o +; RUN: llvm-bcanalyzer -dump %t2.thinlto.bc | FileCheck --check-prefix=COMBINED %s + +target datalayout = "e-p:64:64" +target triple = "x86_64-unknown-linux-gnu" + +; COMBINED: +; COMBINED-NEXT: +; COMBINED-NEXT: +; COMBINED-NEXT: +; COMBINED-NEXT: +; COMBINED-NEXT: +; COMBINED-NEXT: +; COMBINED-NEXT: +define void @f1([3 x i8*]* %vtable) { + %vtablei8 = bitcast [3 x i8*]* %vtable to i8* + %p = call i1 @llvm.type.test(i8* %vtablei8, metadata !"foo") + call void @llvm.assume(i1 %p) + %fptrptr = getelementptr [3 x i8*], [3 x i8*]* %vtable, i32 0, i32 2 + %fptr = load i8*, i8** %fptrptr + %fptr_casted = bitcast i8* %fptr to void (i8*, i32)* + call void %fptr_casted(i8* null, i32 undef) + ret void +} + +; CHECK: +define void @f2([3 x i8*]* %vtable, [3 x i8*]* %vtable2) { + %vtablei8 = bitcast [3 x i8*]* %vtable to i8* + %p = call i1 @llvm.type.test(i8* %vtablei8, metadata !"foo") + call void @llvm.assume(i1 %p) + %fptrptr = getelementptr [3 x i8*], [3 x i8*]* %vtable, i32 0, i32 3 + %fptr = load i8*, i8** %fptrptr + %fptr_casted = bitcast i8* %fptr to void (i8*, i32)* + call void %fptr_casted(i8* null, i32 undef) + + %vtablei82 = bitcast [3 x i8*]* %vtable2 to i8* + %p2 = call i1 @llvm.type.test(i8* %vtablei82, metadata !"bar") + call void @llvm.assume(i1 %p2) + %fptrptr2 = getelementptr [3 x i8*], [3 x i8*]* %vtable2, i32 0, i32 4 + %fptr2 = load i8*, i8** %fptrptr2 + %fptr_casted2 = bitcast i8* %fptr2 to void (i8*, i128)* + call void %fptr_casted2(i8* null, i128 0) + + ret void +} + +; CHECK: +define void @f3(i8* %vtable) { + %pair = call {i8*, i1} @llvm.type.checked.load(i8* %vtable, i32 16, metadata !"foo") + %fptr = extractvalue {i8*, i1} %pair, 0 + %fptr_casted = bitcast i8* %fptr to void (i8*, i32)* + call void %fptr_casted(i8* null, i32 undef) + ret void +} + +; CHECK: +; CHECK-NEXT: +define void @f4([3 x i8*]* %vtable, [3 x i8*]* %vtable2) { + %vtablei8 = bitcast [3 x i8*]* %vtable to i8* + %p = call i1 @llvm.type.test(i8* %vtablei8, metadata !"foo") + call void @llvm.assume(i1 %p) + %fptrptr = getelementptr [3 x i8*], [3 x i8*]* %vtable, i32 0, i32 2 + %fptr = load i8*, i8** %fptrptr + %fptr_casted = bitcast i8* %fptr to void (i8*, i32)* + call void %fptr_casted(i8* null, i32 42) + + %vtablei82 = bitcast [3 x i8*]* %vtable2 to i8* + %p2 = call i1 @llvm.type.test(i8* %vtablei82, metadata !"foo") + call void @llvm.assume(i1 %p2) + %fptrptr2 = getelementptr [3 x i8*], [3 x i8*]* %vtable2, i32 0, i32 3 + %fptr2 = load i8*, i8** %fptrptr2 + %fptr_casted2 = bitcast i8* %fptr2 to void (i8*, i32)* + call void %fptr_casted2(i8* null, i32 43) + ret void +} + +; CHECK: +define void @f5(i8* %vtable) { + %pair = call {i8*, i1} @llvm.type.checked.load(i8* %vtable, i32 16, metadata !"foo") + %fptr = extractvalue {i8*, i1} %pair, 0 + %fptr_casted = bitcast i8* %fptr to void (i8*, i32)* + call void %fptr_casted(i8* null, i32 42) + ret void +} + +; CHECK-NOT: +; CHECK-NOT: