From 7329abf2f81a0432bd0a9e7dec0c55b692fc72e5 Mon Sep 17 00:00:00 2001 From: Kuba Mracek Date: Wed, 6 Oct 2021 15:55:00 -0700 Subject: [PATCH] [GlobalDCE] In VFE, replace the whole 'sub' expression of unused relative-pointer-based vtable slots Differential Revision: https://reviews.llvm.org/D109114 --- llvm/include/llvm/Analysis/TypeMetadataUtils.h | 42 ++++++++++++---------- llvm/lib/Analysis/TypeMetadataUtils.cpp | 17 +++++++++ llvm/lib/Transforms/IPO/GlobalDCE.cpp | 10 ++++++ .../Transforms/GlobalDCE/call-with-ptrtoint.ll | 21 +++++++++++ .../virtual-functions-relative-pointers-bad.ll | 6 +--- .../virtual-functions-relative-pointers-gep.ll | 2 +- .../virtual-functions-relative-pointers.ll | 2 +- 7 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 llvm/test/Transforms/GlobalDCE/call-with-ptrtoint.ll diff --git a/llvm/include/llvm/Analysis/TypeMetadataUtils.h b/llvm/include/llvm/Analysis/TypeMetadataUtils.h index 6ca1124..074c409 100644 --- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h +++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h @@ -22,6 +22,7 @@ namespace llvm { class CallBase; class CallInst; class Constant; +class Function; class DominatorTree; class Instruction; class Module; @@ -56,25 +57,30 @@ void findDevirtualizableCallsForTypeCheckedLoad( SmallVectorImpl &Preds, bool &HasNonCallUses, const CallInst *CI, DominatorTree &DT); -// Processes a Constant recursively looking into elements of arrays, structs and -// expressions to find a trivial pointer element that is located at the given -// offset (relative to the beginning of the whole outer Constant). -// -// Used for example from GlobalDCE to find an entry in a C++ vtable that matches -// a vcall offset. -// -// To support Swift vtables, getPointerAtOffset can see through "relative -// pointers", i.e. (sub-)expressions of the form of: -// -// @symbol = ... { -// i32 trunc (i64 sub ( -// i64 ptrtoint ( @target to i64), i64 ptrtoint (... @symbol to i64) -// ) to i32) -// } -// -// For such (sub-)expressions, getPointerAtOffset returns the @target pointer. +/// Processes a Constant recursively looking into elements of arrays, structs +/// and expressions to find a trivial pointer element that is located at the +/// given offset (relative to the beginning of the whole outer Constant). +/// +/// Used for example from GlobalDCE to find an entry in a C++ vtable that +/// matches a vcall offset. +/// +/// To support Swift vtables, getPointerAtOffset can see through "relative +/// pointers", i.e. (sub-)expressions of the form of: +/// +/// @symbol = ... { +/// i32 trunc (i64 sub ( +/// i64 ptrtoint ( @target to i64), i64 ptrtoint (... @symbol to i64) +/// ) to i32) +/// } +/// +/// For such (sub-)expressions, getPointerAtOffset returns the @target pointer. Constant *getPointerAtOffset(Constant *I, uint64_t Offset, Module &M, 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); + +} // namespace llvm #endif diff --git a/llvm/lib/Analysis/TypeMetadataUtils.cpp b/llvm/lib/Analysis/TypeMetadataUtils.cpp index 65fa2bb..80051fd 100644 --- a/llvm/lib/Analysis/TypeMetadataUtils.cpp +++ b/llvm/lib/Analysis/TypeMetadataUtils.cpp @@ -197,3 +197,20 @@ Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M, } return nullptr; } + +void llvm::replaceRelativePointerUsersWithZero(Function *F) { + for (auto *U : F->users()) { + auto *PtrExpr = dyn_cast(U); + if (!PtrExpr || PtrExpr->getOpcode() != Instruction::PtrToInt) + continue; + + for (auto *PtrToIntUser : PtrExpr->users()) { + auto *SubExpr = dyn_cast(PtrToIntUser); + if (!SubExpr || SubExpr->getOpcode() != Instruction::Sub) + continue; + + SubExpr->replaceNonMetadataUsesWith( + ConstantInt::get(SubExpr->getType(), 0)); + } + } +} diff --git a/llvm/lib/Transforms/IPO/GlobalDCE.cpp b/llvm/lib/Transforms/IPO/GlobalDCE.cpp index 018a594..9977fa7 100644 --- a/llvm/lib/Transforms/IPO/GlobalDCE.cpp +++ b/llvm/lib/Transforms/IPO/GlobalDCE.cpp @@ -416,6 +416,16 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) { // virtual function pointers with null, allowing us to remove the // function itself. ++NumVFuncs; + + // Detect vfuncs that are referenced as "relative pointers" which are used + // in Swift vtables, i.e. entries in the form of: + // + // i32 trunc (i64 sub (i64 ptrtoint @f, i64 ptrtoint ...)) to i32) + // + // In this case, replace the whole "sub" expression with constant 0 to + // avoid leaving a weird sub(0, symbol) expression behind. + replaceRelativePointerUsersWithZero(F); + F->replaceNonMetadataUsesWith(ConstantPointerNull::get(F->getType())); } EraseUnusedGlobalValue(F); diff --git a/llvm/test/Transforms/GlobalDCE/call-with-ptrtoint.ll b/llvm/test/Transforms/GlobalDCE/call-with-ptrtoint.ll new file mode 100644 index 0000000..bff769b --- /dev/null +++ b/llvm/test/Transforms/GlobalDCE/call-with-ptrtoint.ll @@ -0,0 +1,21 @@ +; RUN: opt < %s -globaldce -S + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + +declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) + +define internal void @foo() { + call void @bar_with_fptr_argument(i64 ptrtoint (void ()* @baz to i64)) + ret void +} + +define internal void @bar_with_fptr_argument(i64 %0) { + ret void +} + +define internal void @baz() { + ret void +} + +!999 = !{i32 1, !"Virtual Function Elim", i32 1} +!llvm.module.flags = !{!999} diff --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll index 9562d3d..c386e6c 100644 --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll @@ -14,11 +14,7 @@ declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) !0 = !{i64 0, !"vfunc1.type"} !1 = !{i64 4, !"vfunc2.type"} -; 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: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 +; CHECK: @vtable = internal unnamed_addr constant { [3 x i32] } zeroinitializer, align 8, !type !0, !type !1, !vcall_visibility !2 define internal void @vfunc1() { ret void } define internal void @vfunc2() { 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 index e49dc78..7ec29cc 100644 --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll @@ -16,7 +16,7 @@ declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) ; 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 0, 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 0 ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 ; (1) vfunc1_live is referenced from @main, stays alive diff --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll index c2bbf00..685f375 100644 --- a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll +++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll @@ -14,7 +14,7 @@ declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) ; 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 0, i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) +; CHECK-SAME: i32 0 ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 ; (1) vfunc1_live is referenced from @main, stays alive -- 2.7.4