-
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?
Changes from all commits
83b532a
0d5c7e4
9dea1da
8a91bb3
e514838
843d6c5
7d9f347
2e8ae22
774d268
b0b4238
03034c9
69d3fdb
e6a3606
f308fbe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
/// \file | ||
/// This file provides utility functions for interacting with the libc++abi | ||
/// runtime. | ||
/// | ||
/// This header defines helper functions used for handling libc++abi-specific | ||
/// runtime operations, such as judging if a symbol name is the name of vtable, | ||
/// type information or type name string. | ||
/// | ||
/// The utilities provided here are useful for transformations that require | ||
/// analyzing or modifying libc++abi-specific constructs. | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
#ifndef LLVM_SUPPORT_LIBCXXABI_H | ||
#define LLVM_SUPPORT_LIBCXXABI_H | ||
|
||
#include "llvm/IR/DataLayout.h" | ||
#include "llvm/TargetParser/Triple.h" | ||
|
||
namespace llvm { | ||
|
||
|
||
/// Abstract interface for handling C++ ABI (Application Binary Interface) specifics. | ||
/// | ||
/// This class provides an abstraction for interacting with different C++ ABIs, | ||
/// particularly in the context of name mangling and vtable layout. | ||
/// This allows the CXX ABI-aware optimizations to correctly identify and transform | ||
/// RTTI data and vtables. | ||
/// | ||
/// Note this is an abstract class that should be subclassed to provide | ||
/// implementation details for a specific C++ ABI such as Itanium or MSVC. | ||
class CXXABI { | ||
|
||
virtual const char * getVTablePrefix() = 0; | ||
virtual const char * getTypeNamePrefix() = 0; | ||
virtual const char * getTypeInfoPrefix() = 0; | ||
|
||
public: | ||
static std::unique_ptr<CXXABI> Create(Triple &TT); | ||
virtual ~CXXABI() {} | ||
|
||
/// Return the offset from the type info slot to its address point | ||
/// in the vtable. | ||
virtual int64_t | ||
getOffsetFromTypeInfoSlotToAddressPoint(const DataLayout &DT) = 0; | ||
|
||
bool isVTable(StringRef Name) { return Name.starts_with(getVTablePrefix()); } | ||
bool isTypeName(StringRef Name) { | ||
return Name.starts_with(getTypeNamePrefix()); | ||
} | ||
bool isTypeInfo(StringRef Name) { | ||
return Name.starts_with(getTypeInfoPrefix()); | ||
} | ||
|
||
/// Return the name of type name string from the given name of the | ||
/// type info. | ||
std::string getTypeNameFromTypeInfo(StringRef TypeInfo); | ||
|
||
/// Return the name of the type info from the given name of the vtable. | ||
std::string getTypeInfoFromVTable(StringRef VTable); | ||
}; | ||
|
||
/// Implements C++ ABI support for the Itanium ABI. | ||
/// | ||
/// This class provides functionality specific to the Itanium C++ ABI. | ||
/// It extends the `CXXABI` interface to implement ABI-specific operations. | ||
/// | ||
/// See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti | ||
class Itanium final : public CXXABI { | ||
|
||
const char * getVTablePrefix() override { return "_ZTV"; } | ||
const char * getTypeNamePrefix() override { return "_ZTS"; } | ||
const char * getTypeInfoPrefix() override { return "_ZTI"; } | ||
|
||
public: | ||
virtual ~Itanium() {} | ||
|
||
int64_t | ||
getOffsetFromTypeInfoSlotToAddressPoint(const DataLayout &DL) override { | ||
return -2 * static_cast<int64_t>(DL.getPointerSize()); | ||
} | ||
}; | ||
|
||
} // namespace llvm | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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,63 @@ static void setLiveRoot(ModuleSummaryIndex &Index, StringRef Name) { | |||||||||||||||||||
Summary->setLive(true); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// Return true if the User U is reachable from a non-vtable user | ||||||||||||||||||||
// through the use-def chain. | ||||||||||||||||||||
static bool hasNonVTableUsers(const User *U, CXXABI *ABI) { | ||||||||||||||||||||
LLVM_DEBUG(dbgs() << "Check if " << *U << "has vtable users\n"); | ||||||||||||||||||||
if (isa<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; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
auto *GV = dyn_cast<GlobalVariable>(U); | ||||||||||||||||||||
if (!GV || GV->getMetadata(LLVMContext::MD_type)) | ||||||||||||||||||||
return true; | ||||||||||||||||||||
luxufan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
|
||||||||||||||||||||
return false; | ||||||||||||||||||||
luxufan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
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()); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||||||||||||||||||||
const GlobalVariable *TypeNameGV = M.getNamedGlobal(TypeName); | ||||||||||||||||||||
if (TypeNameGV) | ||||||||||||||||||||
Index.addTypeIdAccessed(TypeNameGV->getName()); | ||||||||||||||||||||
else | ||||||||||||||||||||
Index.addTypeIdAccessed(Index.saveString(TypeName)); | ||||||||||||||||||||
} | ||||||||||||||||||||
luxufan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
ModuleSummaryIndex llvm::buildModuleSummaryIndex( | ||||||||||||||||||||
const Module &M, | ||||||||||||||||||||
std::function<BlockFrequencyInfo *(const Function &F)> GetBFICallback, | ||||||||||||||||||||
|
@@ -1019,6 +1081,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) { | ||||||||||||||||||||
|
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?