-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ThinLTO] Support dead RTTI data elimination under -fno-split-lto-unit #126336
base: main
Are you sure you want to change the base?
Conversation
This commit enhances the ThinLTO pipeline to support the elimination of unused Run-Time Type Information (RTTI) data when the `-fno-split-lto-unit` flag is used. Previously, dead RTTI data was not effectively removed, leading to larger binary sizes.
@llvm/pr-subscribers-llvm-support @llvm/pr-subscribers-llvm-ir Author: None (luxufan) ChangesThis commit enhances the ThinLTO pipeline to support the elimination of unused Run-Time Type Information (RTTI) data when the Patch is 35.63 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/126336.diff 26 Files Affected:
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 7c463f51f63dc5c..090eb4c16ce0b47 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -1592,6 +1592,7 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF,
cast<CXXRecordDecl>(SrcRecordTy->castAs<RecordType>()->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 000000000000000..9408d87495c608b
--- /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 <typeinfo>
+
+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 bdb477b54b532d0..87da08dba34c745 100644
--- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h
+++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h
@@ -51,6 +51,8 @@ void findDevirtualizableCallsForTypeTest(
SmallVectorImpl<CallInst *> &Assumes, const CallInst *CI,
DominatorTree &DT);
+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 c01de4a289a69a0..b5dbfdd24657d6c 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<uint64_t, WholeProgramDevirtResolution> &WPDResMap);
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index 7b47bc88ddb25ff..c2bdab430b68947 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 9eb38c3e4482910..41cb7a5922088c4 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 3c586a1dd21d823..717bb37685f529a 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -643,6 +643,19 @@ class GlobalValueSummary {
/// Return the list of values referenced by this global value definition.
ArrayRef<ValueInfo> 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 +1378,9 @@ class ModuleSummaryIndex {
std::map<StringRef, TypeIdCompatibleVtableInfo, std::less<>>
TypeIdCompatibleVtableMap;
+ /// Type identifiers that may be accessed at run time.
+ SmallVector<StringRef, 0> TypeIdMayBeAccessed;
+
/// Mapping from original ID to GUID. If original ID can map to multiple
/// GUIDs, it will be mapped to 0.
DenseMap<GlobalValue::GUID, GlobalValue::GUID> OidGuidMap;
@@ -1875,6 +1891,12 @@ class ModuleSummaryIndex {
return I->second;
}
+ void addTypeIdAccessed(StringRef TypeId) {
+ TypeIdMayBeAccessed.push_back(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 242a05f7d32c025..085e6eaddc4904e 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<GlobalValue::GUID> &GUIDPreservedSymbols);
+ Error
+ runThinLTO(AddStreamFn AddStream, FileCache Cache,
+ const DenseSet<GlobalValue::GUID> &GUIDPreservedSymbols,
+ function_ref<PrevailingType(GlobalValue::GUID)> 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 000000000000000..37f4b43f9584563
--- /dev/null
+++ b/llvm/include/llvm/Support/LibCXXABI.h
@@ -0,0 +1,49 @@
+#ifndef LLVM_SUPPORT_LIBCXXABI_H
+#define LLVM_SUPPORT_LIBCXXABI_H
+
+#include "llvm/IR/DataLayout.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace llvm {
+
+class CXXABI {
+
+ virtual const char * getVTablePrefix() = 0;
+ virtual const char * getTypeNamePrefix() = 0;
+ virtual const char * getTypeInfoPrefix() = 0;
+
+public:
+ static std::unique_ptr<CXXABI> Create(Triple &TT);
+ virtual ~CXXABI() {}
+ 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());
+ }
+
+ std::string getTypeNameFromTypeInfo(StringRef TypeInfo);
+ std::string getTypeInfoFromVTable(StringRef VTable);
+};
+
+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<int64_t>(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 000000000000000..906abf3d1a9ed27
--- /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<CXXABI> 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 611d4bfbc69e8fe..ec0aa81d05f8a04 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 <cassert>
#include <cstdint>
#include <vector>
@@ -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<GlobalValue::GUID, std::vector<GlobalValue::GUID>> &TypeTests,
SetVector<FunctionSummary::VFuncId, std::vector<FunctionSummary::VFuncId>>
&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,61 @@ static void setLiveRoot(ModuleSummaryIndex &Index, StringRef Name) {
Summary->setLive(true);
}
+static bool hasNonVTableUsers(const User *U, CXXABI *ABI) {
+ LLVM_DEBUG(dbgs() << "Check if " << *U << "has vtable users\n");
+ if (isa<Instruction>(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;
+ }
+
+ if (!ABI->isVTable(Name))
+ return true;
+
+ return false;
+}
+
+static void analyzeRTTIVars(ModuleSummaryIndex &Index, const Module &M) {
+ Triple TT(M.getTargetTriple());
+
+ std::unique_ptr<CXXABI> 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));
+ break;
+ }
+ }
+}
+
ModuleSummaryIndex llvm::buildModuleSummaryIndex(
const Module &M,
std::function<BlockFrequencyInfo *(const Function &F)> GetBFICallback,
@@ -1019,6 +1079,8 @@ ModuleSummaryIndex llvm::buildModuleSummaryIndex(
mdconst::extract_or_null<ConstantInt>(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 9ec0785eb5034d6..9271f9c3d5b0baa 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,42 @@ findCallsAtConstantOffset(SmallVectorImpl<DevirtCallSite> &DevirtCalls,
}
}
+static bool hasTypeIdLoadAtConstantOffset(const Module *M, Value *VPtr,
+ int64_t Offset, const CallInst *CI,
+ CXXABI *ABI) {
+ Triple TT(M->getTargetTriple());
+ bool HasTypeIdLoad = false;
+ for (const Use &U : VPtr->uses()) {
+ Value *User = U.getUser();
+ if (isa<BitCastInst>(User)) {
+ HasTypeIdLoad |= hasTypeIdLoadAtConstantOffset(M, User, Offset, CI, ABI);
+ } else if (isa<LoadInst>(User)) {
+ if (Offset ==
+ ABI->getOffsetFromTypeInfoSlotToAddressPoint(M->getDataLayout()))
+ return true;
+ } else if (auto GEP = dyn_cast<GetElementPtrInst>(User)) {
+ // Take into account the GEP offset.
+ if (VPtr == GEP->getPointerOperand() && GEP->hasAllConstantIndices()) {
+ SmallVector<Value *, 8> 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<CallInst>(User)) {
+ if (Call->getIntrinsicID() == llvm::Intrinsic::load_relative) {
+ if (auto *LoadOffset = dyn_cast<ConstantInt>(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<DevirtCallSite> &DevirtCalls, Value *VPtr,
@@ -103,6 +140,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<CXXABI> ABI = CXXABI::Create(TT);
+ if (!ABI)
+ return false;
+ SmallVector<CallInst *, 1> Assumes;
+
+ const Module *M = CI->getParent()->getParent()->getParent();
+
+ // Find llvm.assume intrinsics for this llvm.type.test call.
+ for (const Use &CIU : CI->uses())
+ if (auto *Assume = dyn_cast<AssumeInst>(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<DevirtCallSite> &DevirtCalls,
SmallVectorImpl<Instruction *> &LoadedPtrs,
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index 5ea507c009bdc61..b6d09f0992c7994 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 be6166f0c41694f..e33ef2e13755c54 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 56f5ff4b20e5dbf..434b3ef8a586702 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<uint64_t> Record);
void parseTypeIdCompatibleVtableInfo(ArrayRef<uint64_t> Record, size_t &Slot,
TypeIdCompatibleVtableInfo &TypeId);
+ void parseTypeIdAccessed(ArrayRef<uint64_t> Record);
std::vector<FunctionSummary::ParamAccess>
parseParamAccesses(ArrayRef<uint64_t> Record);
SmallVector<unsigned> parseAllocInfoContext(ArrayRef<uint64_t> Record,
@@ -7575,6 +7576,14 @@ void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableInfo...
[truncated]
|
@llvm/pr-subscribers-llvm-transforms Author: None (luxufan) ChangesThis commit enhances the ThinLTO pipeline to support the elimination of unused Run-Time Type Information (RTTI) data when the Patch is 35.63 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/126336.diff 26 Files Affected:
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 7c463f51f63dc5c..090eb4c16ce0b47 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -1592,6 +1592,7 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF,
cast<CXXRecordDecl>(SrcRecordTy->castAs<RecordType>()->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 000000000000000..9408d87495c608b
--- /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 <typeinfo>
+
+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 bdb477b54b532d0..87da08dba34c745 100644
--- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h
+++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h
@@ -51,6 +51,8 @@ void findDevirtualizableCallsForTypeTest(
SmallVectorImpl<CallInst *> &Assumes, const CallInst *CI,
DominatorTree &DT);
+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 c01de4a289a69a0..b5dbfdd24657d6c 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<uint64_t, WholeProgramDevirtResolution> &WPDResMap);
diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h
index 7b47bc88ddb25ff..c2bdab430b68947 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 9eb38c3e4482910..41cb7a5922088c4 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 3c586a1dd21d823..717bb37685f529a 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -643,6 +643,19 @@ class GlobalValueSummary {
/// Return the list of values referenced by this global value definition.
ArrayRef<ValueInfo> 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 +1378,9 @@ class ModuleSummaryIndex {
std::map<StringRef, TypeIdCompatibleVtableInfo, std::less<>>
TypeIdCompatibleVtableMap;
+ /// Type identifiers that may be accessed at run time.
+ SmallVector<StringRef, 0> TypeIdMayBeAccessed;
+
/// Mapping from original ID to GUID. If original ID can map to multiple
/// GUIDs, it will be mapped to 0.
DenseMap<GlobalValue::GUID, GlobalValue::GUID> OidGuidMap;
@@ -1875,6 +1891,12 @@ class ModuleSummaryIndex {
return I->second;
}
+ void addTypeIdAccessed(StringRef TypeId) {
+ TypeIdMayBeAccessed.push_back(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 242a05f7d32c025..085e6eaddc4904e 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<GlobalValue::GUID> &GUIDPreservedSymbols);
+ Error
+ runThinLTO(AddStreamFn AddStream, FileCache Cache,
+ const DenseSet<GlobalValue::GUID> &GUIDPreservedSymbols,
+ function_ref<PrevailingType(GlobalValue::GUID)> 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 000000000000000..37f4b43f9584563
--- /dev/null
+++ b/llvm/include/llvm/Support/LibCXXABI.h
@@ -0,0 +1,49 @@
+#ifndef LLVM_SUPPORT_LIBCXXABI_H
+#define LLVM_SUPPORT_LIBCXXABI_H
+
+#include "llvm/IR/DataLayout.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace llvm {
+
+class CXXABI {
+
+ virtual const char * getVTablePrefix() = 0;
+ virtual const char * getTypeNamePrefix() = 0;
+ virtual const char * getTypeInfoPrefix() = 0;
+
+public:
+ static std::unique_ptr<CXXABI> Create(Triple &TT);
+ virtual ~CXXABI() {}
+ 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());
+ }
+
+ std::string getTypeNameFromTypeInfo(StringRef TypeInfo);
+ std::string getTypeInfoFromVTable(StringRef VTable);
+};
+
+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<int64_t>(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 000000000000000..906abf3d1a9ed27
--- /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<CXXABI> 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 611d4bfbc69e8fe..ec0aa81d05f8a04 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 <cassert>
#include <cstdint>
#include <vector>
@@ -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<GlobalValue::GUID, std::vector<GlobalValue::GUID>> &TypeTests,
SetVector<FunctionSummary::VFuncId, std::vector<FunctionSummary::VFuncId>>
&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,61 @@ static void setLiveRoot(ModuleSummaryIndex &Index, StringRef Name) {
Summary->setLive(true);
}
+static bool hasNonVTableUsers(const User *U, CXXABI *ABI) {
+ LLVM_DEBUG(dbgs() << "Check if " << *U << "has vtable users\n");
+ if (isa<Instruction>(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;
+ }
+
+ if (!ABI->isVTable(Name))
+ return true;
+
+ return false;
+}
+
+static void analyzeRTTIVars(ModuleSummaryIndex &Index, const Module &M) {
+ Triple TT(M.getTargetTriple());
+
+ std::unique_ptr<CXXABI> 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));
+ break;
+ }
+ }
+}
+
ModuleSummaryIndex llvm::buildModuleSummaryIndex(
const Module &M,
std::function<BlockFrequencyInfo *(const Function &F)> GetBFICallback,
@@ -1019,6 +1079,8 @@ ModuleSummaryIndex llvm::buildModuleSummaryIndex(
mdconst::extract_or_null<ConstantInt>(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 9ec0785eb5034d6..9271f9c3d5b0baa 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,42 @@ findCallsAtConstantOffset(SmallVectorImpl<DevirtCallSite> &DevirtCalls,
}
}
+static bool hasTypeIdLoadAtConstantOffset(const Module *M, Value *VPtr,
+ int64_t Offset, const CallInst *CI,
+ CXXABI *ABI) {
+ Triple TT(M->getTargetTriple());
+ bool HasTypeIdLoad = false;
+ for (const Use &U : VPtr->uses()) {
+ Value *User = U.getUser();
+ if (isa<BitCastInst>(User)) {
+ HasTypeIdLoad |= hasTypeIdLoadAtConstantOffset(M, User, Offset, CI, ABI);
+ } else if (isa<LoadInst>(User)) {
+ if (Offset ==
+ ABI->getOffsetFromTypeInfoSlotToAddressPoint(M->getDataLayout()))
+ return true;
+ } else if (auto GEP = dyn_cast<GetElementPtrInst>(User)) {
+ // Take into account the GEP offset.
+ if (VPtr == GEP->getPointerOperand() && GEP->hasAllConstantIndices()) {
+ SmallVector<Value *, 8> 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<CallInst>(User)) {
+ if (Call->getIntrinsicID() == llvm::Intrinsic::load_relative) {
+ if (auto *LoadOffset = dyn_cast<ConstantInt>(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<DevirtCallSite> &DevirtCalls, Value *VPtr,
@@ -103,6 +140,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<CXXABI> ABI = CXXABI::Create(TT);
+ if (!ABI)
+ return false;
+ SmallVector<CallInst *, 1> Assumes;
+
+ const Module *M = CI->getParent()->getParent()->getParent();
+
+ // Find llvm.assume intrinsics for this llvm.type.test call.
+ for (const Use &CIU : CI->uses())
+ if (auto *Assume = dyn_cast<AssumeInst>(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<DevirtCallSite> &DevirtCalls,
SmallVectorImpl<Instruction *> &LoadedPtrs,
diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp
index 5ea507c009bdc61..b6d09f0992c7994 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 be6166f0c41694f..e33ef2e13755c54 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 56f5ff4b20e5dbf..434b3ef8a586702 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<uint64_t> Record);
void parseTypeIdCompatibleVtableInfo(ArrayRef<uint64_t> Record, size_t &Slot,
TypeIdCompatibleVtableInfo &TypeId);
+ void parseTypeIdAccessed(ArrayRef<uint64_t> Record);
std::vector<FunctionSummary::ParamAccess>
parseParamAccesses(ArrayRef<uint64_t> Record);
SmallVector<unsigned> parseAllocInfoContext(ArrayRef<uint64_t> Record,
@@ -7575,6 +7576,14 @@ void ModuleSummaryIndexBitcodeReader::parseTypeIdCompatibleVtableInfo...
[truncated]
|
You can test this locally with the following command:git-clang-format --diff 4294fe173e9696a4c090857fa0766cd44c1964dc f308fbee5403ded80229895fd46440cc5cff3530 --extensions h,cpp -- clang/test/CodeGenCXX/typeid-type-test.cpp llvm/include/llvm/Support/LibCXXABI.h llvm/include/llvm/Transforms/IPO/DeadRTTIElimination.h llvm/lib/Support/LibCXXABI.cpp llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp clang/lib/CodeGen/ItaniumCXXABI.cpp llvm/include/llvm/Analysis/TypeMetadataUtils.h llvm/include/llvm/AsmParser/LLParser.h llvm/include/llvm/AsmParser/LLToken.h llvm/include/llvm/Bitcode/LLVMBitCodes.h llvm/include/llvm/IR/ModuleSummaryIndex.h llvm/include/llvm/LTO/LTO.h llvm/lib/Analysis/ModuleSummaryAnalysis.cpp llvm/lib/Analysis/TypeMetadataUtils.cpp llvm/lib/AsmParser/LLLexer.cpp llvm/lib/AsmParser/LLParser.cpp llvm/lib/Bitcode/Reader/BitcodeReader.cpp llvm/lib/Bitcode/Writer/BitcodeWriter.cpp llvm/lib/IR/AsmWriter.cpp llvm/lib/LTO/LTO.cpp llvm/lib/LTO/LTOBackend.cpp View the diff from clang-format here.diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h
index 8650c6a124..83fc96ad18 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -18,9 +18,9 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SetVector.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"
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 085e6eaddc..73b8974b35 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -529,7 +529,7 @@ private:
Error checkPartiallySplit();
- std::string & getTargetTriple() { return TargetTriple; }
+ std::string &getTargetTriple() { return TargetTriple; }
void setTargetTriple(std::string TT) { TargetTriple = std::move(TT); }
diff --git a/llvm/include/llvm/Support/LibCXXABI.h b/llvm/include/llvm/Support/LibCXXABI.h
index c9f0aa9907..2baa89156a 100644
--- a/llvm/include/llvm/Support/LibCXXABI.h
+++ b/llvm/include/llvm/Support/LibCXXABI.h
@@ -27,21 +27,21 @@
namespace llvm {
-
-/// Abstract interface for handling C++ ABI (Application Binary Interface) specifics.
+/// 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.
+/// 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;
+ virtual const char *getVTablePrefix() = 0;
+ virtual const char *getTypeNamePrefix() = 0;
+ virtual const char *getTypeInfoPrefix() = 0;
public:
static std::unique_ptr<CXXABI> Create(Triple &TT);
@@ -76,9 +76,9 @@ public:
/// 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"; }
+ const char *getVTablePrefix() override { return "_ZTV"; }
+ const char *getTypeNamePrefix() override { return "_ZTS"; }
+ const char *getTypeInfoPrefix() override { return "_ZTI"; }
public:
virtual ~Itanium() {}
diff --git a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
index 14b6808356..9df4862c10 100644
--- a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
+++ b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
@@ -927,7 +927,8 @@ static bool hasNonVTableUsers(const User *U, CXXABI *ABI) {
}
// 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] }
+ // @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,
@@ -962,8 +963,7 @@ static void analyzeRTTIVars(ModuleSummaryIndex &Index, const Module &M) {
continue;
if (hasNonVTableUsers(&GV, ABI.get())) {
- std::string TypeName =
- ABI->getTypeNameFromTypeInfo(GV.getName());
+ std::string TypeName = ABI->getTypeNameFromTypeInfo(GV.getName());
const GlobalVariable *TypeNameGV = M.getNamedGlobal(TypeName);
if (TypeNameGV)
Index.addTypeIdAccessed(TypeNameGV->getName());
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 4791dba359..8749b635dd 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -53,9 +53,9 @@
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Transforms/IPO.h"
+#include "llvm/Transforms/IPO/DeadRTTIElimination.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"
@@ -1194,8 +1194,8 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
// 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,
+ // 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,
@@ -1936,7 +1936,6 @@ Error LTO::runThinLTO(
computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols,
IsPrevailing, Conf.OptLevel > 0);
-
if (Conf.CombinedIndexHook &&
!Conf.CombinedIndexHook(ThinLTO.CombinedIndex, GUIDPreservedSymbols))
return Error::success();
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 3ce60a7be9..cccef945fe 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -583,12 +583,12 @@ static void dropDeadSymbols(Module &Mod, const GVSummaryMapTy &DefinedGlobals,
GV->removeDeadConstantUsers();
// 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()))) {
+ // 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();
diff --git a/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp b/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp
index 2c54098200..7d9c15b231 100644
--- a/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp
+++ b/llvm/lib/Transforms/IPO/DeadRTTIElimination.cpp
@@ -32,8 +32,7 @@ void DeadRTTIElimIndex::run() {
for (auto &VI : ExportSummary) {
StringRef GVSName = VI.second.U.Name;
- if (!ABI->isVTable(GVSName) ||
- TypeIdSlotMayLiveVTables.contains(GVSName) ||
+ if (!ABI->isVTable(GVSName) || TypeIdSlotMayLiveVTables.contains(GVSName) ||
VI.second.SummaryList.empty())
continue;
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the work! Do you have some stats with this change applied?
target triple = "aarch64-unknown-linux-gnu" | ||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" | ||
|
||
@_ZTSxxx = external global ptr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind describing how this PR handles vtables with local linkage (in PR description), and extend this regression test for local-linkage vtables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File thinlto-rtti-summary.ll only verifies whether the potentially accessed RTTI data is recorded in the summary. It does not include vtables. Do you mean this file rtti-clean.ll?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean this file rtti-clean.ll?
Yes, rtti-clean.ll
is a good place to have test coverage for local-linkage vtables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still looking through the rest. I'll also patch this change and give it a try.
|
||
std::unique_ptr<CXXABI> CXXABI::Create(Triple &TT) { | ||
if (TT.getOS() == Triple::Linux) | ||
return std::make_unique<Itanium>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning Itanium class for Linux, I wonder if it makes sense to reuse the existing Itanium entry in Triple in a way.
Itanium, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the Itanium entry in Triple is used, enabling this optimization requires the target triple string to be x86_64-itanium-linux-gnu. However, I couldn't find any use cases where --target=x86_64-itanium-linux-gnu is explicitly specified.
Thank you for your review! I currently only have statistics for the implementation for -fsplit-lto-unit, as I have not yet evaluated -fno-split-lto-unit in our benchmark. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sending the PR! I think this should enable some nice size savings hopefully.
I went through most of it (though not the bitcode or llvm assembler changes yet). A couple of concerns:
- It doesn't look like it is guarded on whole program visibility? Maybe I missed that check. See WholeProgramVisibilityEnabledInLTO in LTO.cpp.
- It would be good to avoid having to add the CXXABI handling in LLVM and the TargetTriple/OS checks in LTO - I've added some suggestions on how we can perhaps do that below in the code.
- I'd like to avoid removing references from the index, suggestion on that in the code too.
@@ -1592,6 +1592,7 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF, | |||
cast<CXXRecordDecl>(SrcRecordTy->castAs<RecordType>()->getDecl()); | |||
llvm::Value *Value = CGF.GetVTablePtr(ThisPtr, CGM.GlobalsInt8PtrTy, | |||
ClassDecl); | |||
CGF.EmitTypeMetadataCodeForVCall(ClassDecl, Value, SourceLocation()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EmitTypeMetadataCodeForVCall does some special handling if CFI is enabled. Is it correct to do that handling here? Alternatively, since this isn't a vcall, maybe refactor out the type test insertion from this method and call only that here. Also add a comment here (in particular with the current function name it is confusing since this isn't a vcall).
Should this handling be added to MicrosoftCXXABI as well? Asking because I notice that currently EmitTypeMetadataCodeForVCall is invoked for both ABIs. Alternatively, add a TODO there?
HasTypeIdLoad |= hasTypeIdLoadAtConstantOffset(M, User, Offset, CI, ABI); | ||
} else if (isa<LoadInst>(User)) { | ||
if (Offset == | ||
ABI->getOffsetFromTypeInfoSlotToAddressPoint(M->getDataLayout())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For WPD and CFI this the information about type tests and the vtable address points are correlated from the summary during the thin link. Can we do that for this optimization as well so that we don't need to put info about the ABI in LLVM? E.g. see how we summarize the potential virtual calls for each type test. Alternatively, since the presence of a possible type id load results in conservative behavior, we may not even need to know the exact offset in this analysis.
|
||
if (!ThinLTO.CombinedIndex.withGlobalValueDeadStripping()) | ||
computeDeadSymbolsWithConstProp(ThinLTO.CombinedIndex, GUIDPreservedSymbols, | ||
IsPrevailing, Conf.OptLevel > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just move up and use the isPrevailing lambda defined a bit further below here (line 1946)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we can, but it would require refactoring the computeDeadSymbolsWithConstProp function. Because the signature of these two isPrevailing functions are different
IsPrevailing, Conf.OptLevel > 0); | ||
|
||
|
||
if (Conf.CombinedIndexHook && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this change, some existing test cases would fail. This is because the patch alters the position of the dead symbol analysis (moving it after DeadRTTIElimIndex). So this change makes the dumped index file still include information about dead symbols.
|
||
++NumDeadTypeInfo; | ||
for (auto &SL : VI.second.SummaryList) | ||
SL->eraseRef(ABI->getTypeInfoFromVTable(GVSName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love removing references from the Index. Can this handling just be incorporated into computeDeadSymbolsWithConstProp?
|
||
if (hasNonVTableUsers(&GV, ABI.get())) { | ||
std::string TypeName = | ||
ABI->getTypeNameFromTypeInfo(GV.getName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In WPD we simply hardcode these prefixes - the assumption is if the name starts with one prefix, we can assume what the other is. See
llvm-project/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
Lines 831 to 839 in c665480
if (!TypeID.consume_front("_ZTS")) | |
return false; | |
// TypeID is keyed off the type name symbol (_ZTS). However, the native | |
// object may not contain this symbol if it does not contain a key | |
// function for the base type and thus only contains a reference to the | |
// type info (_ZTI). To catch this case we query using the type info | |
// symbol corresponding to the TypeID. | |
std::string typeInfo = ("_ZTI" + TypeID).str(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I use a hardcoded approach in this patch, there will be multiple places where this kind of hardcoding is needed, which, in my opinion, would make the code less maintainable and harder to read. What do you think?
// 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens without this change? Can it just be done unconditionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the RTTI GlobalVariable has been inferred as dead, the vtable still references it. Without this change, the RTTI GlobalVariable cannot be eliminated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this patch on an internal data center application and it reduced the .data.rel.ro
section size by 3.69% and the number of _ZTS
or _ZTI
prefixed entries by ~27% ~ 28%.
Despite the good size savings, the numbers make me curious about what are the remaining ~73% ~ 74% entries. Taking a further look shows these preserved RTTIs come from vtables whose superclass has its type-id accessed.
Thank you for helping evaluate this patch. I am curious about what these type IDs is used for—is it for dynamic_cast, exception handling, or the typeid operator? |
This commit enhances the ThinLTO pipeline to support the elimination of unused Run-Time Type Information (RTTI) data when the
-fno-split-lto-unit
flag is used. Previously, dead RTTI data was not effectively removed, leading to larger binary sizes.