Index: llvm/docs/TypeMetadata.rst =================================================================== --- llvm/docs/TypeMetadata.rst +++ llvm/docs/TypeMetadata.rst @@ -282,8 +282,14 @@ All virtual function calls which might use this vtable are in the current module. -In addition, all function pointer loads from a vtable marked with the -``!vcall_visibility`` metadata (with a non-zero value) must be done using the +In addition, vtables marked with the ``!vcall_visibility`` metadata (with a +non-zero value) must follow these rules: + +1. All virtual function pointers in the vtable must have a matching ``!type`` +attachment. Function pointers without a ``!type`` attachment are not +participating in removal of unused function pointers. + +2. All virtual function pointer loads from a vtable must be done using the :ref:`llvm.type.checked.load ` intrinsic, so that virtual calls sites can be correlated with the vtables which they might load from. Other parts of the vtable (RTTI, offset-to-top, ...) can still be accessed with Index: llvm/include/llvm/Transforms/IPO/GlobalDCE.h =================================================================== --- llvm/include/llvm/Transforms/IPO/GlobalDCE.h +++ llvm/include/llvm/Transforms/IPO/GlobalDCE.h @@ -47,6 +47,9 @@ DenseMap, 4>> TypeIdMap; + /// VTable -> set of vfuncs in that vtable (that have !type metadata). + DenseMap> VFuncMap; + // Global variables which are vtables, and which we have enough information // about to safely do dead virtual function elimination. SmallPtrSet VFESafeVTables; Index: llvm/lib/Transforms/IPO/GlobalDCE.cpp =================================================================== --- llvm/lib/Transforms/IPO/GlobalDCE.cpp +++ llvm/lib/Transforms/IPO/GlobalDCE.cpp @@ -131,11 +131,22 @@ // complete information about all virtual call sites which could call // though this vtable, then skip it, because the call site information will // be more precise. + bool IgnoreDependency = false; + if (VFESafeVTables.count(GVU) && isa(&GV)) { + // Only ignore those deps that are in VFuncMap for this vtable (i.e. have + // an offset in one of the !type entries). + if (auto *VTable = dyn_cast(GVU)) { + IgnoreDependency = VFuncMap[VTable].count(&GV) > 0; + } + } + + if (IgnoreDependency) { LLVM_DEBUG(dbgs() << "Ignoring dep " << GVU->getName() << " -> " << GV.getName() << "\n"); continue; } + GVDependencies[GVU].insert(&GV); } } @@ -159,6 +170,7 @@ void GlobalDCEPass::ScanVTables(Module &M) { SmallVector Types; + SmallPtrSet VFuncs; LLVM_DEBUG(dbgs() << "Building type info -> vtable map\n"); auto *LTOPostLinkMD = @@ -169,6 +181,7 @@ for (GlobalVariable &GV : M.globals()) { Types.clear(); + VFuncs.clear(); GV.getMetadata(LLVMContext::MD_type, Types); if (GV.isDeclaration() || Types.empty()) continue; @@ -185,8 +198,19 @@ ->getZExtValue(); TypeIdMap[TypeID].insert(std::make_pair(&GV, Offset)); + + Constant *C = getPointerAtOffset(GV.getInitializer(), Offset, + *GV.getParent(), &GV); + C = C ? C->stripPointerCasts() : nullptr; + if (auto VFunc = dyn_cast_or_null(C)) { + VFuncs.insert(VFunc); + } } + // Record all the vfunctions that have a matching offset in one of the !type + // attributes. + VFuncMap[&GV] = VFuncs; + // If the type corresponding to the vtable is private to this translation // unit, we know that we can see all virtual functions which might use it, // so VFE is safe. @@ -439,6 +463,7 @@ GVDependencies.clear(); ComdatMembers.clear(); TypeIdMap.clear(); + VFuncMap.clear(); VFESafeVTables.clear(); if (Changed) Index: llvm/test/Transforms/GlobalDCE/virtual-functions-non-vfunc-entries.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-non-vfunc-entries.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-non-vfunc-entries.ll @@ -4,7 +4,7 @@ declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) -; A vtable that contains a non-nfunc entry, @regular_non_virtual_func. +; A vtable that contains a non-nfunc entry, @regular_non_virtual_func, which should not participate in VFE. @vtable = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [ i8* bitcast (void ()* @vfunc1_live to i8*), i8* bitcast (void ()* @vfunc2_dead to i8*), @@ -16,7 +16,7 @@ ; CHECK: @vtable = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [ ; CHECK-SAME: i8* bitcast (void ()* @vfunc1_live to i8*), ; CHECK-SAME: i8* null, -; CHECK-SAME: i8* null +; CHECK-SAME: i8* bitcast (void ()* @regular_non_virtual_func to i8*) ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 ; (1) vfunc1_live is referenced from @main, stays alive @@ -31,9 +31,9 @@ ret void } -; (3) regular, non-virtual function that just happens to be referenced from the vtable data structure +; (3) regular, non-virtual function that just happens to be referenced from the vtable data structure, should stay alive define internal void @regular_non_virtual_func() { - ; CHECK-NOT: define internal void @regular_non_virtual_func( + ; CHECK: define internal void @regular_non_virtual_func( ret void } Index: llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll @@ -8,7 +8,7 @@ i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), - ; a "bad" relative pointer because it's base is not the @vtable symbol + ; a "bad" relative pointer because it's base is not the @vtable symbol, should not be null-ed by GlobalDCE i32 trunc (i64 sub (i64 ptrtoint (void ()* @weird_ref_1 to i64), i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 0, !"vfunc1.type"} @@ -17,7 +17,7 @@ ; CHECK: @vtable = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [ ; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), ; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), -; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* @weird_ref_1 to i64), i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 define internal void @vfunc1() { ret void }