[llvm] 4c066bd - [GlobalDCE] Handle relative pointers in VFE (for Swift vtables)

Kuba Mracek via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 31 07:07:36 PDT 2021


Author: Kuba Mracek
Date: 2021-08-31T07:07:22-07:00
New Revision: 4c066bd08be3591cfbc81df478c8fc8618755ba1

URL: https://github.com/llvm/llvm-project/commit/4c066bd08be3591cfbc81df478c8fc8618755ba1
DIFF: https://github.com/llvm/llvm-project/commit/4c066bd08be3591cfbc81df478c8fc8618755ba1.diff

LOG: [GlobalDCE] Handle relative pointers in VFE (for Swift vtables)

To support Virtual Function Elimination to Swift, this PR adds support for Swift
vtables which contain "relative pointers" instead of direct pointer references.
These are in the form of:

@symbol = ... {
  i32 trunc (i64 sub (i64 ptrtoint (<type> @target to i64), i64 ptrtoint (... @symbol to i64)) to i32)
}

The PR extends GlobalDCE's way of looking up a vtable offset into a dependency
to be able to see through this expression and find the target symbol.

Differential Revision: https://reviews.llvm.org/D107645

Added: 
    llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll
    llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll

Modified: 
    llvm/include/llvm/Analysis/TypeMetadataUtils.h
    llvm/lib/Analysis/TypeMetadataUtils.cpp
    llvm/lib/Transforms/IPO/GlobalDCE.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Analysis/TypeMetadataUtils.h b/llvm/include/llvm/Analysis/TypeMetadataUtils.h
index 3f76031429006..6ca112457b6f7 100644
--- a/llvm/include/llvm/Analysis/TypeMetadataUtils.h
+++ b/llvm/include/llvm/Analysis/TypeMetadataUtils.h
@@ -56,7 +56,25 @@ void findDevirtualizableCallsForTypeCheckedLoad(
     SmallVectorImpl<Instruction *> &Preds, bool &HasNonCallUses,
     const CallInst *CI, DominatorTree &DT);
 
-Constant *getPointerAtOffset(Constant *I, uint64_t Offset, Module &M);
+// 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 (<type> @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);
 }
 
 #endif

diff  --git a/llvm/lib/Analysis/TypeMetadataUtils.cpp b/llvm/lib/Analysis/TypeMetadataUtils.cpp
index f015ba9a09cab..c55071a3685f4 100644
--- a/llvm/lib/Analysis/TypeMetadataUtils.cpp
+++ b/llvm/lib/Analysis/TypeMetadataUtils.cpp
@@ -126,7 +126,8 @@ void llvm::findDevirtualizableCallsForTypeCheckedLoad(
                               Offset->getZExtValue(), CI, DT);
 }
 
-Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M) {
+Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M,
+                                   Constant *TopLevelGlobal) {
   if (I->getType()->isPointerTy()) {
     if (Offset == 0)
       return I;
@@ -142,7 +143,8 @@ Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M) {
 
     unsigned Op = SL->getElementContainingOffset(Offset);
     return getPointerAtOffset(cast<Constant>(I->getOperand(Op)),
-                              Offset - SL->getElementOffset(Op), M);
+                              Offset - SL->getElementOffset(Op), M,
+                              TopLevelGlobal);
   }
   if (auto *C = dyn_cast<ConstantArray>(I)) {
     ArrayType *VTableTy = C->getType();
@@ -153,7 +155,36 @@ Constant *llvm::getPointerAtOffset(Constant *I, uint64_t Offset, Module &M) {
       return nullptr;
 
     return getPointerAtOffset(cast<Constant>(I->getOperand(Op)),
-                              Offset % ElemSize, M);
+                              Offset % ElemSize, M, TopLevelGlobal);
+  }
+
+  // (Swift-specific) relative-pointer support starts here.
+  if (auto *CI = dyn_cast<ConstantInt>(I)) {
+    if (Offset == 0 && CI->getZExtValue() == 0) {
+      return I;
+    }
+  }
+  if (auto *C = dyn_cast<ConstantExpr>(I)) {
+    switch (C->getOpcode()) {
+    case Instruction::Trunc:
+    case Instruction::PtrToInt:
+      return getPointerAtOffset(cast<Constant>(C->getOperand(0)), Offset, M,
+                                TopLevelGlobal);
+    case Instruction::Sub: {
+      auto *Operand0 = cast<Constant>(C->getOperand(0));
+      auto *Operand1 = cast<Constant>(C->getOperand(1));
+      auto *Operand1TargetGlobal = getPointerAtOffset(Operand1, 0, M);
+
+      // Check that in the "sub (@a, @b)" expression, @b points back to the top
+      // level global that we're processing. Otherwise bail.
+      if (Operand1TargetGlobal != TopLevelGlobal)
+        return nullptr;
+
+      return getPointerAtOffset(Operand0, Offset, M, TopLevelGlobal);
+    }
+    default:
+      return nullptr;
+    }
   }
   return nullptr;
 }

diff  --git a/llvm/lib/Transforms/IPO/GlobalDCE.cpp b/llvm/lib/Transforms/IPO/GlobalDCE.cpp
index fb4cb23b837e0..018a594127f03 100644
--- a/llvm/lib/Transforms/IPO/GlobalDCE.cpp
+++ b/llvm/lib/Transforms/IPO/GlobalDCE.cpp
@@ -210,7 +210,7 @@ void GlobalDCEPass::ScanVTableLoad(Function *Caller, Metadata *TypeId,
 
     Constant *Ptr =
         getPointerAtOffset(VTable->getInitializer(), VTableOffset + CallOffset,
-                           *Caller->getParent());
+                           *Caller->getParent(), VTable);
     if (!Ptr) {
       LLVM_DEBUG(dbgs() << "can't find pointer in vtable!\n");
       VFESafeVTables.erase(VTable);

diff  --git a/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll
new file mode 100644
index 0000000000000..9562d3db82e24
--- /dev/null
+++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll
@@ -0,0 +1,35 @@
+; RUN: opt < %s -globaldce -S | FileCheck %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)
+
+ at 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),
+
+  ; 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)
+]}, 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] } { [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
+
+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 }
+
+define void @main() {
+  %1 = ptrtoint { [3 x i32] }* @vtable to i64 ; to keep @vtable alive
+  call void @weird_ref_2()
+  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.ll b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll
new file mode 100644
index 0000000000000..c2bbf00ba56db
--- /dev/null
+++ b/llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll
@@ -0,0 +1,39 @@
+; RUN: opt < %s -globaldce -S | FileCheck %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)
+
+; 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.
+ at 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)
+]}, 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 0,                                                    i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32)
+; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2
+
+; (1) vfunc1_live is referenced from @main, stays alive
+define internal void @vfunc1_live() {
+  ; CHECK: define internal void @vfunc1_live(
+  ret void
+}
+
+; (2) vfunc2_dead is never referenced, gets removed and vtable slot is null'd
+define internal void @vfunc2_dead() {
+  ; CHECK-NOT: define internal void @vfunc2_dead(
+  ret void
+}
+
+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")
+  ret void
+}
+
+!999 = !{i32 1, !"Virtual Function Elim", i32 1}
+!llvm.module.flags = !{!999}


        


More information about the llvm-commits mailing list