diff --git a/llvm/include/llvm/Analysis/TypeMetadataUtils.h b/llvm/include/llvm/Analysis/TypeMetadataUtils.h --- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h +++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h @@ -78,8 +78,8 @@ Constant *TopLevelGlobal = nullptr); /// Finds the same "relative pointer" pattern as described above, where the -/// target is `F`, and replaces the entire pattern with a constant zero. -void replaceRelativePointerUsersWithZero(Function *F); +/// target is `C`, and replaces the entire pattern with a constant zero. +void replaceRelativePointerUsersWithZero(Constant *C); } // namespace llvm diff --git a/llvm/lib/Analysis/TypeMetadataUtils.cpp b/llvm/lib/Analysis/TypeMetadataUtils.cpp --- a/llvm/lib/Analysis/TypeMetadataUtils.cpp +++ b/llvm/lib/Analysis/TypeMetadataUtils.cpp @@ -210,19 +210,26 @@ return nullptr; } -void llvm::replaceRelativePointerUsersWithZero(Function *F) { - for (auto *U : F->users()) { - auto *PtrExpr = dyn_cast(U); - if (!PtrExpr || PtrExpr->getOpcode() != Instruction::PtrToInt) - continue; +static void replaceRelativePointerUserWithZero(User *U) { + auto *PtrExpr = dyn_cast(U); + if (!PtrExpr || PtrExpr->getOpcode() != Instruction::PtrToInt) + return; - for (auto *PtrToIntUser : PtrExpr->users()) { - auto *SubExpr = dyn_cast(PtrToIntUser); - if (!SubExpr || SubExpr->getOpcode() != Instruction::Sub) - continue; + for (auto *PtrToIntUser : PtrExpr->users()) { + auto *SubExpr = dyn_cast(PtrToIntUser); + if (!SubExpr || SubExpr->getOpcode() != Instruction::Sub) + return; - SubExpr->replaceNonMetadataUsesWith( - ConstantInt::get(SubExpr->getType(), 0)); - } + SubExpr->replaceNonMetadataUsesWith( + ConstantInt::get(SubExpr->getType(), 0)); + } +} + +void llvm::replaceRelativePointerUsersWithZero(Constant *C) { + for (auto *U : C->users()) { + if (auto *Equiv = dyn_cast(U)) + replaceRelativePointerUsersWithZero(Equiv); + else + replaceRelativePointerUserWithZero(U); } } diff --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll @@ -2,28 +2,46 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) +declare { ptr, i1 } @llvm.type.checked.load(ptr, i32, metadata) @vtable = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [ - 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), + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc1 to i64), i64 ptrtoint (ptr @vtable to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc2 to i64), i64 ptrtoint (ptr @vtable to i64)) to i32), ; a "bad" relative pointer because it's base is not the @vtable symbol - i32 trunc (i64 sub (i64 ptrtoint (void ()* @weird_ref_1 to i64), i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (ptr @weird_ref_1 to i64), i64 ptrtoint (ptr @weird_ref_2 to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 0, !"vfunc1.type"} !1 = !{i64 4, !"vfunc2.type"} ; CHECK: @vtable = internal unnamed_addr constant { [3 x i32] } zeroinitializer, align 8, !type !0, !type !1, !vcall_visibility !2 +@vtable2 = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [ + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3 to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc4 to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32), + + ; a "bad" relative pointer because it's base is not the @vtable symbol + i32 trunc (i64 sub (i64 ptrtoint (ptr @weird_ref_3 to i64), i64 ptrtoint (ptr @weird_ref_4 to i64)) to i32) +]}, align 4, !type !3, !type !4, !vcall_visibility !{i64 2} +!3 = !{i64 0, !"vfunc3.type"} +!4 = !{i64 4, !"vfunc4.type"} + +; CHECK: @vtable2 = internal unnamed_addr constant { [3 x i32] } zeroinitializer, align 4, !type !3, !type !4, !vcall_visibility !2 + define internal void @vfunc1() { ret void } define internal void @vfunc2() { ret void } define internal void @weird_ref_1() { ret void } define internal void @weird_ref_2() { ret void } +declare void @vfunc3() +declare void @vfunc4() +declare void @weird_ref_3() +declare void @weird_ref_4() define void @main() { - %1 = ptrtoint { [3 x i32] }* @vtable to i64 ; to keep @vtable alive + %1 = ptrtoint ptr @vtable to i64 ; to keep @vtable alive call void @weird_ref_2() + %2 = ptrtoint ptr @vtable2 to i64 ; to keep @vtable2 alive + call void @weird_ref_4() ret void } diff --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll @@ -2,23 +2,37 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) +declare { ptr, i1 } @llvm.type.checked.load(ptr, i32, metadata) ; A vtable with "relative pointers", slots don't contain pointers to implementations, but instead have an i32 offset from the vtable itself to the implementation. @vtable = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ i32 42, i32 1337, - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2_dead to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc1_live to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable, i32 0, i32 0, i32 2) to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc2_dead to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable, i32 0, i32 0, i32 2) to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 8, !"vfunc1.type"} !1 = !{i64 12, !"vfunc2.type"} ; CHECK: @vtable = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ -; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc1_live to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable, i32 0, i32 0, i32 2) to i64)) to i32), ; CHECK-SAME: i32 0 ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 +@vtable2 = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ + i32 42, + i32 1337, + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3_live_extern to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable2, i32 0, i32 0, i32 2) to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc4_dead_extern to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable2, i32 0, i32 0, i32 2) to i64)) to i32) +]}, align 4, !type !3, !type !4, !vcall_visibility !{i64 2} +!3 = !{i64 8, !"vfunc3.type"} +!4 = !{i64 12, !"vfunc4.type"} + +; CHECK: @vtable2 = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3_live_extern to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32] }, ptr @vtable2, i32 0, i32 0, i32 2) to i64)) to i32), +; CHECK-SAME: i32 0 +; CHECK-SAME: ] }, align 4, !type !3, !type !4, !vcall_visibility !2 + ; (1) vfunc1_live is referenced from @main, stays alive define internal void @vfunc1_live() { ; CHECK: define internal void @vfunc1_live( @@ -31,9 +45,19 @@ ret void } +; (3) vfunc3_live_extern is referenced from @main, stays alive +; CHECK: declare void @vfunc3_live_extern +declare void @vfunc3_live_extern() + +; (4) vfunc4_dead_extern is never referenced, gets removed and vtable slot is null'd +; CHECK-NOT: declare void @vfunc4_dead_extern +declare void @vfunc4_dead_extern() + define void @main() { - %1 = ptrtoint { [4 x i32] }* @vtable to i64 ; to keep @vtable alive - %2 = tail call { i8*, i1 } @llvm.type.checked.load(i8* null, i32 0, metadata !"vfunc1.type") + %1 = ptrtoint ptr @vtable to i64 ; to keep @vtable alive + %2 = tail call { ptr, i1 } @llvm.type.checked.load(ptr null, i32 0, metadata !"vfunc1.type") + %3 = ptrtoint ptr @vtable2 to i64 ; to keep @vtable2 alive + %4 = tail call { ptr, i1 } @llvm.type.checked.load(ptr null, i32 0, metadata !"vfunc3.type") ret void } diff --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll @@ -2,21 +2,35 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) +declare { ptr, i1 } @llvm.type.checked.load(ptr, i32, metadata) ; A vtable with "relative pointers", slots don't contain pointers to implementations, but instead have an i32 offset from the vtable itself to the implementation. @vtable = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2_dead to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc1_live to i64), i64 ptrtoint (ptr @vtable to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc2_dead to i64), i64 ptrtoint (ptr @vtable to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 0, !"vfunc1.type"} !1 = !{i64 4, !"vfunc2.type"} ; CHECK: @vtable = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ -; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (ptr @vfunc1_live to i64), i64 ptrtoint (ptr @vtable to i64)) to i32), ; CHECK-SAME: i32 0 ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 +; Similar to above, but the vtable is more aligned to how C++ relative vtables look. +; That is, the functions may not be dso-local. +@vtable2 = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3_live_extern to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc4_dead_extern to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32) +]}, align 4, !type !3, !type !4, !vcall_visibility !{i64 2} +!3 = !{i64 0, !"vfunc3.type"} +!4 = !{i64 4, !"vfunc4.type"} + +; CHECK: @vtable2 = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vfunc3_live_extern to i64), i64 ptrtoint (ptr @vtable2 to i64)) to i32), +; CHECK-SAME: i32 0 +; CHECK-SAME: ] }, align 4, !type !3, !type !4, !vcall_visibility !2 + ; (1) vfunc1_live is referenced from @main, stays alive define internal void @vfunc1_live() { ; CHECK: define internal void @vfunc1_live( @@ -29,9 +43,19 @@ ret void } +; (3) vfunc3_live_extern is referenced from @main, stays alive +; CHECK: declare void @vfunc3_live_extern +declare void @vfunc3_live_extern() + +; (4) vfunc4_dead_extern is never referenced, gets removed and vtable slot is null'd +; CHECK-NOT: declare void @vfunc4_dead_extern +declare void @vfunc4_dead_extern() + define void @main() { - %1 = ptrtoint { [2 x i32] }* @vtable to i64 ; to keep @vtable alive - %2 = tail call { i8*, i1 } @llvm.type.checked.load(i8* null, i32 0, metadata !"vfunc1.type") + %1 = ptrtoint ptr @vtable to i64 ; to keep @vtable alive + %2 = tail call { ptr, i1 } @llvm.type.checked.load(ptr null, i32 0, metadata !"vfunc1.type") + %3 = ptrtoint ptr @vtable2 to i64 ; to keep @vtable2 alive + %4 = tail call { ptr, i1 } @llvm.type.checked.load(ptr null, i32 0, metadata !"vfunc3.type") ret void }