diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 7c463f51f63dc..090eb4c16ce0b 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1592,6 +1592,7 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF, cast(SrcRecordTy->castAs()->getDecl()); llvm::Value *Value = CGF.GetVTablePtr(ThisPtr, CGM.GlobalsInt8PtrTy, ClassDecl); + CGF.EmitTypeMetadataCodeForVCall(ClassDecl, Value, SourceLocation()); if (CGM.getItaniumVTableContext().isRelativeLayout()) { // Load the type info. diff --git a/clang/test/CodeGenCXX/typeid-type-test.cpp b/clang/test/CodeGenCXX/typeid-type-test.cpp new file mode 100644 index 0000000000000..9408d87495c60 --- /dev/null +++ b/clang/test/CodeGenCXX/typeid-type-test.cpp @@ -0,0 +1,32 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5 +// RUN: %clang_cc1 -I%S -triple x86_64-unknown-linux -flto -fwhole-program-vtables -fvisibility=hidden -emit-llvm -o - %s | FileCheck %s + +#include + +namespace Test1 { +struct A { virtual void f(); }; + +// CHECK-LABEL: define hidden noundef nonnull align 8 dereferenceable(16) ptr @_ZN5Test19gettypeidEPNS_1AE( +// CHECK-SAME: ptr noundef [[A:%.*]]) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8 +// CHECK-NEXT: [[TMP1:%.*]] = icmp eq ptr [[TMP0]], null +// CHECK-NEXT: br i1 [[TMP1]], label %[[TYPEID_BAD_TYPEID:.*]], label %[[TYPEID_END:.*]] +// CHECK: [[TYPEID_BAD_TYPEID]]: +// CHECK-NEXT: call void @__cxa_bad_typeid() #[[ATTR3:[0-9]+]] +// CHECK-NEXT: unreachable +// CHECK: [[TYPEID_END]]: +// CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr [[TMP0]], align 8 +// CHECK-NEXT: [[TMP2:%.*]] = call i1 @llvm.type.test(ptr [[VTABLE]], metadata !"_ZTSN5Test11AE") +// CHECK-NEXT: call void @llvm.assume(i1 [[TMP2]]) +// CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds ptr, ptr [[VTABLE]], i64 -1 +// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[TMP3]], align 8 +// CHECK-NEXT: ret ptr [[TMP4]] +// +const std::type_info &gettypeid(A *a) { + return typeid(*a); +} + +} diff --git a/llvm/include/llvm/Analysis/TypeMetadataUtils.h b/llvm/include/llvm/Analysis/TypeMetadataUtils.h index bdb477b54b532..c553de833a203 100644 --- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h +++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h @@ -51,6 +51,10 @@ void findDevirtualizableCallsForTypeTest( SmallVectorImpl &Assumes, const CallInst *CI, DominatorTree &DT); +/// Given a call to the intrinsic \@llvm.type.test, return true if a type id +/// load exists that associated with this intrinsic call. +bool hasTypeIdLoadForTypeTest(const CallInst *CI); + /// Given a call to the intrinsic \@llvm.type.checked.load, find all /// devirtualizable call sites based on the call and return them in DevirtCalls. void findDevirtualizableCallsForTypeCheckedLoad( diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h index c01de4a289a69..b5dbfdd24657d 100644 --- a/llvm/include/llvm/AsmParser/LLParser.h +++ b/llvm/include/llvm/AsmParser/LLParser.h @@ -427,6 +427,7 @@ namespace llvm { bool parseTypeIdEntry(unsigned ID); bool parseTypeIdSummary(TypeIdSummary &TIS); bool parseTypeIdCompatibleVtableEntry(unsigned ID); + bool parseTypeIdMayBeAccessed(unsigned ID); bool parseTypeTestResolution(TypeTestResolution &TTRes); bool parseOptionalWpdResolutions( std::map &WPDResMap); diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h index 7b47bc88ddb25..c2bdab430b689 100644 --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -422,6 +422,7 @@ enum Kind { kw_args, kw_typeid, kw_typeidCompatibleVTable, + kw_typeidMayBeAccessed, kw_summary, kw_typeTestRes, kw_kind, diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 9eb38c3e44829..41cb7a5922088 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -335,6 +335,7 @@ enum GlobalValueSummarySymtabCodes { // CallStackRadixTreeBuilder class in ProfileData/MemProf.h for format. // [n x entry] FS_CONTEXT_RADIX_TREE_ARRAY = 32, + FS_RTTI = 33, }; enum MetadataCodes { diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h index 3c586a1dd21d8..8650c6a124dbb 100644 --- a/llvm/include/llvm/IR/ModuleSummaryIndex.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -20,6 +20,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -643,6 +644,19 @@ class GlobalValueSummary { /// Return the list of values referenced by this global value definition. ArrayRef refs() const { return RefEdgeList; } + /// Erase all reference whose name is equal to Name. + bool eraseRef(StringRef Name) { + bool Erased = false; + erase_if(RefEdgeList, [&](ValueInfo VI) { + if (VI.name() == Name) { + Erased = true; + return true; + } + return false; + }); + return Erased; + } + /// If this is an alias summary, returns the summary of the aliased object (a /// global variable or function), otherwise returns itself. GlobalValueSummary *getBaseObject(); @@ -1365,6 +1379,9 @@ class ModuleSummaryIndex { std::map> TypeIdCompatibleVtableMap; + /// Type identifiers that may be accessed at run time. + SetVector TypeIdMayBeAccessed; + /// Mapping from original ID to GUID. If original ID can map to multiple /// GUIDs, it will be mapped to 0. DenseMap OidGuidMap; @@ -1875,6 +1892,12 @@ class ModuleSummaryIndex { return I->second; } + void addTypeIdAccessed(StringRef TypeId) { + TypeIdMayBeAccessed.insert(TypeId); + } + + const auto &getTypeIdAccessed() const { return TypeIdMayBeAccessed; } + /// Collect for the given module the list of functions it defines /// (GUID -> Summary). void collectDefinedFunctionsForModule(StringRef ModulePath, diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h index 242a05f7d32c0..085e6eaddc490 100644 --- a/llvm/include/llvm/LTO/LTO.h +++ b/llvm/include/llvm/LTO/LTO.h @@ -386,6 +386,8 @@ class LTO { private: Config Conf; + std::string TargetTriple; + struct RegularLTOState { RegularLTOState(unsigned ParallelCodeGenParallelismLevel, const Config &Conf); @@ -520,11 +522,17 @@ class LTO { const SymbolResolution *&ResI, const SymbolResolution *ResE); Error runRegularLTO(AddStreamFn AddStream); - Error runThinLTO(AddStreamFn AddStream, FileCache Cache, - const DenseSet &GUIDPreservedSymbols); + Error + runThinLTO(AddStreamFn AddStream, FileCache Cache, + const DenseSet &GUIDPreservedSymbols, + function_ref isPrevailing); Error checkPartiallySplit(); + std::string & getTargetTriple() { return TargetTriple; } + + void setTargetTriple(std::string TT) { TargetTriple = std::move(TT); } + mutable bool CalledGetMaxTasks = false; // LTO mode when using Unified LTO. diff --git a/llvm/include/llvm/Support/LibCXXABI.h b/llvm/include/llvm/Support/LibCXXABI.h new file mode 100644 index 0000000000000..c9f0aa99070fe --- /dev/null +++ b/llvm/include/llvm/Support/LibCXXABI.h @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// \file +/// This file provides utility functions for interacting with the libc++abi +/// runtime. +/// +/// This header defines helper functions used for handling libc++abi-specific +/// runtime operations, such as judging if a symbol name is the name of vtable, +/// type information or type name string. +/// +/// The utilities provided here are useful for transformations that require +/// analyzing or modifying libc++abi-specific constructs. +// +//===----------------------------------------------------------------------===// +// +#ifndef LLVM_SUPPORT_LIBCXXABI_H +#define LLVM_SUPPORT_LIBCXXABI_H + +#include "llvm/IR/DataLayout.h" +#include "llvm/TargetParser/Triple.h" + +namespace llvm { + + +/// Abstract interface for handling C++ ABI (Application Binary Interface) specifics. +/// +/// This class provides an abstraction for interacting with different C++ ABIs, +/// particularly in the context of name mangling and vtable layout. +/// This allows the CXX ABI-aware optimizations to correctly identify and transform +/// RTTI data and vtables. +/// +/// Note this is an abstract class that should be subclassed to provide +/// implementation details for a specific C++ ABI such as Itanium or MSVC. +class CXXABI { + + virtual const char * getVTablePrefix() = 0; + virtual const char * getTypeNamePrefix() = 0; + virtual const char * getTypeInfoPrefix() = 0; + +public: + static std::unique_ptr Create(Triple &TT); + virtual ~CXXABI() {} + + /// Return the offset from the type info slot to its address point + /// in the vtable. + virtual int64_t + getOffsetFromTypeInfoSlotToAddressPoint(const DataLayout &DT) = 0; + + bool isVTable(StringRef Name) { return Name.starts_with(getVTablePrefix()); } + bool isTypeName(StringRef Name) { + return Name.starts_with(getTypeNamePrefix()); + } + bool isTypeInfo(StringRef Name) { + return Name.starts_with(getTypeInfoPrefix()); + } + + /// Return the name of type name string from the given name of the + /// type info. + std::string getTypeNameFromTypeInfo(StringRef TypeInfo); + + /// Return the name of the type info from the given name of the vtable. + std::string getTypeInfoFromVTable(StringRef VTable); +}; + +/// Implements C++ ABI support for the Itanium ABI. +/// +/// This class provides functionality specific to the Itanium C++ ABI. +/// It extends the `CXXABI` interface to implement ABI-specific operations. +/// +/// See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti +class Itanium final : public CXXABI { + + const char * getVTablePrefix() override { return "_ZTV"; } + const char * getTypeNamePrefix() override { return "_ZTS"; } + const char * getTypeInfoPrefix() override { return "_ZTI"; } + +public: + virtual ~Itanium() {} + + int64_t + getOffsetFromTypeInfoSlotToAddressPoint(const DataLayout &DL) override { + return -2 * static_cast(DL.getPointerSize()); + } +}; + +} // namespace llvm +#endif diff --git a/llvm/include/llvm/Transforms/IPO/DeadRTTIElimination.h b/llvm/include/llvm/Transforms/IPO/DeadRTTIElimination.h new file mode 100644 index 0000000000000..906abf3d1a9ed --- /dev/null +++ b/llvm/include/llvm/Transforms/IPO/DeadRTTIElimination.h @@ -0,0 +1,21 @@ +#ifndef LLVM_TRANSFORMS_IPO_DEADRTTIELIMINATION_H +#define LLVM_TRANSFORMS_IPO_DEADRTTIELIMINATION_H + +#include "llvm/IR/ModuleSummaryIndex.h" +#include "llvm/Support/LibCXXABI.h" +#include "llvm/TargetParser/Triple.h" + +namespace llvm { +class DeadRTTIElimIndex { + ModuleSummaryIndex &ExportSummary; + std::unique_ptr ABI; + +public: + DeadRTTIElimIndex(ModuleSummaryIndex &ExportSummary, Triple &TT) + : ExportSummary(ExportSummary), ABI(CXXABI::Create(TT)) {} + + void run(); +}; +} // namespace llvm + +#endif // LLVM_TRANSFORMS_SCALAR_DEADRTTIELIMINATION_H diff --git a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp index 611d4bfbc69e8..14b6808356e18 100644 --- a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp +++ b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp @@ -52,6 +52,7 @@ #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/LibCXXABI.h" #include #include #include @@ -202,7 +203,7 @@ static void addVCallToSet( /// 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, + ModuleSummaryIndex &Index, const CallInst *CI, SetVector> &TypeTests, SetVector> &TypeTestAssumeVCalls, @@ -241,6 +242,10 @@ static void addIntrinsicToSummary( addVCallToSet(Call, Guid, TypeTestAssumeVCalls, TypeTestAssumeConstVCalls); + if (Triple(CI->getModule()->getTargetTriple()).getOS() == Triple::Linux && + hasTypeIdLoadForTypeTest(CI)) + Index.addTypeIdAccessed(TypeId->getString()); + break; } @@ -431,7 +436,7 @@ static void computeFunctionSummary( if (CalledFunction) { if (CI && CalledFunction->isIntrinsic()) { addIntrinsicToSummary( - CI, TypeTests, TypeTestAssumeVCalls, TypeCheckedLoadVCalls, + Index, CI, TypeTests, TypeTestAssumeVCalls, TypeCheckedLoadVCalls, TypeTestAssumeConstVCalls, TypeCheckedLoadConstVCalls, DT); continue; } @@ -911,6 +916,63 @@ static void setLiveRoot(ModuleSummaryIndex &Index, StringRef Name) { Summary->setLive(true); } +// Return true if the User U is reachable from a non-vtable user +// through the use-def chain. +static bool hasNonVTableUsers(const User *U, CXXABI *ABI) { + LLVM_DEBUG(dbgs() << "Check if " << *U << "has vtable users\n"); + if (isa(U)) { + // If the type info is used in dynamic_cast or exception handling, + // its user must be the instruction. + return true; + } + + // The virtual table type is either a struct of arrays. For example: + // @vtable = constant { [3 x ptr] } { [3 x ptr] [ ptr null, ptr @rtti, ptr @vf] } + // + // In this case, the user of @rtti is an anonymous ConstantArray. + // Therefore, if the user of the type information is anonymous, + // we need to perform a depth-first search (DFS) to locate its named users. + // + // And we also need to iterate its users if the current user is the type + // info global variable itself. + StringRef Name = U->getName(); + if (Name.empty() || ABI->isTypeInfo(Name)) { + for (const User *It : U->users()) + if (hasNonVTableUsers(It, ABI)) + return true; + return false; + } + + auto *GV = dyn_cast(U); + if (!GV || GV->getMetadata(LLVMContext::MD_type)) + return true; + + return false; +} + +static void analyzeRTTIVars(ModuleSummaryIndex &Index, const Module &M) { + Triple TT(M.getTargetTriple()); + + std::unique_ptr ABI = CXXABI::Create(TT); + if (!ABI) + return; + + for (const GlobalVariable &GV : M.globals()) { + if (!ABI->isTypeInfo(GV.getName())) + continue; + + if (hasNonVTableUsers(&GV, ABI.get())) { + std::string TypeName = + ABI->getTypeNameFromTypeInfo(GV.getName()); + const GlobalVariable *TypeNameGV = M.getNamedGlobal(TypeName); + if (TypeNameGV) + Index.addTypeIdAccessed(TypeNameGV->getName()); + else + Index.addTypeIdAccessed(Index.saveString(TypeName)); + } + } +} + ModuleSummaryIndex llvm::buildModuleSummaryIndex( const Module &M, std::function GetBFICallback, @@ -1019,6 +1081,8 @@ ModuleSummaryIndex llvm::buildModuleSummaryIndex( mdconst::extract_or_null(M.getModuleFlag("ThinLTO"))) IsThinLTO = MD->getZExtValue(); + analyzeRTTIVars(Index, M); + // Compute summaries for all functions defined in module, and save in the // index. for (const auto &F : M) { diff --git a/llvm/lib/Analysis/TypeMetadataUtils.cpp b/llvm/lib/Analysis/TypeMetadataUtils.cpp index 9ec0785eb5034..afd1a51148da8 100644 --- a/llvm/lib/Analysis/TypeMetadataUtils.cpp +++ b/llvm/lib/Analysis/TypeMetadataUtils.cpp @@ -17,6 +17,7 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" +#include "llvm/Support/LibCXXABI.h" using namespace llvm; @@ -50,6 +51,41 @@ findCallsAtConstantOffset(SmallVectorImpl &DevirtCalls, } } +static bool hasTypeIdLoadAtConstantOffset(const Module *M, Value *VPtr, + int64_t Offset, const CallInst *CI, + CXXABI *ABI) { + bool HasTypeIdLoad = false; + for (const Use &U : VPtr->uses()) { + Value *User = U.getUser(); + if (isa(User)) { + HasTypeIdLoad |= hasTypeIdLoadAtConstantOffset(M, User, Offset, CI, ABI); + } else if (isa(User)) { + if (Offset == + ABI->getOffsetFromTypeInfoSlotToAddressPoint(M->getDataLayout())) + return true; + } else if (auto GEP = dyn_cast(User)) { + // Take into account the GEP offset. + if (VPtr == GEP->getPointerOperand() && GEP->hasAllConstantIndices()) { + SmallVector Indices(drop_begin(GEP->operands())); + int64_t GEPOffset = M->getDataLayout().getIndexedOffsetInType( + GEP->getSourceElementType(), Indices); + HasTypeIdLoad |= + hasTypeIdLoadAtConstantOffset(M, User, Offset + GEPOffset, CI, ABI); + } + } else if (auto *Call = dyn_cast(User)) { + if (Call->getIntrinsicID() == llvm::Intrinsic::load_relative) { + if (auto *LoadOffset = dyn_cast(Call->getOperand(1))) { + HasTypeIdLoad |= + hasTypeIdLoadAtConstantOffset(M, User, Offset, CI, ABI); + } + } + } else { + HasTypeIdLoad = true; + } + } + return HasTypeIdLoad; +} + // Search for virtual calls that load from VPtr and add them to DevirtCalls. static void findLoadCallsAtConstantOffset( const Module *M, SmallVectorImpl &DevirtCalls, Value *VPtr, @@ -103,6 +139,30 @@ void llvm::findDevirtualizableCallsForTypeTest( M, DevirtCalls, CI->getArgOperand(0)->stripPointerCasts(), 0, CI, DT); } +bool llvm::hasTypeIdLoadForTypeTest(const CallInst *CI) { + assert(CI->getCalledFunction()->getIntrinsicID() == Intrinsic::type_test || + CI->getCalledFunction()->getIntrinsicID() == + Intrinsic::public_type_test); + Triple TT(CI->getModule()->getTargetTriple()); + std::unique_ptr ABI = CXXABI::Create(TT); + if (!ABI) + return false; + SmallVector Assumes; + + const Module *M = CI->getModule(); + + // Find llvm.assume intrinsics for this llvm.type.test call. + for (const Use &CIU : CI->uses()) + if (auto *Assume = dyn_cast(CIU.getUser())) + Assumes.push_back(Assume); + + if (!Assumes.empty()) + return hasTypeIdLoadAtConstantOffset( + M, CI->getArgOperand(0)->stripPointerCasts(), 0, CI, ABI.get()); + + return false; +} + void llvm::findDevirtualizableCallsForTypeCheckedLoad( SmallVectorImpl &DevirtCalls, SmallVectorImpl &LoadedPtrs, diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 5ea507c009bdc..b6d09f0992c79 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -826,6 +826,7 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(args); KEYWORD(typeid); KEYWORD(typeidCompatibleVTable); + KEYWORD(typeidMayBeAccessed); KEYWORD(summary); KEYWORD(typeTestRes); KEYWORD(kind); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index be6166f0c4169..e33ef2e13755c 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -1118,6 +1118,9 @@ bool LLParser::parseSummaryEntry() { case lltok::kw_typeidCompatibleVTable: result = parseTypeIdCompatibleVtableEntry(SummaryID); break; + case lltok::kw_typeidMayBeAccessed: + result = parseTypeIdMayBeAccessed(SummaryID); + break; case lltok::kw_flags: result = parseSummaryIndexFlags(); break; @@ -8918,6 +8921,33 @@ bool LLParser::parseTypeIdSummary(TypeIdSummary &TIS) { static ValueInfo EmptyVI = ValueInfo(false, (GlobalValueSummaryMapTy::value_type *)-8); +bool LLParser::parseTypeIdMayBeAccessed(unsigned ID) { + assert(Lex.getKind() == lltok::kw_typeidMayBeAccessed); + 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; + + Index->addTypeIdAccessed(Index->saveString(Name)); + + while (Lex.getKind() != lltok::rparen) { + if (parseToken(lltok::comma, "expected ',' here") || + parseStringConstant(Name)) + return true; + Index->addTypeIdAccessed(Index->saveString(Name)); + } + + if (parseToken(lltok::rparen, "expected ')' here")) + return true; + + return false; +} + /// TypeIdCompatibleVtableEntry /// ::= 'typeidCompatibleVTable' ':' '(' 'name' ':' STRINGCONSTANT ',' /// TypeIdCompatibleVtableInfo diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp index 56f5ff4b20e5d..434b3ef8a5867 100644 --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1015,6 +1015,7 @@ class ModuleSummaryIndexBitcodeReader : public BitcodeReaderBase { void parseTypeIdCompatibleVtableSummaryRecord(ArrayRef Record); void parseTypeIdCompatibleVtableInfo(ArrayRef Record, size_t &Slot, TypeIdCompatibleVtableInfo &TypeId); + void parseTypeIdAccessed(ArrayRef Record); std::vector parseParamAccesses(ArrayRef Record); SmallVector parseAllocInfoContext(ArrayRef Record, @@ -7575,6 +7576,14 @@ void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableInfo( TypeId.push_back({Offset, Callee}); } +void ModuleSummaryIndexBitcodeReader::parseTypeIdAccessed( + ArrayRef Record) { + for (unsigned I = 0; I < Record.size(); I += 2) { + TheIndex.addTypeIdAccessed( + {Strtab.data() + Record[I], static_cast(Record[I + 1])}); + } +} + void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableSummaryRecord( ArrayRef Record) { size_t Slot = 0; @@ -8071,6 +8080,10 @@ Error ModuleSummaryIndexBitcodeReader::parseEntireSummary(unsigned ID) { parseTypeIdCompatibleVtableSummaryRecord(Record); break; + case bitc::FS_RTTI: + parseTypeIdAccessed(Record); + break; + case bitc::FS_BLOCK_COUNT: TheIndex.addBlockCount(Record[0]); break; diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index 31c96400dd0fe..4b1fc6cf8a955 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -4601,6 +4601,12 @@ void ModuleBitcodeWriterBase::writePerModuleGlobalValueSummary() { Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); unsigned RadixAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + Abbv = std::make_shared(); + Abbv->Add(BitCodeAbbrevOp(bitc::FS_RTTI)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); + unsigned RTTIAccessedAbbrev = Stream.EmitAbbrev(std::move(Abbv)); + // First walk through all the functions and collect the allocation contexts in // their associated summaries, for use in constructing a radix tree of // contexts. Note that we need to do this in the same order as the functions @@ -4690,6 +4696,15 @@ void ModuleBitcodeWriterBase::writePerModuleGlobalValueSummary() { NameVals.clear(); } + if (!Index->getTypeIdAccessed().empty()) { + for (auto TypeId : Index->getTypeIdAccessed()) { + NameVals.push_back(StrtabBuilder.add(TypeId)); + NameVals.push_back(TypeId.size()); + } + Stream.EmitRecord(bitc::FS_RTTI, NameVals, RTTIAccessedAbbrev); + NameVals.clear(); + } + if (Index->getBlockCount()) Stream.EmitRecord(bitc::FS_BLOCK_COUNT, ArrayRef{Index->getBlockCount()}); diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp index a37a8901489cf..e4d242792247a 100644 --- a/llvm/lib/IR/AsmWriter.cpp +++ b/llvm/lib/IR/AsmWriter.cpp @@ -3128,6 +3128,18 @@ void AssemblyWriter::printModuleSummaryIndex() { Out << ") ; guid = " << GUID << "\n"; } + // Print the TypeIdMayBeAccessed entries. + if (!TheIndex->getTypeIdAccessed().empty()) { + Out << "^" << NumSlots << " = typeidMayBeAccessed: (name: "; + FieldSeparator FS; + for (auto TypeId : TheIndex->getTypeIdAccessed()) { + Out << FS; + Out << "\"" << TypeId << "\""; + } + Out << ")\n"; + ++NumSlots; + } + // Don't emit flags when it's not really needed (value is zero by default). if (TheIndex->getFlags()) { Out << "^" << NumSlots << " = flags: " << TheIndex->getFlags() << "\n"; diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp index 0f53c60851217..4791dba3591f5 100644 --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -55,6 +55,7 @@ #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/IPO/MemProfContextDisambiguation.h" #include "llvm/Transforms/IPO/WholeProgramDevirt.h" +#include "llvm/Transforms/IPO/DeadRTTIElimination.h" #include "llvm/Transforms/Utils/FunctionImportUtils.h" #include "llvm/Transforms/Utils/SplitModule.h" @@ -729,6 +730,9 @@ Error LTO::add(std::unique_ptr Input, ArrayRef Res) { assert(!CalledGetMaxTasks); + if (getTargetTriple().empty()) + setTargetTriple(Input->getTargetTriple().str()); + if (Conf.ResolutionFile) writeToResolutionFile(*Conf.ResolutionFile, Input.get(), Res); @@ -1187,8 +1191,15 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) { return PrevailingType::Unknown; return It->second; }; - computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols, - isPrevailing, Conf.OptLevel > 0); + + // For ThinLTO with split-lto-unit or FullLTO, at least one module must be + // compiled using the regular LTO pipeline. In the regular LTO pipeline, + // lowerTypeTestsModule will call isGlobalValueLive, whose result is determined here. + // Therefore, if any module is to be compiled in RegularLTO, + // we need to compute the dead symbols in advance. + if (!RegularLTO.ModsWithSummaries.empty()) + computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols, + isPrevailing, Conf.OptLevel > 0); // Setup output file to emit statistics. auto StatsFileOrErr = setupStatsFile(Conf.StatsFile); @@ -1208,7 +1219,7 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) { if (!Result) // This will reset the GlobalResolutions optional once done with it to // reduce peak memory before importing. - Result = runThinLTO(AddStream, Cache, GUIDPreservedSymbols); + Result = runThinLTO(AddStream, Cache, GUIDPreservedSymbols, isPrevailing); if (StatsFile) PrintStatisticsJSON(StatsFile->os()); @@ -1839,8 +1850,10 @@ ThinBackend lto::createWriteIndexesThinBackend( return ThinBackend(Func, Parallelism); } -Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache, - const DenseSet &GUIDPreservedSymbols) { +Error LTO::runThinLTO( + AddStreamFn AddStream, FileCache Cache, + const DenseSet &GUIDPreservedSymbols, + function_ref IsPrevailing) { LLVM_DEBUG(dbgs() << "Running ThinLTO\n"); ThinLTO.CombinedIndex.releaseTemporaryMemory(); timeTraceProfilerBegin("ThinLink", StringRef("")); @@ -1856,10 +1869,6 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache, return Error::success(); } - if (Conf.CombinedIndexHook && - !Conf.CombinedIndexHook(ThinLTO.CombinedIndex, GUIDPreservedSymbols)) - return Error::success(); - // Collect for each module the list of function it defines (GUID -> // Summary). DenseMap ModuleToDefinedGVSummaries( @@ -1920,6 +1929,18 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache, ThinLTO.CombinedIndex, WholeProgramVisibilityEnabledInLTO, DynamicExportSymbols, VisibleToRegularObjSymbols); + Triple TT(getTargetTriple()); + DeadRTTIElimIndex(ThinLTO.CombinedIndex, TT).run(); + + if (!ThinLTO.CombinedIndex.withGlobalValueDeadStripping()) + computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols, + IsPrevailing, Conf.OptLevel > 0); + + + if (Conf.CombinedIndexHook && + !Conf.CombinedIndexHook(ThinLTO.CombinedIndex, GUIDPreservedSymbols)) + return Error::success(); + // Perform index-based WPD. This will return immediately if there are // no index entries in the typeIdMetadata map (e.g. if we are instead // performing IR-based WPD in hybrid regular/thin LTO mode). diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp index 8a2dddce4892c..3ce60a7be9610 100644 --- a/llvm/lib/LTO/LTOBackend.cpp +++ b/llvm/lib/LTO/LTOBackend.cpp @@ -33,6 +33,7 @@ #include "llvm/Passes/StandardInstrumentations.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/LibCXXABI.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" @@ -573,14 +574,29 @@ static void dropDeadSymbols(Module &Mod, const GVSummaryMapTy &DefinedGlobals, convertToDeclaration(GV); } + Triple TT(Mod.getTargetTriple()); + std::unique_ptr ABI = CXXABI::Create(TT); + // Now that all dead bodies have been dropped, delete the actual objects // themselves when possible. for (GlobalValue *GV : DeadGVs) { GV->removeDeadConstantUsers(); - // Might reference something defined in native object (i.e. dropped a - // non-prevailing IR def, but we need to keep the declaration). - if (GV->use_empty()) + + // The RTTI data consists of both type information and the type name string. + // Although they are considered dead, there are still users that reference them. + // For example, the type information might be used by a vtable, and the type name + // string might be used by the type info. + // Therefore, we need to replace these uses to null pointer before erasing them. + if (ABI && (ABI->isTypeInfo(GV->getName()) || + ABI->isTypeName(GV->getName()))) { + GV->replaceAllUsesWith( + ConstantPointerNull::get(PointerType::get(Mod.getContext(), 0))); GV->eraseFromParent(); + } else if (GV->use_empty()) { + // Might reference something defined in native object (i.e. dropped a + // non-prevailing IR def, but we need to keep the declaration). + GV->eraseFromParent(); + } } } diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 2ecaea4b02bf6..50eb6ac3a5073 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -206,6 +206,7 @@ add_llvm_component_library(LLVMSupport KnownBits.cpp LEB128.cpp LineIterator.cpp + LibCXXABI.cpp Locale.cpp LockFileManager.cpp ManagedStatic.cpp diff --git a/llvm/lib/Support/LibCXXABI.cpp b/llvm/lib/Support/LibCXXABI.cpp new file mode 100644 index 0000000000000..f3b38b94d35c2 --- /dev/null +++ b/llvm/lib/Support/LibCXXABI.cpp @@ -0,0 +1,25 @@ +#include "llvm/Support/LibCXXABI.h" + +namespace llvm { + +std::unique_ptr CXXABI::Create(Triple &TT) { + if (TT.getOS() == Triple::Linux) + return std::make_unique(); + + return nullptr; +} + +std::string CXXABI::getTypeNameFromTypeInfo(StringRef TypeInfo) { + assert(TypeInfo.starts_with(getTypeInfoPrefix()) && + "TypeInfo is not starts with the correct type infor prefix"); + TypeInfo.consume_front(getTypeInfoPrefix()); + return getTypeNamePrefix() + TypeInfo.str(); +} + +std::string CXXABI::getTypeInfoFromVTable(StringRef VTable) { + assert(VTable.starts_with(getVTablePrefix()) && + "TypeInfo is not starts with the correct type infor prefix"); + VTable.consume_front(getVTablePrefix()); + return getTypeInfoPrefix() + VTable.str(); +} +} // namespace llvm diff --git a/llvm/lib/Transforms/IPO/CMakeLists.txt b/llvm/lib/Transforms/IPO/CMakeLists.txt index 15cb57399d246..6627ca72b3d95 100644 --- a/llvm/lib/Transforms/IPO/CMakeLists.txt +++ b/llvm/lib/Transforms/IPO/CMakeLists.txt @@ -10,6 +10,7 @@ add_llvm_component_library(LLVMipo ConstantMerge.cpp CrossDSOCFI.cpp DeadArgumentElimination.cpp + DeadRTTIElimination.cpp ElimAvailExtern.cpp EmbedBitcodePass.cpp ExpandVariadics.cpp diff --git a/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp b/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp new file mode 100644 index 0000000000000..2c54098200fa7 --- /dev/null +++ b/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp @@ -0,0 +1,49 @@ +#include "llvm/Transforms/IPO/DeadRTTIElimination.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/IR/ModuleSummaryIndex.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/LibCXXABI.h" + +using namespace llvm; + +#define DEBUG_TYPE "dre" + +STATISTIC(NumDeadTypeInfo, "Number of dead type info global variable"); + +void DeadRTTIElimIndex::run() { + if (!ABI) + return; + + if (ExportSummary.typeIdCompatibleVtableMap().empty()) + return; + + DenseSet TypeIdSlotMayLiveVTables; + + const auto &UsedTypeIds = ExportSummary.getTypeIdAccessed(); + for (StringRef TypeId : UsedTypeIds) { + auto Info = ExportSummary.getTypeIdCompatibleVtableSummary(TypeId); + + if (!Info.has_value()) + continue; + + for (auto CompatibleVTable : *Info) + TypeIdSlotMayLiveVTables.insert(CompatibleVTable.VTableVI.name()); + } + + for (auto &VI : ExportSummary) { + StringRef GVSName = VI.second.U.Name; + if (!ABI->isVTable(GVSName) || + TypeIdSlotMayLiveVTables.contains(GVSName) || + VI.second.SummaryList.empty()) + continue; + + auto *GVS = dyn_cast(VI.second.SummaryList[0].get()); + if (GVS && + GVS->getVCallVisibility() == llvm::GlobalObject::VCallVisibilityPublic) + continue; + + ++NumDeadTypeInfo; + for (auto &SL : VI.second.SummaryList) + SL->eraseRef(ABI->getTypeInfoFromVTable(GVSName)); + } +} diff --git a/llvm/test/Assembler/thinlto-rtti-summary.ll b/llvm/test/Assembler/thinlto-rtti-summary.ll new file mode 100644 index 0000000000000..3707edc34e718 --- /dev/null +++ b/llvm/test/Assembler/thinlto-rtti-summary.ll @@ -0,0 +1,20 @@ +; 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 -b %t2 %t3 + +target triple = "aarch64-unknown-linux-gnu" +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + +@_ZTSxxx = external global ptr +@_ZTIxxx = external global ptr +@xxx = constant [1 x ptr] [ptr @_ZTIxxx] + +^0 = module: (path: "", hash: (0, 0, 0, 0, 0)) +^1 = gv: (name: "_ZTIxxx") ; guid = 2928584540419986814 +^2 = gv: (name: "xxx", summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0, importType: definition), varFlags: (readonly: 1, writeonly: 0, constant: 1), refs: (^1)))) ; guid = 5616283335571169781 +^3 = gv: (name: "_ZTSxxx") ; guid = 16805677846636166078 +^4 = typeidMayBeAccessed: (name: "_ZTSxxx") +^5 = blockcount: 0 diff --git a/llvm/test/ThinLTO/X86/rtti-clean.ll b/llvm/test/ThinLTO/X86/rtti-clean.ll new file mode 100644 index 0000000000000..7d9b1907db35f --- /dev/null +++ b/llvm/test/ThinLTO/X86/rtti-clean.ll @@ -0,0 +1,34 @@ +; RUN: opt -thinlto-bc -o %t.o %s +; +; RUN: llvm-lto2 run %t.o -o %t2 -save-temps \ +; RUN: -r=%t.o,main,px \ +; RUN: -r=%t.o,_ZTSvt,p \ +; RUN: -r=%t.o,_ZTIvt,p \ +; RUN: -r=%t.o,_ZTVvt,p \ +; RUN: -whole-program-visibility +; RUN: llvm-dis %t2.1.1.promote.bc -o - | FileCheck %s +; + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@_ZTSvt = external constant ptr +@_ZTIvt = weak_odr constant { ptr } { ptr @_ZTSvt } + +%vtTy = type { [3 x ptr] } + +; CHECK: @_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr null, ptr @vf] }, !type !0, !vcall_visitbiliy !1 +@_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt, ptr @vf] }, !type !0, !vcall_visitbiliy !1 + +define internal void @vf() { + ret void +} + +define void @main() { + %vfunc = load ptr, ptr @_ZTVvt + ret void +} + +!0 = !{i32 16, !"_ZTSvt"} +!1 = !{i64 1} +!2 = !{i32 16, !"_ZTSvt1"} diff --git a/llvm/test/ThinLTO/X86/rtti-dont-clean.ll b/llvm/test/ThinLTO/X86/rtti-dont-clean.ll new file mode 100644 index 0000000000000..7b2069cb06a37 --- /dev/null +++ b/llvm/test/ThinLTO/X86/rtti-dont-clean.ll @@ -0,0 +1,64 @@ +; RUN: opt -thinlto-bc -o %t.o %s +; +; RUN: llvm-lto2 run %t.o -o %t2 -save-temps \ +; RUN: -r=%t.o,main,px \ +; RUN: -r=%t.o,_ZTSvt,p \ +; RUN: -r=%t.o,_ZTIvt,p \ +; RUN: -r=%t.o,_ZTVvt,p \ +; RUN: -r=%t.o,_ZTSvt1,p \ +; RUN: -r=%t.o,_ZTIvt1,p \ +; RUN: -r=%t.o,_ZTVvt1,p \ +; RUN: -r=%t.o,use,p \ +; RUN: -r=%t.o,dyncast,p \ +; RUN: -r=%t.o,__dynamic_cast,px \ +; RUN: -whole-program-visibility +; RUN: llvm-dis %t2.1.1.promote.bc -o - | FileCheck %s +; + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@_ZTSvt = external constant ptr +@_ZTIvt = weak_odr constant { ptr } { ptr @_ZTSvt } + +@_ZTSvt1 = external constant ptr +@_ZTIvt1 = weak_odr constant { ptr } { ptr @_ZTSvt1 } + +%vtTy = type { [3 x ptr] } + +; CHECK: @_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt, ptr @vf] }, !type !0, !vcall_visitbiliy !1 +@_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt, ptr @vf] }, !type !0, !vcall_visitbiliy !1 +; CHECK: @_ZTVvt1 = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt1, ptr @vf] }, !type !2, !vcall_visitbiliy !1 +@_ZTVvt1 = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt1, ptr @vf] }, !type !2, !vcall_visitbiliy !1 + +define internal void @vf() { + ret void +} + +define ptr @use(ptr %p) { + %vtable = load ptr, ptr %p + %x = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTSvt") + call void @llvm.assume(i1 %x) + %rttiptr = getelementptr i8, ptr %vtable, i64 -16 + %rtti = load ptr, ptr %rttiptr + ret ptr %rtti +} + +define ptr @dyncast(ptr %p) { + %r = call ptr @__dynamic_cast(ptr %p, ptr @_ZTIvt1, ptr @_ZTIvt1, i64 0) + ret ptr %r +} + +; Make symbol _ZTVvt and _ZTVvt1 alive. +define void @main() { + %vfunc = load ptr, ptr @_ZTVvt + %vfunc1 = load ptr, ptr @_ZTVvt1 + ret void +} + +declare ptr @__dynamic_cast(ptr, ptr, ptr, i64) +declare void @llvm.assume(i1) +declare i1 @llvm.type.test(ptr %ptr, metadata %type) nounwind readnone +!0 = !{i32 16, !"_ZTSvt"} +!1 = !{i64 1} +!2 = !{i32 16, !"_ZTSvt1"} diff --git a/llvm/test/ThinLTO/X86/rtti-no-type-metadata.ll b/llvm/test/ThinLTO/X86/rtti-no-type-metadata.ll new file mode 100644 index 0000000000000..9c68a559280f0 --- /dev/null +++ b/llvm/test/ThinLTO/X86/rtti-no-type-metadata.ll @@ -0,0 +1,32 @@ +; RUN: opt -thinlto-bc -o %t.o %s +; +; RUN: llvm-lto2 run %t.o -o %t2 -save-temps \ +; RUN: -r=%t.o,main,px \ +; RUN: -r=%t.o,_ZTSvt,p \ +; RUN: -r=%t.o,_ZTIvt,p \ +; RUN: -r=%t.o,_ZTVvt,p \ +; RUN: -whole-program-visibility +; RUN: llvm-dis %t2.1.1.promote.bc -o - | FileCheck %s +; + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@_ZTSvt = external constant ptr +@_ZTIvt = weak_odr constant { ptr } { ptr @_ZTSvt } + +%vtTy = type { [3 x ptr] } + +; CHECK: @_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt, ptr @vf] }, !vcall_visitbiliy !0 +@_ZTVvt = weak_odr constant %vtTy { [3 x ptr] [ptr null, ptr @_ZTIvt, ptr @vf] }, !vcall_visitbiliy !0 + +define internal void @vf() { + ret void +} + +define void @main() { + %vfunc = load ptr, ptr @_ZTVvt + ret void +} + +!0 = !{i64 1}