Index: include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- include/llvm/Bitcode/LLVMBitCodes.h +++ include/llvm/Bitcode/LLVMBitCodes.h @@ -263,10 +263,31 @@ // Index-wide flags FS_FLAGS = 20, // Maps type identifier to summary information for that type identifier. + // Produced by the thin link (only lives in combined index). // TYPE_ID: [typeid, kind, bitwidth, align, size, bitmask, inlinebits, // n x (typeid, kind, name, numrba, // numrba x (numarg, numarg x arg, kind, info, byte, bit))] FS_TYPE_ID = 21, + // For background see overview at https://llvm.org/docs/TypeMetadata.html. + // The type metadata includes both the type identifier and the offset of + // the address point of the type (the address held by objects of that type + // which may not be the beginning of the virtual table). Vtable definitions + // are decorated with type metadata for the types they are compatible with. + // + // Maps type identifier to summary information for that type identifier + // computed from type metadata: the valueid of each vtable definition + // decorated with a type metadata for that identifier, and the offset from + // the corresponding type metadata. + // Exists in the per-module summary to provide information to thin link + // for index-based whole program devirtualization. + // TYPE_ID_METADATA: [typeid, n x (valueid, offset)] + FS_TYPE_ID_METADATA = 22, + // Summarizes vtable definition for use in index-based whole program + // devirtualization during the thin link. + // PERMODULE_VTABLE_GLOBALVAR_INIT_REFS: [valueid, flags, varflags, + // numrefs, numrefs x valueid, + // n x (valueid, offset)] + FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS = 23, }; enum MetadataCodes { Index: include/llvm/IR/ModuleSummaryIndex.h =================================================================== --- include/llvm/IR/ModuleSummaryIndex.h +++ include/llvm/IR/ModuleSummaryIndex.h @@ -678,6 +678,18 @@ } }; +/// The ValueInfo and offset for a function within a vtable definition +/// initializer array. +struct VirtFuncOffset { + VirtFuncOffset(ValueInfo VI, uint64_t Offset) + : FuncVI(VI), VTableOffset(Offset) {} + + ValueInfo FuncVI; + uint64_t VTableOffset; +}; +/// List of functions referenced by a particular vtable definition. +using VTableFuncList = std::vector; + /// Global variable summary information to aid decisions and /// implementation of importing. /// @@ -685,6 +697,11 @@ /// modified during the program run or not. This affects ThinLTO /// internalization class GlobalVarSummary : public GlobalValueSummary { +private: + /// For vtable definitions this holds the list of functions and + /// their corresponding offsets within the initializer array. + std::unique_ptr VTableFuncs; + public: struct GVarFlags { GVarFlags(bool ReadOnly = false) : ReadOnly(ReadOnly) {} @@ -705,6 +722,17 @@ GVarFlags varflags() const { return VarFlags; } void setReadOnly(bool RO) { VarFlags.ReadOnly = RO; } bool isReadOnly() const { return VarFlags.ReadOnly; } + + void setVTableFuncs(VTableFuncList Funcs) { + assert(!VTableFuncs); + VTableFuncs = llvm::make_unique(std::move(Funcs)); + } + + ArrayRef vTableFuncs() const { + if (VTableFuncs) + return *VTableFuncs; + return {}; + } }; struct TypeTestResolution { @@ -803,6 +831,29 @@ using TypeIdSummaryMapTy = std::multimap>; +/// The following data structures summarize type metadata information. +/// For type metadata overview see https://llvm.org/docs/TypeMetadata.html. +/// Each type metadata includes both the type identifier and the offset of +/// the address point of the type (the address held by objects of that type +/// which may not be the beginning of the virtual table). Vtable definitions +/// are decorated with type metadata for the types they are compatible with. +/// +/// Holds information about vtable definitions decorated with type metadata: +/// the vtable definition value and its address point offset in a type +/// identifier metadata it is decorated (compatible) with. +struct TypeIdOffsetVtableInfo { + TypeIdOffsetVtableInfo(uint64_t Offset, ValueInfo VI) + : AddressPointOffset(Offset), VTableVI(VI) {} + + uint64_t AddressPointOffset; + ValueInfo VTableVI; +}; +/// List of vtable definitions decorated by a particular type identifier, +/// and their corresponding offsets in that type identifier's metadata. +/// Note that each type identifier may be compatible with multiple vtables, due +/// to inheritance, which is why this is a vector. +using TypeIdCompatibleVtableInfo = std::vector; + /// Class to hold module path string table and global value map, /// and encapsulate methods for operating on them. class ModuleSummaryIndex { @@ -815,9 +866,15 @@ ModulePathStringTableTy ModulePathStringTable; /// Mapping from type identifier GUIDs to type identifier and its summary - /// information. + /// information. Produced by thin link. TypeIdSummaryMapTy TypeIdMap; + /// Mapping from type identifier to information about vtables decorated + /// with that type identifier's metadata. Produced by per module summary + /// analysis and consumed by thin link. For more information, see description + /// above where TypeIdCompatibleVtableInfo is defined. + std::map TypeIdCompatibleVtableMap; + /// Mapping from original ID to GUID. If original ID can map to multiple /// GUIDs, it will be mapped to 0. std::map OidGuidMap; @@ -1181,6 +1238,29 @@ return nullptr; } + const std::map & + typeIdCompatibleVtableMap() const { + return TypeIdCompatibleVtableMap; + } + + /// Return an existing or new TypeIdCompatibleVtableMap entry for \p TypeId. + /// This accessor can mutate the map and therefore should not be used in + /// the ThinLTO backends. + TypeIdCompatibleVtableInfo & + getOrInsertTypeIdCompatibleVtableSummary(StringRef TypeId) { + return TypeIdCompatibleVtableMap[TypeId]; + } + + /// For the given \p TypeId, this returns the TypeIdCompatibleVtableMap + /// entry if present in the summary map. This may be used when importing. + Optional + getTypeIdCompatibleVtableSummary(StringRef TypeId) const { + auto I = TypeIdCompatibleVtableMap.find(TypeId); + if (I == TypeIdCompatibleVtableMap.end()) + return None; + return I->second; + } + /// Collect for the given module the list of functions it defines /// (GUID -> Summary). void collectDefinedFunctionsForModule(StringRef ModulePath, Index: lib/Analysis/ModuleSummaryAnalysis.cpp =================================================================== --- lib/Analysis/ModuleSummaryAnalysis.cpp +++ lib/Analysis/ModuleSummaryAnalysis.cpp @@ -410,9 +410,98 @@ Index.addGlobalValueSummary(F, std::move(FuncSummary)); } +/// Find function pointers referenced within the given vtable initializer +/// (or subset of an initializer) \p I. The starting offset of \p I within +/// the vtable initializer is \p StartingOffset. Any discovered function +/// pointers are added to \p VTableFuncs along with their cumulative offset +/// within the initializer. +static void findFuncPointers(const Constant *I, uint64_t StartingOffset, + const Module &M, ModuleSummaryIndex &Index, + VTableFuncList &VTableFuncs) { + // First check if this is a function pointer. + if (I->getType()->isPointerTy()) { + auto Fn = dyn_cast(I->stripPointerCasts()); + // We can disregard __cxa_pure_virtual as a possible call target, as + // calls to pure virtuals are UB. + if (Fn && Fn->getName() != "__cxa_pure_virtual") + VTableFuncs.push_back({Index.getOrInsertValueInfo(Fn), StartingOffset}); + return; + } + + // Walk through the elements in the constant struct or array and recursively + // look for virtual function pointers. + const DataLayout &DL = M.getDataLayout(); + if (auto *C = dyn_cast(I)) { + StructType *STy = dyn_cast(C->getType()); + assert(STy); + const StructLayout *SL = DL.getStructLayout(C->getType()); + + for (StructType::element_iterator EB = STy->element_begin(), EI = EB, + EE = STy->element_end(); + EI != EE; ++EI) { + auto Offset = SL->getElementOffset(EI - EB); + unsigned Op = SL->getElementContainingOffset(Offset); + findFuncPointers(cast(I->getOperand(Op)), + StartingOffset + Offset, M, Index, VTableFuncs); + } + } else if (auto *C = dyn_cast(I)) { + ArrayType *ATy = C->getType(); + Type *EltTy = ATy->getElementType(); + uint64_t EltSize = DL.getTypeAllocSize(EltTy); + for (unsigned i = 0, e = ATy->getNumElements(); i != e; ++i) { + findFuncPointers(cast(I->getOperand(i)), + StartingOffset + i * EltSize, M, Index, VTableFuncs); + } + } +} + +// Identify the function pointers referenced by vtable definition \p V. +static void computeVTableFuncs(ModuleSummaryIndex &Index, + const GlobalVariable &V, const Module &M, + VTableFuncList &VTableFuncs) { + if (!V.isConstant()) + return; + + findFuncPointers(V.getInitializer(), /*StartingOffset=*/0, M, Index, + VTableFuncs); + +#ifndef NDEBUG + // Validate that the VTableFuncs list is ordered by offset. + uint64_t PrevOffset = 0; + for (auto &P : VTableFuncs) { + // The findVFuncPointers traversal should have encountered the + // functions in offset order. We need to use ">=" since PrevOffset + // starts at 0. + assert(P.VTableOffset >= PrevOffset); + PrevOffset = P.VTableOffset; + } +#endif +} + +/// Record vtable definition \p V for each type metadata it references. static void -computeVariableSummary(ModuleSummaryIndex &Index, const GlobalVariable &V, - DenseSet &CantBePromoted) { +recordTypeIdCompatibleVtableReferences(ModuleSummaryIndex &Index, + const GlobalVariable &V, + SmallVectorImpl &Types) { + for (MDNode *Type : Types) { + auto TypeID = Type->getOperand(1).get(); + + uint64_t Offset = + cast( + cast(Type->getOperand(0))->getValue()) + ->getZExtValue(); + + if (auto *TypeId = dyn_cast(TypeID)) + Index.getOrInsertTypeIdCompatibleVtableSummary(TypeId->getString()) + .push_back({Offset, Index.getOrInsertValueInfo(&V)}); + } +} + +static void computeVariableSummary(ModuleSummaryIndex &Index, + const GlobalVariable &V, + DenseSet &CantBePromoted, + const Module &M, + SmallVectorImpl &Types) { SetVector RefEdges; SmallPtrSet Visited; bool HasBlockAddress = findRefEdges(Index, &V, RefEdges, Visited); @@ -420,6 +509,21 @@ GlobalValueSummary::GVFlags Flags(V.getLinkage(), NonRenamableLocal, /* Live = */ false, V.isDSOLocal()); + VTableFuncList VTableFuncs; + // If splitting is not enabled, then we compute the summary information + // necessary for index-based whole program devirtualization. + if (!Index.enableSplitLTOUnit()) { + Types.clear(); + V.getMetadata(LLVMContext::MD_type, Types); + if (!Types.empty()) { + // Identify the function pointers referenced by this vtable definition. + computeVTableFuncs(Index, V, M, VTableFuncs); + + // Record this vtable definition for each type metadata it references. + recordTypeIdCompatibleVtableReferences(Index, V, Types); + } + } + // Don't mark variables we won't be able to internalize as read-only. GlobalVarSummary::GVarFlags VarFlags( !V.hasComdat() && !V.hasAppendingLinkage() && !V.isInterposable() && @@ -430,6 +534,8 @@ CantBePromoted.insert(V.getGUID()); if (HasBlockAddress) GVarSummary->setNotEligibleToImport(); + if (!VTableFuncs.empty()) + GVarSummary->setVTableFuncs(VTableFuncs); Index.addGlobalValueSummary(V, std::move(GVarSummary)); } @@ -574,10 +680,11 @@ // Compute summaries for all variables defined in module, and save in the // index. + SmallVector Types; for (const GlobalVariable &G : M.globals()) { if (G.isDeclaration()) continue; - computeVariableSummary(Index, G, CantBePromoted); + computeVariableSummary(Index, G, CantBePromoted, M, Types); } // Compute summaries for all aliases defined in module, and save in the Index: lib/AsmParser/LLLexer.cpp =================================================================== --- lib/AsmParser/LLLexer.cpp +++ lib/AsmParser/LLLexer.cpp @@ -749,6 +749,8 @@ KEYWORD(critical); KEYWORD(relbf); KEYWORD(variable); + KEYWORD(vTableFuncs); + KEYWORD(virtFunc); KEYWORD(aliasee); KEYWORD(refs); KEYWORD(typeIdInfo); @@ -761,6 +763,7 @@ KEYWORD(offset); KEYWORD(args); KEYWORD(typeid); + KEYWORD(typeidCompatibleVTable); KEYWORD(summary); KEYWORD(typeTestRes); KEYWORD(kind); Index: lib/AsmParser/LLParser.h =================================================================== --- lib/AsmParser/LLParser.h +++ lib/AsmParser/LLParser.h @@ -368,9 +368,11 @@ IdToIndexMapType &IdToIndexMap, unsigned Index); bool ParseVFuncId(FunctionSummary::VFuncId &VFuncId, IdToIndexMapType &IdToIndexMap, unsigned Index); + bool ParseOptionalVTableFuncs(VTableFuncList &VTableFuncs); bool ParseOptionalRefs(std::vector &Refs); bool ParseTypeIdEntry(unsigned ID); bool ParseTypeIdSummary(TypeIdSummary &TIS); + bool ParseTypeIdCompatibleVtableEntry(unsigned ID); bool ParseTypeTestResolution(TypeTestResolution &TTRes); bool ParseOptionalWpdResolutions( std::map &WPDResMap); Index: lib/AsmParser/LLParser.cpp =================================================================== --- lib/AsmParser/LLParser.cpp +++ lib/AsmParser/LLParser.cpp @@ -829,6 +829,9 @@ case lltok::kw_typeid: return ParseTypeIdEntry(SummaryID); break; + case lltok::kw_typeidCompatibleVTable: + return ParseTypeIdCompatibleVtableEntry(SummaryID); + break; default: return Error(Lex.getLoc(), "unexpected summary kind"); } @@ -7446,6 +7449,92 @@ return false; } +static ValueInfo EmptyVI = + ValueInfo(false, (GlobalValueSummaryMapTy::value_type *)-8); + +/// TypeIdCompatibleVtableEntry +/// ::= 'typeidCompatibleVTable' ':' '(' 'name' ':' STRINGCONSTANT ',' +/// TypeIdCompatibleVtableInfo +/// ')' +bool LLParser::ParseTypeIdCompatibleVtableEntry(unsigned ID) { + assert(Lex.getKind() == lltok::kw_typeidCompatibleVTable); + Lex.Lex(); + + std::string Name; + if (ParseToken(lltok::colon, "expected ':' here") || + ParseToken(lltok::lparen, "expected '(' here") || + ParseToken(lltok::kw_name, "expected 'name' here") || + ParseToken(lltok::colon, "expected ':' here") || + ParseStringConstant(Name)) + return true; + + TypeIdCompatibleVtableInfo &TI = + Index->getOrInsertTypeIdCompatibleVtableSummary(Name); + if (ParseToken(lltok::comma, "expected ',' here") || + ParseToken(lltok::kw_summary, "expected 'summary' here") || + ParseToken(lltok::colon, "expected ':' here") || + ParseToken(lltok::lparen, "expected '(' here")) + return true; + + IdToIndexMapType IdToIndexMap; + // Parse each call edge + do { + uint64_t Offset; + if (ParseToken(lltok::lparen, "expected '(' here") || + ParseToken(lltok::kw_offset, "expected 'offset' here") || + ParseToken(lltok::colon, "expected ':' here") || ParseUInt64(Offset) || + ParseToken(lltok::comma, "expected ',' here")) + return true; + + LocTy Loc = Lex.getLoc(); + unsigned GVId; + ValueInfo VI; + if (ParseGVReference(VI, GVId)) + return true; + + // Keep track of the TypeIdCompatibleVtableInfo array index needing a + // forward reference. We will save the location of the ValueInfo needing an + // update, but can only do so once the std::vector is finalized. + if (VI == EmptyVI) + IdToIndexMap[GVId].push_back(std::make_pair(TI.size(), Loc)); + TI.push_back({Offset, VI}); + + if (ParseToken(lltok::rparen, "expected ')' in call")) + return true; + } while (EatIfPresent(lltok::comma)); + + // Now that the TI vector is finalized, it is safe to save the locations + // of any forward GV references that need updating later. + for (auto I : IdToIndexMap) { + for (auto P : I.second) { + assert(TI[P.first].VTableVI == EmptyVI && + "Forward referenced ValueInfo expected to be empty"); + auto FwdRef = ForwardRefValueInfos.insert(std::make_pair( + I.first, std::vector>())); + FwdRef.first->second.push_back( + std::make_pair(&TI[P.first].VTableVI, P.second)); + } + } + + if (ParseToken(lltok::rparen, "expected ')' here") || + ParseToken(lltok::rparen, "expected ')' here")) + return true; + + // Check if this ID was forward referenced, and if so, update the + // corresponding GUIDs. + auto FwdRefTIDs = ForwardRefTypeIds.find(ID); + if (FwdRefTIDs != ForwardRefTypeIds.end()) { + for (auto TIDRef : FwdRefTIDs->second) { + assert(!*TIDRef.first && + "Forward referenced type id GUID expected to be 0"); + *TIDRef.first = GlobalValue::getGUID(Name); + } + ForwardRefTypeIds.erase(FwdRefTIDs); + } + + return false; +} + /// TypeTestResolution /// ::= 'typeTestRes' ':' '(' 'kind' ':' /// ( 'unsat' | 'byteArray' | 'inline' | 'single' | 'allOnes' ) ',' @@ -7954,6 +8043,7 @@ /*Live=*/false, /*IsLocal=*/false); GlobalVarSummary::GVarFlags GVarFlags(/*ReadOnly*/ false); std::vector Refs; + VTableFuncList VTableFuncs; if (ParseToken(lltok::colon, "expected ':' here") || ParseToken(lltok::lparen, "expected '(' here") || ParseModuleReference(ModulePath) || @@ -7962,10 +8052,20 @@ ParseGVarFlags(GVarFlags)) return true; - // Parse optional refs field - if (EatIfPresent(lltok::comma)) { - if (ParseOptionalRefs(Refs)) - return true; + // Parse optional fields + while (EatIfPresent(lltok::comma)) { + switch (Lex.getKind()) { + case lltok::kw_vTableFuncs: + if (ParseOptionalVTableFuncs(VTableFuncs)) + return true; + break; + case lltok::kw_refs: + if (ParseOptionalRefs(Refs)) + return true; + break; + default: + return Error(Lex.getLoc(), "expected optional variable summary field"); + } } if (ParseToken(lltok::rparen, "expected ')' here")) @@ -7975,6 +8075,7 @@ llvm::make_unique(GVFlags, GVarFlags, std::move(Refs)); GS->setModulePath(ModulePath); + GS->setVTableFuncs(std::move(VTableFuncs)); AddGlobalValueToIndex(Name, GUID, (GlobalValue::LinkageTypes)GVFlags.Linkage, ID, std::move(GS)); @@ -8195,6 +8296,67 @@ return false; } +/// OptionalVTableFuncs +/// := 'vTableFuncs' ':' '(' VTableFunc [',' VTableFunc]* ')' +/// VTableFunc ::= '(' 'virtFunc' ':' GVReference ',' 'offset' ':' UInt64 ')' +bool LLParser::ParseOptionalVTableFuncs(VTableFuncList &VTableFuncs) { + assert(Lex.getKind() == lltok::kw_vTableFuncs); + Lex.Lex(); + + if (ParseToken(lltok::colon, "expected ':' in vTableFuncs") | + ParseToken(lltok::lparen, "expected '(' in vTableFuncs")) + return true; + + IdToIndexMapType IdToIndexMap; + // Parse each virtual function pair + do { + ValueInfo VI; + if (ParseToken(lltok::lparen, "expected '(' in vTableFunc") || + ParseToken(lltok::kw_virtFunc, "expected 'callee' in vTableFunc") || + ParseToken(lltok::colon, "expected ':'")) + return true; + + LocTy Loc = Lex.getLoc(); + unsigned GVId; + if (ParseGVReference(VI, GVId)) + return true; + + uint64_t Offset; + if (ParseToken(lltok::comma, "expected comma") || + ParseToken(lltok::kw_offset, "expected offset") || + ParseToken(lltok::colon, "expected ':'") || ParseUInt64(Offset)) + return true; + + // Keep track of the VTableFuncs array index needing a forward reference. + // We will save the location of the ValueInfo needing an update, but + // can only do so once the std::vector is finalized. + if (VI == EmptyVI) + IdToIndexMap[GVId].push_back(std::make_pair(VTableFuncs.size(), Loc)); + VTableFuncs.push_back({VI, Offset}); + + if (ParseToken(lltok::rparen, "expected ')' in vTableFunc")) + return true; + } while (EatIfPresent(lltok::comma)); + + // Now that the VTableFuncs vector is finalized, it is safe to save the + // locations of any forward GV references that need updating later. + for (auto I : IdToIndexMap) { + for (auto P : I.second) { + assert(VTableFuncs[P.first].FuncVI == EmptyVI && + "Forward referenced ValueInfo expected to be empty"); + auto FwdRef = ForwardRefValueInfos.insert(std::make_pair( + I.first, std::vector>())); + FwdRef.first->second.push_back( + std::make_pair(&VTableFuncs[P.first].FuncVI, P.second)); + } + } + + if (ParseToken(lltok::rparen, "expected ')' in vTableFuncs")) + return true; + + return false; +} + /// OptionalRefs /// := 'refs' ':' '(' GVReference [',' GVReference]* ')' bool LLParser::ParseOptionalRefs(std::vector &Refs) { Index: lib/AsmParser/LLToken.h =================================================================== --- lib/AsmParser/LLToken.h +++ lib/AsmParser/LLToken.h @@ -380,6 +380,8 @@ kw_critical, kw_relbf, kw_variable, + kw_vTableFuncs, + kw_virtFunc, kw_aliasee, kw_refs, kw_typeIdInfo, @@ -392,6 +394,7 @@ kw_offset, kw_args, kw_typeid, + kw_typeidCompatibleVTable, kw_summary, kw_typeTestRes, kw_kind, Index: lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- lib/Bitcode/Reader/BitcodeReader.cpp +++ lib/Bitcode/Reader/BitcodeReader.cpp @@ -747,6 +747,9 @@ bool HasRelBF); Error parseEntireSummary(unsigned ID); Error parseModuleStringTable(); + void parseTypeIdCompatibleVtableSummaryRecord(ArrayRef Record); + void parseTypeIdCompatibleVtableInfo(ArrayRef Record, size_t &Slot, + TypeIdCompatibleVtableInfo &TypeId); std::pair getValueInfoFromValueId(unsigned ValueId); @@ -5300,6 +5303,27 @@ parseWholeProgramDevirtResolution(Record, Strtab, Slot, TypeId); } +void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableInfo( + ArrayRef Record, size_t &Slot, + TypeIdCompatibleVtableInfo &TypeId) { + uint64_t Offset = Record[Slot++]; + ValueInfo Callee = getValueInfoFromValueId(Record[Slot++]).first; + TypeId.push_back({Offset, Callee}); +} + +void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableSummaryRecord( + ArrayRef Record) { + size_t Slot = 0; + TypeIdCompatibleVtableInfo &TypeId = + TheIndex.getOrInsertTypeIdCompatibleVtableSummary( + {Strtab.data() + Record[Slot], + static_cast(Record[Slot + 1])}); + Slot += 2; + + while (Slot < Record.size()) + parseTypeIdCompatibleVtableInfo(Record, Slot, TypeId); +} + static void setImmutableRefs(std::vector &Refs, unsigned Count) { // Read-only refs are in the end of the refs list. for (unsigned RefNo = Refs.size() - Count; RefNo < Refs.size(); ++RefNo) @@ -5514,6 +5538,34 @@ TheIndex.addGlobalValueSummary(GUID.first, std::move(FS)); break; } + // FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS: [valueid, flags, varflags, + // numrefs, numrefs x valueid, + // n x (valueid, offset)] + case bitc::FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS: { + unsigned ValueID = Record[0]; + uint64_t RawFlags = Record[1]; + GlobalVarSummary::GVarFlags GVF = getDecodedGVarFlags(Record[2]); + unsigned NumRefs = Record[3]; + unsigned RefListStartIndex = 4; + unsigned VTableListStartIndex = RefListStartIndex + NumRefs; + auto Flags = getDecodedGVSummaryFlags(RawFlags, Version); + std::vector Refs = makeRefList( + ArrayRef(Record).slice(RefListStartIndex, NumRefs)); + VTableFuncList VTableFuncs; + for (unsigned I = VTableListStartIndex, E = Record.size(); I != E; ++I) { + ValueInfo Callee = getValueInfoFromValueId(Record[I]).first; + uint64_t Offset = Record[++I]; + VTableFuncs.push_back({Callee, Offset}); + } + auto VS = + llvm::make_unique(Flags, GVF, std::move(Refs)); + VS->setModulePath(getThisModule()->first()); + VS->setVTableFuncs(VTableFuncs); + auto GUID = getValueInfoFromValueId(ValueID); + VS->setOriginalName(GUID.second); + TheIndex.addGlobalValueSummary(GUID.first, std::move(VS)); + break; + } // FS_COMBINED: [valueid, modid, flags, instcount, fflags, numrefs, // numrefs x valueid, n x (valueid)] // FS_COMBINED_PROFILE: [valueid, modid, flags, instcount, fflags, numrefs, @@ -5680,6 +5732,10 @@ case bitc::FS_TYPE_ID: parseTypeIdSummaryRecord(Record, Strtab, TheIndex); break; + + case bitc::FS_TYPE_ID_METADATA: + parseTypeIdCompatibleVtableSummaryRecord(Record); + break; } } llvm_unreachable("Exit infinite loop"); Index: lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- lib/Bitcode/Writer/BitcodeWriter.cpp +++ lib/Bitcode/Writer/BitcodeWriter.cpp @@ -214,7 +214,8 @@ const Function &F); void writeModuleLevelReferences(const GlobalVariable &V, SmallVector &NameVals, - unsigned FSModRefsAbbrev); + unsigned FSModRefsAbbrev, + unsigned FSModVTableRefsAbbrev); void assignValueId(GlobalValue::GUID ValGUID) { GUIDToValueIdMap[ValGUID] = ++GlobalValueId; @@ -3582,6 +3583,19 @@ W.second); } +static void writeTypeIdCompatibleVtableSummaryRecord( + SmallVector &NameVals, StringTableBuilder &StrtabBuilder, + const std::string &Id, const TypeIdCompatibleVtableInfo &Summary, + ValueEnumerator &VE) { + NameVals.push_back(StrtabBuilder.add(Id)); + NameVals.push_back(Id.size()); + + for (auto &P : Summary) { + NameVals.push_back(P.AddressPointOffset); + NameVals.push_back(VE.getValueID(P.VTableVI.getValue())); + } +} + // Helper to emit a single function summary record. void ModuleBitcodeWriterBase::writePerModuleFunctionSummaryRecord( SmallVector &NameVals, GlobalValueSummary *Summary, @@ -3626,7 +3640,7 @@ // and emit them in a summary record. void ModuleBitcodeWriterBase::writeModuleLevelReferences( const GlobalVariable &V, SmallVector &NameVals, - unsigned FSModRefsAbbrev) { + unsigned FSModRefsAbbrev, unsigned FSModVTableRefsAbbrev) { auto VI = Index->getValueInfo(V.getGUID()); if (!VI || VI.getSummaryList().empty()) { // Only declarations should not have a summary (a declaration might however @@ -3640,6 +3654,10 @@ NameVals.push_back(getEncodedGVSummaryFlags(VS->flags())); NameVals.push_back(getEncodedGVarFlags(VS->varflags())); + auto VTableFuncs = VS->vTableFuncs(); + if (!VTableFuncs.empty()) + NameVals.push_back(VS->refs().size()); + unsigned SizeBeforeRefs = NameVals.size(); for (auto &RI : VS->refs()) NameVals.push_back(VE.getValueID(RI.getValue())); @@ -3647,8 +3665,20 @@ // been initialized from a DenseSet. llvm::sort(NameVals.begin() + SizeBeforeRefs, NameVals.end()); - Stream.EmitRecord(bitc::FS_PERMODULE_GLOBALVAR_INIT_REFS, NameVals, - FSModRefsAbbrev); + if (!VTableFuncs.empty()) { + // VTableFuncs pairs should already be sorted by offset. + for (auto &P : VTableFuncs) { + NameVals.push_back(VE.getValueID(P.FuncVI.getValue())); + NameVals.push_back(P.VTableOffset); + } + } + + if (VTableFuncs.empty()) + Stream.EmitRecord(bitc::FS_PERMODULE_GLOBALVAR_INIT_REFS, NameVals, + FSModRefsAbbrev); + else + Stream.EmitRecord(bitc::FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS, NameVals, + FSModVTableRefsAbbrev); NameVals.clear(); } @@ -3729,6 +3759,17 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); unsigned FSModRefsAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + // Abbrev for FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS. + Abbv = std::make_shared(); + Abbv->Add(BitCodeAbbrevOp(bitc::FS_PERMODULE_VTABLE_GLOBALVAR_INIT_REFS)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // valueid + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // flags + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs + // numrefs x valueid, n x (valueid , offset) + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); + unsigned FSModVTableRefsAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + // Abbrev for FS_ALIAS. Abbv = std::make_shared(); Abbv->Add(BitCodeAbbrevOp(bitc::FS_ALIAS)); @@ -3737,6 +3778,16 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // valueid unsigned FSAliasAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + // Abbrev for FS_TYPE_ID_METADATA + Abbv = std::make_shared(); + Abbv->Add(BitCodeAbbrevOp(bitc::FS_TYPE_ID_METADATA)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // typeid strtab index + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // typeid length + // n x (valueid , offset) + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); + unsigned TypeIdCompatibleVtableAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + SmallVector NameVals; // Iterate over the list of functions instead of the Index to // ensure the ordering is stable. @@ -3761,7 +3812,8 @@ // Capture references from GlobalVariable initializers, which are outside // of a function scope. for (const GlobalVariable &G : M.globals()) - writeModuleLevelReferences(G, NameVals, FSModRefsAbbrev); + writeModuleLevelReferences(G, NameVals, FSModRefsAbbrev, + FSModVTableRefsAbbrev); for (const GlobalAlias &A : M.aliases()) { auto *Aliasee = A.getBaseObject(); @@ -3779,6 +3831,16 @@ NameVals.clear(); } + if (!Index->typeIdCompatibleVtableMap().empty()) { + for (auto &S : Index->typeIdCompatibleVtableMap()) { + writeTypeIdCompatibleVtableSummaryRecord(NameVals, StrtabBuilder, S.first, + S.second, VE); + Stream.EmitRecord(bitc::FS_TYPE_ID_METADATA, NameVals, + TypeIdCompatibleVtableAbbrev); + NameVals.clear(); + } + } + Stream.ExitBlock(); } Index: lib/IR/AsmWriter.cpp =================================================================== --- lib/IR/AsmWriter.cpp +++ lib/IR/AsmWriter.cpp @@ -1037,6 +1037,9 @@ TidIter != TheIndex->typeIds().end(); TidIter++) CreateTypeIdSlot(TidIter->second.first); + for (auto &TId : TheIndex->typeIdCompatibleVtableMap()) + CreateGUIDSlot(GlobalValue::getGUID(TId.first)); + ST_DEBUG("end processIndex!\n"); } @@ -2410,6 +2413,7 @@ void printGlobalVarSummary(const GlobalVarSummary *GS); void printFunctionSummary(const FunctionSummary *FS); void printTypeIdSummary(const TypeIdSummary &TIS); + void printTypeIdCompatibleVtableSummary(const TypeIdCompatibleVtableInfo &TI); void printTypeTestResolution(const TypeTestResolution &TTRes); void printArgs(const std::vector &Args); void printWPDRes(const WholeProgramDevirtResolution &WPDRes); @@ -2712,6 +2716,15 @@ printTypeIdSummary(TidIter->second.second); Out << ") ; guid = " << TidIter->first << "\n"; } + + // Print the TypeIdCompatibleVtableMap entries. + for (auto &TId : TheIndex->typeIdCompatibleVtableMap()) { + auto GUID = GlobalValue::getGUID(TId.first); + Out << "^" << Machine.getGUIDSlot(GUID) + << " = typeidCompatibleVTable: (name: \"" << TId.first << "\""; + printTypeIdCompatibleVtableSummary(TId.second); + Out << ") ; guid = " << GUID << "\n"; + } } static const char * @@ -2794,6 +2807,19 @@ Out << ")"; } +void AssemblyWriter::printTypeIdCompatibleVtableSummary( + const TypeIdCompatibleVtableInfo &TI) { + Out << ", summary: ("; + FieldSeparator FS; + for (auto &P : TI) { + Out << FS; + Out << "(offset: " << P.AddressPointOffset << ", "; + Out << "^" << Machine.getGUIDSlot(P.VTableVI.getGUID()); + Out << ")"; + } + Out << ")"; +} + void AssemblyWriter::printArgs(const std::vector &Args) { Out << "args: ("; FieldSeparator FS; @@ -2863,6 +2889,19 @@ void AssemblyWriter::printGlobalVarSummary(const GlobalVarSummary *GS) { Out << ", varFlags: (readonly: " << GS->VarFlags.ReadOnly << ")"; + + auto VTableFuncs = GS->vTableFuncs(); + if (!VTableFuncs.empty()) { + Out << ", vTableFuncs: ("; + FieldSeparator FS; + for (auto &P : VTableFuncs) { + Out << FS; + Out << "(virtFunc: ^" << Machine.getGUIDSlot(P.FuncVI.getGUID()) + << ", offset: " << P.VTableOffset; + Out << ")"; + } + Out << ")"; + } } static std::string getLinkageName(GlobalValue::LinkageTypes LT) { Index: lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp =================================================================== --- lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp +++ lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp @@ -417,34 +417,53 @@ } } -// Returns whether this module needs to be split because splitting is -// enabled and it uses type metadata. -bool requiresSplit(Module &M) { - // First check if the LTO Unit splitting has been enabled. +// Check if the LTO Unit splitting has been enabled. +bool enableSplitLTOUnit(Module &M) { bool EnableSplitLTOUnit = false; if (auto *MD = mdconst::extract_or_null( M.getModuleFlag("EnableSplitLTOUnit"))) EnableSplitLTOUnit = MD->getZExtValue(); - if (!EnableSplitLTOUnit) - return false; + return EnableSplitLTOUnit; +} - // Module only needs to be split if it contains type metadata. +// Returns whether this module needs to be split because it uses type metadata. +bool hasTypeMetadata(Module &M) { for (auto &GO : M.global_objects()) { if (GO.hasMetadata(LLVMContext::MD_type)) return true; } - return false; } void writeThinLTOBitcode(raw_ostream &OS, raw_ostream *ThinLinkOS, function_ref AARGetter, Module &M, const ModuleSummaryIndex *Index) { - // Split module if splitting is enabled and it contains any type metadata. - if (requiresSplit(M)) - return splitAndWriteThinLTOBitcode(OS, ThinLinkOS, AARGetter, M); + std::unique_ptr NewIndex = nullptr; + // See if this module has any type metadata. If so, we try to split it + // or at least promote type ids to enable WPD. + if (hasTypeMetadata(M)) { + if (enableSplitLTOUnit(M)) + return splitAndWriteThinLTOBitcode(OS, ThinLinkOS, AARGetter, M); + // Promote type ids as needed for index-based WPD. + std::string ModuleId = getUniqueModuleId(&M); + if (!ModuleId.empty()) { + promoteTypeIds(M, ModuleId); + // Need to rebuild the index so that it contains type metadata + // for the newly promoted type ids. + // FIXME: Probably should not bother building the index at all + // in the caller of writeThinLTOBitcode (which does so via the + // ModuleSummaryIndexAnalysis pass), since we have to rebuild it + // anyway whenever there is type metadata (here or in + // splitAndWriteThinLTOBitcode). Just always build it once via the + // buildModuleSummaryIndex when Module(s) are ready. + ProfileSummaryInfo PSI(M); + NewIndex = llvm::make_unique( + buildModuleSummaryIndex(M, nullptr, &PSI)); + Index = NewIndex.get(); + } + } - // Otherwise we can just write it out as a regular module. + // Write it out as an unsplit ThinLTO module. // Save the module hash produced for the full bitcode, which will // be used in the backends, and use that in the minimized bitcode Index: test/Assembler/thinlto-vtable-summary.ll =================================================================== --- /dev/null +++ test/Assembler/thinlto-vtable-summary.ll @@ -0,0 +1,38 @@ +; Test summary parsing of index-based WPD related summary fields +; RUN: llvm-as %s -o - | llvm-dis -o %t.ll +; RUN: grep "^\^" %s >%t2 +; RUN: grep "^\^" %t.ll >%t3 +; Expect that the summary information is the same after round-trip through +; llvm-as and llvm-dis. +; RUN: diff %t2 %t3 + +source_filename = "thinlto-vtable-summary.ll" +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-grtev4-linux-gnu" + +%struct.A = type { i32 (...)** } +%struct.B = type { %struct.A } +%struct.C = type { %struct.A } + +@_ZTV1B = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !1 +@_ZTV1C = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2 + +declare i32 @_ZN1B1fEi(%struct.B*, i32) + +declare i32 @_ZN1A1nEi(%struct.A*, i32) + +declare i32 @_ZN1C1fEi(%struct.C*, i32) + +!0 = !{i64 16, !"_ZTS1A"} +!1 = !{i64 16, !"_ZTS1B"} +!2 = !{i64 16, !"_ZTS1C"} + +^0 = module: (path: "", hash: (0, 0, 0, 0, 0)) +^1 = gv: (name: "_ZN1A1nEi") ; guid = 1621563287929432257 +^2 = gv: (name: "_ZTV1B", summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0), varFlags: (readonly: 0), vTableFuncs: ((virtFunc: ^3, offset: 16), (virtFunc: ^1, offset: 24)), refs: (^3, ^1)))) ; guid = 5283576821522790367 +^3 = gv: (name: "_ZN1B1fEi") ; guid = 7162046368816414394 +^4 = gv: (name: "_ZTV1C", summaries: (variable: (module: ^0, flags: (linkage: external, notEligibleToImport: 0, live: 0, dsoLocal: 0), varFlags: (readonly: 0), vTableFuncs: ((virtFunc: ^5, offset: 16), (virtFunc: ^1, offset: 24)), refs: (^1, ^5)))) ; guid = 13624023785555846296 +^5 = gv: (name: "_ZN1C1fEi") ; guid = 14876272565662207556 +^6 = typeidCompatibleVTable: (name: "_ZTS1A", summary: ((offset: 16, ^2), (offset: 16, ^4))) ; guid = 7004155349499253778 +^7 = typeidCompatibleVTable: (name: "_ZTS1B", summary: ((offset: 16, ^2))) ; guid = 6203814149063363976 +^8 = typeidCompatibleVTable: (name: "_ZTS1C", summary: ((offset: 16, ^4))) ; guid = 1884921850105019584 Index: test/ThinLTO/X86/devirt.ll =================================================================== --- /dev/null +++ test/ThinLTO/X86/devirt.ll @@ -0,0 +1,146 @@ +; REQUIRES: x86-registered-target + +; Test devirtualization through the thin link and backend. + +; Generate split module with summary for hybrid Thin/Regular LTO WPD. +; RUN: opt -thinlto-bc -thinlto-split-lto-unit -o %t.o %s + +; Check that we have module flag showing splitting enabled, and that we don't +; generate summary information needed for index-based WPD. +; RUN: llvm-modextract -b -n=0 %t.o -o %t.o.0 +; RUN: llvm-dis -o - %t.o.0 | FileCheck %s --check-prefix=ENABLESPLITFLAG --implicit-check-not=vTableFuncs --implicit-check-not=typeidCompatibleVTable +; RUN: llvm-modextract -b -n=1 %t.o -o %t.o.1 +; RUN: llvm-dis -o - %t.o.1 | FileCheck %s --check-prefix=ENABLESPLITFLAG --implicit-check-not=vTableFuncs --implicit-check-not=typeidCompatibleVTable +; ENABLESPLITFLAG: !{i32 1, !"EnableSplitLTOUnit", i32 1} + +; Generate unsplit module with summary for ThinLTO index-based WPD. +; RUN: opt -thinlto-bc -o %t2.o %s + +; Check that we don't have module flag when splitting not enabled for ThinLTO, +; and that we generate summary information needed for index-based WPD. +; RUN: llvm-dis -o - %t2.o | FileCheck %s --check-prefix=NOENABLESPLITFLAG +; NOENABLESPLITFLAG-DAG: !{i32 1, !"EnableSplitLTOUnit", i32 0} +; NOENABLESPLITFLAG-DAG: [[An:\^[0-9]+]] = gv: (name: "_ZN1A1nEi") +; NOENABLESPLITFLAG-DAG: [[Bf:\^[0-9]+]] = gv: (name: "_ZN1B1fEi") +; NOENABLESPLITFLAG-DAG: [[Cf:\^[0-9]+]] = gv: (name: "_ZN1C1fEi") +; NOENABLESPLITFLAG-DAG: [[Dm:\^[0-9]+]] = gv: (name: "_ZN1D1mEi") +; NOENABLESPLITFLAG-DAG: [[B:\^[0-9]+]] = gv: (name: "_ZTV1B", {{.*}} vTableFuncs: ((virtFunc: [[Bf]], offset: 16), (virtFunc: [[An]], offset: 24)), refs: ([[Bf]], [[An]]) +; NOENABLESPLITFLAG-DAG: [[C:\^[0-9]+]] = gv: (name: "_ZTV1C", {{.*}} vTableFuncs: ((virtFunc: [[Cf]], offset: 16), (virtFunc: [[An]], offset: 24)), refs: ([[An]], [[Cf]]) +; NOENABLESPLITFLAG-DAG: [[D:\^[0-9]+]] = gv: (name: "_ZTV1D", {{.*}} vTableFuncs: ((virtFunc: [[Dm]], offset: 16)), refs: ([[Dm]]) +; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1A", summary: ((offset: 16, [[B]]), (offset: 16, [[C]]))) +; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1B", summary: ((offset: 16, [[B]]))) +; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "_ZTS1C", summary: ((offset: 16, [[C]]))) +; Type Id on _ZTV1D should have been promoted +; NOENABLESPLITFLAG-DAG: typeidCompatibleVTable: (name: "1${{.*}}", summary: ((offset: 16, [[D]]))) + +; TODO: Test index-based WPD one %t2.o once implemented. + +; Legacy PM +; RUN: llvm-lto2 run %t.o -save-temps -pass-remarks=. \ +; RUN: -o %t3 \ +; RUN: -r=%t.o,test,px \ +; RUN: -r=%t.o,_ZN1A1nEi,p \ +; RUN: -r=%t.o,_ZN1B1fEi,p \ +; RUN: -r=%t.o,_ZN1C1fEi,p \ +; RUN: -r=%t.o,_ZN1D1mEi,p \ +; RUN: -r=%t.o,_ZTV1B, \ +; RUN: -r=%t.o,_ZTV1C, \ +; RUN: -r=%t.o,_ZTV1D, \ +; RUN: -r=%t.o,_ZN1A1nEi, \ +; RUN: -r=%t.o,_ZN1B1fEi, \ +; RUN: -r=%t.o,_ZN1C1fEi, \ +; RUN: -r=%t.o,_ZN1D1mEi, \ +; RUN: -r=%t.o,_ZTV1B,px \ +; RUN: -r=%t.o,_ZTV1C,px \ +; RUN: -r=%t.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR + +; New PM +; RUN: llvm-lto2 run %t.o -save-temps -use-new-pm -pass-remarks=. \ +; RUN: -o %t3 \ +; RUN: -r=%t.o,test,px \ +; RUN: -r=%t.o,_ZN1A1nEi,p \ +; RUN: -r=%t.o,_ZN1B1fEi,p \ +; RUN: -r=%t.o,_ZN1C1fEi,p \ +; RUN: -r=%t.o,_ZN1D1mEi,p \ +; RUN: -r=%t.o,_ZTV1B, \ +; RUN: -r=%t.o,_ZTV1C, \ +; RUN: -r=%t.o,_ZTV1D, \ +; RUN: -r=%t.o,_ZN1A1nEi, \ +; RUN: -r=%t.o,_ZN1B1fEi, \ +; RUN: -r=%t.o,_ZN1C1fEi, \ +; RUN: -r=%t.o,_ZN1D1mEi, \ +; RUN: -r=%t.o,_ZTV1B,px \ +; RUN: -r=%t.o,_ZTV1C,px \ +; RUN: -r=%t.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK +; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR + +; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi +; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-grtev4-linux-gnu" + +%struct.A = type { i32 (...)** } +%struct.B = type { %struct.A } +%struct.C = type { %struct.A } +%struct.D = type { i32 (...)** } + +@_ZTV1B = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !1 +@_ZTV1C = constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.C*, i32)* @_ZN1C1fEi to i8*), i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A1nEi to i8*)] }, !type !0, !type !2 +@_ZTV1D = constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* undef, i8* bitcast (i32 (%struct.D*, i32)* @_ZN1D1mEi to i8*)] }, !type !3 + + +; CHECK-IR-LABEL: define i32 @test +define i32 @test(%struct.A* %obj, %struct.D* %obj2, i32 %a) { +entry: + %0 = bitcast %struct.A* %obj to i8*** + %vtable = load i8**, i8*** %0 + %1 = bitcast i8** %vtable to i8* + %p = call i1 @llvm.type.test(i8* %1, metadata !"_ZTS1A") + call void @llvm.assume(i1 %p) + %fptrptr = getelementptr i8*, i8** %vtable, i32 1 + %2 = bitcast i8** %fptrptr to i32 (%struct.A*, i32)** + %fptr1 = load i32 (%struct.A*, i32)*, i32 (%struct.A*, i32)** %2, align 8 + + ; Check that the call was devirtualized. + ; CHECK-IR: %call = tail call i32 @_ZN1A1nEi + %call = tail call i32 %fptr1(%struct.A* nonnull %obj, i32 %a) + + %3 = bitcast i8** %vtable to i32 (%struct.A*, i32)** + %fptr22 = load i32 (%struct.A*, i32)*, i32 (%struct.A*, i32)** %3, align 8 + + ; We still have to call it as virtual. + ; CHECK-IR: %call3 = tail call i32 %fptr22 + %call3 = tail call i32 %fptr22(%struct.A* nonnull %obj, i32 %call) + + %4 = bitcast %struct.D* %obj2 to i8*** + %vtable2 = load i8**, i8*** %4 + %5 = bitcast i8** %vtable2 to i8* + %p2 = call i1 @llvm.type.test(i8* %5, metadata !4) + call void @llvm.assume(i1 %p2) + + %6 = bitcast i8** %vtable2 to i32 (%struct.D*, i32)** + %fptr33 = load i32 (%struct.D*, i32)*, i32 (%struct.D*, i32)** %6, align 8 + + ; Check that the call was devirtualized. + ; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi + %call4 = tail call i32 %fptr33(%struct.D* nonnull %obj2, i32 %call3) + ret i32 %call4 +} +; CHECK-IR-LABEL: ret i32 +; CHECK-IR-LABEL: } + +declare i1 @llvm.type.test(i8*, metadata) +declare void @llvm.assume(i1) + +declare i32 @_ZN1B1fEi(%struct.B* %this, i32 %a) +declare i32 @_ZN1A1nEi(%struct.A* %this, i32 %a) +declare i32 @_ZN1C1fEi(%struct.C* %this, i32 %a) +declare i32 @_ZN1D1mEi(%struct.D* %this, i32 %a) + +!0 = !{i64 16, !"_ZTS1A"} +!1 = !{i64 16, !"_ZTS1B"} +!2 = !{i64 16, !"_ZTS1C"} +!3 = !{i64 16, !4} +!4 = distinct !{} Index: tools/llvm-bcanalyzer/llvm-bcanalyzer.cpp =================================================================== --- tools/llvm-bcanalyzer/llvm-bcanalyzer.cpp +++ tools/llvm-bcanalyzer/llvm-bcanalyzer.cpp @@ -317,6 +317,7 @@ STRINGIFY_CODE(FS, PERMODULE_PROFILE) STRINGIFY_CODE(FS, PERMODULE_RELBF) STRINGIFY_CODE(FS, PERMODULE_GLOBALVAR_INIT_REFS) + STRINGIFY_CODE(FS, PERMODULE_VTABLE_GLOBALVAR_INIT_REFS) STRINGIFY_CODE(FS, COMBINED) STRINGIFY_CODE(FS, COMBINED_PROFILE) STRINGIFY_CODE(FS, COMBINED_GLOBALVAR_INIT_REFS) @@ -334,6 +335,7 @@ STRINGIFY_CODE(FS, CFI_FUNCTION_DEFS) STRINGIFY_CODE(FS, CFI_FUNCTION_DECLS) STRINGIFY_CODE(FS, TYPE_ID) + STRINGIFY_CODE(FS, TYPE_ID_METADATA) } case bitc::METADATA_ATTACHMENT_ID: switch(CodeID) {