Skip to content
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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions clang/lib/CodeGen/ItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Copy link
Contributor

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?


if (CGM.getItaniumVTableContext().isRelativeLayout()) {
// Load the type info.
Expand Down
32 changes: 32 additions & 0 deletions clang/test/CodeGenCXX/typeid-type-test.cpp
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);
}

}
4 changes: 4 additions & 0 deletions llvm/include/llvm/Analysis/TypeMetadataUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ void findDevirtualizableCallsForTypeTest(
SmallVectorImpl<CallInst *> &Assumes, const CallInst *CI,
DominatorTree &DT);

/// Given a call to the intrinsic \@llvm.type.test, return true if a type id
/// load exists that associated with this intrinsic call.
bool hasTypeIdLoadForTypeTest(const CallInst *CI);

luxufan marked this conversation as resolved.
Show resolved Hide resolved
/// 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(
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/AsmParser/LLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/AsmParser/LLToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ enum Kind {
kw_args,
kw_typeid,
kw_typeidCompatibleVTable,
kw_typeidMayBeAccessed,
kw_summary,
kw_typeTestRes,
kw_kind,
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/Bitcode/LLVMBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions llvm/include/llvm/IR/ModuleSummaryIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -643,6 +644,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();
Expand Down Expand Up @@ -1365,6 +1379,9 @@ class ModuleSummaryIndex {
std::map<StringRef, TypeIdCompatibleVtableInfo, std::less<>>
TypeIdCompatibleVtableMap;

/// Type identifiers that may be accessed at run time.
SetVector<StringRef> 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;
Expand Down Expand Up @@ -1875,6 +1892,12 @@ class ModuleSummaryIndex {
return I->second;
}

void addTypeIdAccessed(StringRef TypeId) {
TypeIdMayBeAccessed.insert(TypeId);
}

const auto &getTypeIdAccessed() const { return TypeIdMayBeAccessed; }

/// Collect for the given module the list of functions it defines
/// (GUID -> Summary).
void collectDefinedFunctionsForModule(StringRef ModulePath,
Expand Down
12 changes: 10 additions & 2 deletions llvm/include/llvm/LTO/LTO.h
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ class LTO {
private:
Config Conf;

std::string TargetTriple;

struct RegularLTOState {
RegularLTOState(unsigned ParallelCodeGenParallelismLevel,
const Config &Conf);
Expand Down Expand Up @@ -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.
Expand Down
93 changes: 93 additions & 0 deletions llvm/include/llvm/Support/LibCXXABI.h
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
21 changes: 21 additions & 0 deletions llvm/include/llvm/Transforms/IPO/DeadRTTIElimination.h
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
68 changes: 66 additions & 2 deletions llvm/lib/Analysis/ModuleSummaryAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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());
Copy link
Contributor

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

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();
. If we can avoid adding all the CXXABI handling to LLVM and the TargetTriple/OS checks to LTO that would be ideal

Copy link
Contributor Author

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?

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,
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading