[deref] Implement initial set of inference rules for deref-at-point
authorPhilip Reames <listmail@philipreames.com>
Wed, 24 Mar 2021 23:18:09 +0000 (16:18 -0700)
committerPhilip Reames <listmail@philipreames.com>
Wed, 24 Mar 2021 23:20:41 +0000 (16:20 -0700)
This implements a subset of the initial set of inference rules proposed in the llvm-dev thread "RFC: Decomposing deref(N) into deref(N) + nofree". The nolias one got moved to a separate review as there was some concerns raised which require further discussion.

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

llvm/lib/IR/Value.cpp
llvm/test/Analysis/ValueTracking/deref-abstract-gc.ll
llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll

index cfb91b55f707e911677bbe84bd74179cdf48978f..8c06d4fe22d9b4aea35060c29fe980773716cfef 100644 (file)
@@ -728,6 +728,64 @@ Value::stripInBoundsOffsets(function_ref<void(const Value *)> Func) const {
   return stripPointerCastsAndOffsets<PSK_InBounds>(this, Func);
 }
 
+// Return true if the memory object referred to by V can by freed in the scope
+// for which the SSA value defining the allocation is statically defined.  E.g.
+// deallocation after the static scope of a value does not count.
+static bool canBeFreed(const Value *V) {
+  assert(V->getType()->isPointerTy());
+
+  // Cases that can simply never be deallocated
+  // *) Constants aren't allocated per se, thus not deallocated either.
+  if (isa<Constant>(V))
+    return false;
+
+  const Function *F = nullptr;
+  if (auto *I = dyn_cast<Instruction>(V))
+    F = I->getFunction();
+  if (auto *A = dyn_cast<Argument>(V))
+    F = A->getParent();
+
+  if (!F)
+    return true;
+
+  // A pointer to an object in a function which neither frees, nor can arrange
+  // for another thread to free on its behalf, can not be freed in the scope
+  // of the function.
+  if (F->doesNotFreeMemory() && F->hasNoSync())
+    return false;
+
+  // With garbage collection, deallocation typically occurs solely at or after
+  // safepoints.  If we're compiling for a collector which uses the
+  // gc.statepoint infrastructure, safepoints aren't explicitly present
+  // in the IR until after lowering from abstract to physical machine model.
+  // The collector could chose to mix explicit deallocation and gc'd objects
+  // which is why we need the explicit opt in on a per collector basis.
+  if (!F->hasGC())
+    return true;
+  
+  const auto &GCName = F->getGC();
+  const StringRef StatepointExampleName("statepoint-example");
+  if (GCName != StatepointExampleName)
+    return true;
+
+  auto *PT = cast<PointerType>(V->getType());
+  if (PT->getAddressSpace() != 1)
+    // For the sake of this example GC, we arbitrarily pick addrspace(1) as our
+    // GC managed heap.  This must match the same check in
+    // RewriteStatepointsForGC (and probably needs better factored.)
+    return true;
+
+  // It is cheaper to scan for a declaration than to scan for a use in this
+  // function.  Note that gc.statepoint is a type overloaded function so the
+  // usual trick of requesting declaration of the intrinsic from the module
+  // doesn't work.
+  for (auto &Fn : *F->getParent())
+    if (Fn.getIntrinsicID() == Intrinsic::experimental_gc_statepoint)
+      return true;
+  return false;
+}
+
+
 uint64_t Value::getPointerDereferenceableBytes(const DataLayout &DL,
                                                bool &CanBeNull,
                                                bool &CanBeFreed) const {
@@ -735,7 +793,7 @@ uint64_t Value::getPointerDereferenceableBytes(const DataLayout &DL,
 
   uint64_t DerefBytes = 0;
   CanBeNull = false;
-  CanBeFreed = UseDerefAtPointSemantics;
+  CanBeFreed = UseDerefAtPointSemantics && canBeFreed(this);
   if (const Argument *A = dyn_cast<Argument>(this)) {
     DerefBytes = A->getDereferenceableBytes();
     if (DerefBytes == 0) {
@@ -798,7 +856,6 @@ uint64_t Value::getPointerDereferenceableBytes(const DataLayout &DL,
       // CanBeNull flag.
       DerefBytes = DL.getTypeStoreSize(GV->getValueType()).getFixedSize();
       CanBeNull = false;
-      CanBeFreed = false;
     }
   }
   return DerefBytes;
index bfa1f48797f266d0c683bd8863b9403013af3f9f..70fd9eda5f8f2e420cbf6c9b9ecab911a5bfa6de 100644 (file)
@@ -7,13 +7,12 @@ target datalayout = "e-i32:32:64"
 ; conceptually live forever.  But there may be non-managed objects which are
 ; freed.
 ; CHECK-LABEL: 'abstract_model'
-; CHECK-NOT: %gc_ptr
+; CHECK: %gc_ptr
 ; CHECK-NOT: %other_ptr
 ; FIXME: Can infer the gc pointer case
 define void @abstract_model(i32 addrspace(1)* dereferenceable(8) %gc_ptr,
                             i32* dereferenceable(8) %other_ptr)
     gc "statepoint-example" {
-; CHECK: The following are dereferenceable:
 entry:
   call void @mayfree()
   load i32, i32 addrspace(1)* %gc_ptr
index 86e6ce23d58638dbe4934afd58401190d5f8d118..1b66112db8bb66e7d966a069d07435b532539703 100644 (file)
@@ -245,16 +245,14 @@ define i32 @f_0(i32 %val) {
 ; CHECK-LABEL: 'negative'
 ; GLOBAL: %p
 ; POINT-NOT: %p
-define void @negative(i32* dereferenceable(8) %p) nofree nosync {
+define void @negative(i32* dereferenceable(8) %p) {
   call void @mayfree()
   %v = load i32, i32* %p
   ret void
 }
 
 ; CHECK-LABEL: 'infer_func_attrs1'
-; GLOBAL: %p
-; POINT-NOT: %p
-; FIXME: Can be inferred from attributes
+; CHECK: %p
 define void @infer_func_attrs1(i32* dereferenceable(8) %p) nofree nosync {
   call void @mayfree()
   %v = load i32, i32* %p