-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[ThinLTO] Support dead RTTI data elimination under -fno-split-lto-unit #126336
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
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 6df524e3c8c2cf3cc02c425f52996ae9e75ffa25 --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/Support/LibCXXABI.cpp b/llvm/lib/Support/LibCXXABI.cpp
index 469b14bf3b..2f41db0de6 100644
--- a/llvm/lib/Support/LibCXXABI.cpp
+++ b/llvm/lib/Support/LibCXXABI.cpp
@@ -4,7 +4,7 @@ namespace llvm {
std::unique_ptr<CXXABI> CXXABI::Create(Triple &TT) {
// TODO: Introduce new module flags to distinguish between the
- // Itanium and Microsoft C++ ABIs. In this factory method,
+ // Itanium and Microsoft C++ ABIs. In this factory method,
// check the flag to return the corresponding CXXABI object.
return std::make_unique<Itanium>();
}
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.
llvm/lib/Support/LibCXXABI.cpp
Outdated
|
||
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.
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.
This is overly restrictive, however, since Linux is not the only user of the Itanium C++ ABI. See my suggestion in another comment reply about passing this down from clang.
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.
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?
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.
Perhaps we can centralize this somewhere, but in a slightly different way. As noted in another of my follow on comments in this reply, selecting the Itanium ABI just for Linux is too restrictive. A couple of possibilities:
- Set the ABI in the front end on a module flag (i.e. Itanium or Microsoft), and query that in CXXABI::Create. I.e. see uses in Clang of Module::addModuleFlag.
- Centralize somewhere the deduction like your getTypeNameFromTypeInfo, but hardcoded to the Itanium ABI conversion like we are doing in WPD - and something like a "hasKnownABI" (not sure that is the best name though) that checks if the prefix is recognized and expect users to guard the handling by that, and assert it is true in the conversion functions. This would be analogous to what is currently happening in WPD but refactored out of there as well.
Doing 2 might be a temporary solution with a TODO to do 1.
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.
Option 2 is similar to unconditionally returning the Itanium CXXABI in CXXABI::Create. For now, I proceeded with this approach and added a TODO to introduce a module flag that can distinguish between the Itanium and Microsoft ABIs. This flag will later be used to determine which ABI is currently in use. If this approach does not align with your expectations, please feel free to let me know, and I’d be happy to adjust accordingly
// 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? |
return hasTypeIdLoadAtConstantOffset( | ||
M, CI->getArgOperand(0)->stripPointerCasts(), 0, CI, ABI.get()); | ||
|
||
return false; |
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.
Could you elaborate the reason that llvm.type.test
or llvm.public.type.test
are used to discover type-id loads from the program?
@@ -50,6 +51,41 @@ findCallsAtConstantOffset(SmallVectorImpl<DevirtCallSite> &DevirtCalls, | |||
} | |||
} | |||
|
|||
static bool hasTypeIdLoadAtConstantOffset(const Module *M, Value *VPtr, | |||
int64_t Offset, const CallInst *CI, |
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.
Could you describe the purpose of this function at a high level, and add some comments along with the different cases?
} | ||
} else { | ||
HasTypeIdLoad = true; | ||
} |
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.
https://gcc.godbolt.org/z/oczv8zhe5 has a reduced IR test case, and specifically, _ZTS1C
is considered an accessed type id when %15 = phi ptr [ %11, %9 ], [ %7, %5 ]
hits line 83, and the relevant IR lines are commented below to show how this happens.
9: ; preds = %2
%10 = tail call noundef ptr @_Z7createCv() #4
# The vtable pointer
%11 = load ptr, ptr %10, align 8, !tbaa !8
%12 = tail call i1 @llvm.public.type.test(ptr %11, metadata !"_ZTS1C")
tail call void @llvm.assume(i1 %12)
br label %13
13: ; preds = %9, %5
# vtable pointer %11 is used by phi, and 'HasTypeIdLoad' is set to true.
%15 = phi ptr [ %11, %9 ], [ %7, %5 ]
%16 = phi ptr [ %10, %9 ], [ %6, %5 ]
Basically, when type.test
or type.public.test
captures a type-id (presumably not uncommon where virtual calls are made), it's likely that its type-id (call it _ZTS3Foo
) are considered 'accessed' when the compiler runs at line 83 (seemingly a fallback case). Combined with the type-hierarchy handling, the subtree rooted under _ZTS3Foo
will have its RTTI and type name string preserved.
This accounts for a majority of preserved RTTI in the evaluated benchmark (#126336 (comment)).
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.
Thank you for testing this patch and providing the test case. I have fixed the issue and added it as a test case.
I have two findings after some investigation
|
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.