[llvm] 3279724 - llvm/ObjCARC: Eliminate inlined AutoreleaseRV calls

Duncan P. N. Exon Smith via llvm-commits llvm-commits at lists.llvm.org
Tue Nov 19 12:02:21 PST 2019


Author: Duncan P. N. Exon Smith
Date: 2019-11-19T12:02:01-08:00
New Revision: 3279724905c14a8db383ade53af40a0dd49504d8

URL: https://github.com/llvm/llvm-project/commit/3279724905c14a8db383ade53af40a0dd49504d8
DIFF: https://github.com/llvm/llvm-project/commit/3279724905c14a8db383ade53af40a0dd49504d8.diff

LOG: llvm/ObjCARC: Eliminate inlined AutoreleaseRV calls

Pair up inlined AutoreleaseRV calls with their matching RetainRV or
ClaimRV.

- RetainRV cancels out AutoreleaseRV.  Delete both instructions.
- ClaimRV is a peephole for RetainRV+Release.  Delete AutoreleaseRV and
  replace ClaimRV with Release.

This avoids problems where more aggressive inlining triggers memory
regressions.

This patch is happy to skip over non-callable instructions and non-ARC
intrinsics looking for the pair.  It is likely sound to also skip over
opaque function calls, but that's harder to reason about, and it's not
relevant to the goal here: if there's an opaque function call splitting
up a pair, it's very unlikely that a handshake would have happened
dynamically without inlining.

Note that this patch also subsumes the previous logic that looked
backwards from ReleaseRV.

https://reviews.llvm.org/D70370
rdar://problem/46509586

Added: 
    llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll

Modified: 
    llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
    llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index f8fa6216724f..b80c1675050b 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -507,10 +507,20 @@ namespace {
     void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV,
                                    ARCInstKind &Class);
     void OptimizeIndividualCalls(Function &F);
-    void
-    OptimizeIndividualCallImpl(Function &F,
-                               DenseMap<BasicBlock *, ColorVector> &BlockColors,
-                               Instruction *Inst, ARCInstKind Class);
+
+    /// Optimize an individual call, optionally passing the
+    /// GetArgRCIdentityRoot if it has already been computed.
+    void OptimizeIndividualCallImpl(
+        Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
+        Instruction *Inst, ARCInstKind Class, const Value *Arg);
+
+    /// Try to optimize an AutoreleaseRV with a RetainRV or ClaimRV.  If the
+    /// optimization occurs, returns true to indicate that the caller should
+    /// assume the instructions are dead.
+    bool OptimizeInlinedAutoreleaseRVCall(
+        Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
+        Instruction *Inst, const Value *&Arg, ARCInstKind Class,
+        Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg);
 
     void CheckForCFGHazards(const BasicBlock *BB,
                             DenseMap<const BasicBlock *, BBState> &BBStates,
@@ -594,36 +604,8 @@ void ObjCARCOpt::getAnalysisUsage(AnalysisUsage &AU) const {
   AU.setPreservesCFG();
 }
 
-static bool isSafeBetweenRVCalls(const Instruction *I) {
-  if (IsNoopInstruction(I))
-    return true;
-
-  auto *CB = dyn_cast<CallBase>(I);
-  if (!CB)
-    return false;
-
-  Intrinsic::ID IID = CB->getIntrinsicID();
-  if (IID == Intrinsic::not_intrinsic)
-    return false;
-
-  switch (IID) {
-  case Intrinsic::lifetime_start:
-  case Intrinsic::lifetime_end:
-    // The inliner adds new lifetime markers as part of the return sequence,
-    // which should be skipped when looking for paired return RV call.
-    LLVM_FALLTHROUGH;
-  case Intrinsic::stacksave:
-  case Intrinsic::stackrestore:
-    // If the inlined code contains dynamic allocas, the above applies as well.
-    return true;
-  default:
-    return false;
-  }
-}
-
 /// Turn objc_retainAutoreleasedReturnValue into objc_retain if the operand is
-/// not a return value.  Or, if it can be paired with an
-/// objc_autoreleaseReturnValue, delete the pair and return true.
+/// not a return value.
 bool
 ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
   // Check for the argument being from an immediately preceding call or invoke.
@@ -649,39 +631,6 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
     }
   }
 
-  // Track PHIs which are equivalent to our Arg.
-  SmallDenseSet<const Value*, 2> EquivalentArgs;
-  EquivalentArgs.insert(Arg);
-
-  // Add PHIs that are equivalent to Arg to ArgUsers.
-  if (const PHINode *PN = dyn_cast<PHINode>(Arg)) {
-    SmallVector<const Value *, 2> ArgUsers;
-    getEquivalentPHIs(*PN, ArgUsers);
-    EquivalentArgs.insert(ArgUsers.begin(), ArgUsers.end());
-  }
-
-  // Check for being preceded by an objc_autoreleaseReturnValue on the same
-  // pointer. In this case, we can delete the pair.
-  BasicBlock::iterator I = RetainRV->getIterator(),
-                       Begin = RetainRV->getParent()->begin();
-  if (I != Begin) {
-    do
-      --I;
-    while (I != Begin && isSafeBetweenRVCalls(&*I));
-    if (GetBasicARCInstKind(&*I) == ARCInstKind::AutoreleaseRV &&
-        EquivalentArgs.count(GetArgRCIdentityRoot(&*I))) {
-      Changed = true;
-      ++NumPeeps;
-
-      LLVM_DEBUG(dbgs() << "Erasing autoreleaseRV,retainRV pair: " << *I << "\n"
-                        << "Erasing " << *RetainRV << "\n");
-
-      EraseInstruction(&*I);
-      EraseInstruction(RetainRV);
-      return true;
-    }
-  }
-
   // Turn it to a plain objc_retain.
   Changed = true;
   ++NumPeeps;
@@ -699,6 +648,62 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
   return false;
 }
 
+bool ObjCARCOpt::OptimizeInlinedAutoreleaseRVCall(
+    Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
+    Instruction *Inst, const Value *&Arg, ARCInstKind Class,
+    Instruction *AutoreleaseRV, const Value *&AutoreleaseRVArg) {
+  // Must be in the same basic block.
+  assert(Inst->getParent() == AutoreleaseRV->getParent());
+
+  // Must operate on the same root.
+  Arg = GetArgRCIdentityRoot(Inst);
+  AutoreleaseRVArg = GetArgRCIdentityRoot(AutoreleaseRV);
+  if (Arg != AutoreleaseRVArg) {
+    // If there isn't an exact match, check if we have equivalent PHIs.
+    const PHINode *PN = dyn_cast<PHINode>(Arg);
+    if (!PN)
+      return false;
+
+    SmallVector<const Value *, 4> ArgUsers;
+    getEquivalentPHIs(*PN, ArgUsers);
+    if (llvm::find(ArgUsers, AutoreleaseRVArg) == ArgUsers.end())
+      return false;
+  }
+
+  // Okay, this is a match.  Merge them.
+  ++NumPeeps;
+  LLVM_DEBUG(dbgs() << "Found inlined objc_autoreleaseReturnValue '"
+                    << *AutoreleaseRV << "' paired with '" << *Inst << "'\n");
+
+  // Delete the RV pair, starting with the AutoreleaseRV.
+  AutoreleaseRV->replaceAllUsesWith(
+      cast<CallInst>(AutoreleaseRV)->getArgOperand(0));
+  EraseInstruction(AutoreleaseRV);
+  if (Class == ARCInstKind::RetainRV) {
+    // AutoreleaseRV and RetainRV cancel out.  Delete the RetainRV.
+    Inst->replaceAllUsesWith(cast<CallInst>(Inst)->getArgOperand(0));
+    EraseInstruction(Inst);
+    return true;
+  }
+
+  // ClaimRV is a frontend peephole for RetainRV + Release.  Since the
+  // AutoreleaseRV and RetainRV cancel out, replace the ClaimRV with a Release.
+  assert(Class == ARCInstKind::ClaimRV);
+  Value *CallArg = cast<CallInst>(Inst)->getArgOperand(0);
+  CallInst *Release = CallInst::Create(
+      EP.get(ARCRuntimeEntryPointKind::Release), CallArg, "", Inst);
+  assert(IsAlwaysTail(ARCInstKind::ClaimRV) &&
+         "Expected ClaimRV to be safe to tail call");
+  Release->setTailCall();
+  Inst->replaceAllUsesWith(CallArg);
+  EraseInstruction(Inst);
+
+  // Run the normal optimizations on Release.
+  OptimizeIndividualCallImpl(F, BlockColors, Release, ARCInstKind::Release,
+                             Arg);
+  return true;
+}
+
 /// Turn objc_autoreleaseReturnValue into objc_autorelease if the result is not
 /// used as a return value.
 void ObjCARCOpt::OptimizeAutoreleaseRVCall(Function &F,
@@ -785,31 +790,98 @@ void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
       isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn())))
     BlockColors = colorEHFunclets(F);
 
+  // Store any delayed AutoreleaseRV intrinsics, so they can be easily paired
+  // with RetainRV and ClaimRV.
+  Instruction *DelayedAutoreleaseRV = nullptr;
+  const Value *DelayedAutoreleaseRVArg = nullptr;
+  auto setDelayedAutoreleaseRV = [&](Instruction *AutoreleaseRV) {
+    assert(!DelayedAutoreleaseRV || !AutoreleaseRV);
+    DelayedAutoreleaseRV = AutoreleaseRV;
+    DelayedAutoreleaseRVArg = nullptr;
+  };
+  auto optimizeDelayedAutoreleaseRV = [&]() {
+    if (!DelayedAutoreleaseRV)
+      return;
+    OptimizeIndividualCallImpl(F, BlockColors, DelayedAutoreleaseRV,
+                               ARCInstKind::AutoreleaseRV,
+                               DelayedAutoreleaseRVArg);
+    setDelayedAutoreleaseRV(nullptr);
+  };
+  auto shouldDelayAutoreleaseRV = [&](Instruction *NonARCInst) {
+    // Nothing to delay, but we may as well skip the logic below.
+    if (!DelayedAutoreleaseRV)
+      return true;
+
+    // If we hit the end of the basic block we're not going to find an RV-pair.
+    // Stop delaying.
+    if (NonARCInst->isTerminator())
+      return false;
+
+    // Given the frontend rules for emitting AutoreleaseRV, RetainRV, and
+    // ClaimRV, it's probably safe to skip over even opaque function calls
+    // here since OptimizeInlinedAutoreleaseRVCall will confirm that they
+    // have the same RCIdentityRoot.  However, what really matters is
+    // skipping instructions or intrinsics that the inliner could leave behind;
+    // be conservative for now and don't skip over opaque calls, which could
+    // potentially include other ARC calls.
+    auto *CB = dyn_cast<CallBase>(NonARCInst);
+    if (!CB)
+      return true;
+    return CB->getIntrinsicID() != Intrinsic::not_intrinsic;
+  };
+
   // Visit all objc_* calls in F.
   for (inst_iterator I = inst_begin(&F), E = inst_end(&F); I != E; ) {
     Instruction *Inst = &*I++;
 
     ARCInstKind Class = GetBasicARCInstKind(Inst);
 
-    LLVM_DEBUG(dbgs() << "Visiting: Class: " << Class << "; " << *Inst << "\n");
-
     // Skip this loop if this instruction isn't itself an ARC intrinsic.
+    const Value *Arg = nullptr;
     switch (Class) {
     default:
+      optimizeDelayedAutoreleaseRV();
       break;
     case ARCInstKind::CallOrUser:
     case ARCInstKind::User:
     case ARCInstKind::None:
+      // This is a non-ARC instruction.  If we're delaying an AutoreleaseRV,
+      // check if it's safe to skip over it; if not, optimize the AutoreleaseRV
+      // now.
+      if (!shouldDelayAutoreleaseRV(Inst))
+        optimizeDelayedAutoreleaseRV();
+      continue;
+    case ARCInstKind::AutoreleaseRV:
+      optimizeDelayedAutoreleaseRV();
+      setDelayedAutoreleaseRV(Inst);
       continue;
+    case ARCInstKind::RetainRV:
+    case ARCInstKind::ClaimRV:
+      if (DelayedAutoreleaseRV) {
+        // We have a potential RV pair.  Check if they cancel out.
+        if (OptimizeInlinedAutoreleaseRVCall(F, BlockColors, Inst, Arg, Class,
+                                             DelayedAutoreleaseRV,
+                                             DelayedAutoreleaseRVArg)) {
+          setDelayedAutoreleaseRV(nullptr);
+          continue;
+        }
+        optimizeDelayedAutoreleaseRV();
+      }
+      break;
     }
 
-    OptimizeIndividualCallImpl(F, BlockColors, Inst, Class);
+    OptimizeIndividualCallImpl(F, BlockColors, Inst, Class, Arg);
   }
+
+  // Catch the final delayed AutoreleaseRV.
+  optimizeDelayedAutoreleaseRV();
 }
 
 void ObjCARCOpt::OptimizeIndividualCallImpl(
     Function &F, DenseMap<BasicBlock *, ColorVector> &BlockColors,
-    Instruction *Inst, ARCInstKind Class) {
+    Instruction *Inst, ARCInstKind Class, const Value *Arg) {
+  LLVM_DEBUG(dbgs() << "Visiting: Class: " << Class << "; " << *Inst << "\n");
+
   // Some of the ARC calls can be deleted if their arguments are global
   // variables that are inert in ARC.
   if (IsNoopOnGlobal(Class)) {
@@ -958,7 +1030,9 @@ void ObjCARCOpt::OptimizeIndividualCallImpl(
     return;
   }
 
-  const Value *Arg = GetArgRCIdentityRoot(Inst);
+  // If we haven't already looked up the root, look it up now.
+  if (!Arg)
+    Arg = GetArgRCIdentityRoot(Inst);
 
   // ARC calls with null are no-ops. Delete them.
   if (IsNullOrUndef(Arg)) {

diff  --git a/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll b/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll
new file mode 100644
index 000000000000..84d33193ece6
--- /dev/null
+++ b/llvm/test/Transforms/ObjCARC/inlined-autorelease-return-value.ll
@@ -0,0 +1,292 @@
+; RUN: opt -basicaa -objc-arc -S < %s | FileCheck %s
+
+target datalayout = "e-p:64:64:64"
+
+declare i8* @llvm.objc.retain(i8*)
+declare i8* @llvm.objc.autoreleaseReturnValue(i8*)
+declare i8* @llvm.objc.retainAutoreleasedReturnValue(i8*)
+declare i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8*)
+declare void @opaque()
+declare void @llvm.lifetime.start(i64, i8* nocapture)
+declare void @llvm.lifetime.end(i64, i8* nocapture)
+
+; CHECK-LABEL: define i8* @elide_with_retainRV(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_retainRV(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_bitcast(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %c = bitcast i32* %x to i8*
+; CHECK-NEXT:    ret i8* %c
+define i8* @elide_with_retainRV_bitcast(i32* %x) nounwind {
+entry:
+  %a = bitcast i32* %x to i8*
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %c = bitcast i32* %x to i8*
+  %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %c) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_phi(
+; CHECK-NOT:   define
+; CHECK:       phis:
+; CHECK-NEXT:    phi i8*
+; CHECK-NEXT:    ret i8*
+define i8* @elide_with_retainRV_phi(i8* %x) nounwind {
+entry:
+  br label %phis
+
+phis:
+  %a = phi i8* [ %x, %entry ]
+  %c = phi i8* [ %x, %entry ]
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %c) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_splitByRetain(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %b = call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(i8* %b)
+define i8* @elide_with_retainRV_splitByRetain(i8* %x) nounwind {
+entry:
+  ; Cleanup is blocked by other ARC intrinsics for ease of implementation; we
+  ; only delay processing AutoreleaseRV until the very next ARC intrinsic.  In
+  ; practice, it would be very strange for this to matter.
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.retain(i8* %x) nounwind
+  %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_splitByOpaque(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %b = call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    call void @opaque()
+; CHECK-NEXT:    %d = tail call i8* @llvm.objc.retain(i8* %b)
+; CHECK-NEXT:    ret i8* %d
+define i8* @elide_with_retainRV_splitByOpaque(i8* %x) nounwind {
+entry:
+  ; Cleanup should get blocked by opaque calls.
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  call void @opaque() nounwind
+  %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_splitByLifetime(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0i8(i64 8, i8* %x)
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0i8(i64 8, i8* %x)
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_retainRV_splitByLifetime(i8* %x) nounwind {
+entry:
+  ; Cleanup should skip over lifetime intrinsics.
+  call void @llvm.lifetime.start(i64 8, i8* %x)
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  call void @llvm.lifetime.end(i64 8, i8* %x)
+  %d = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_wrongArg(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.objc.release(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(i8* %y)
+define i8* @elide_with_retainRV_wrongArg(i8* %x, i8* %y) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %y) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_wrongBB(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    br label %next
+; CHECK:       next:
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(
+; CHECK-NEXT:    ret i8*
+define i8* @elide_with_retainRV_wrongBB(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  br label %next
+
+next:
+  %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_beforeAutoreleaseRV(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %x)
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_retainRV_beforeAutoreleaseRV(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  %d = call i8* @llvm.objc.autoreleaseReturnValue(i8* %c) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_retainRV_afterRetain(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(i8* %x)
+; CHECK-NEXT:    ret i8* %a
+define i8* @elide_with_retainRV_afterRetain(i8* %x) nounwind {
+entry:
+  %a = call i8* @llvm.objc.retain(i8* %x) nounwind
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %c = call i8* @llvm.objc.retainAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    tail call void @llvm.objc.release(i8* %x)
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_claimRV(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_bitcast(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %c = bitcast i32* %x to i8*
+; CHECK-NEXT:    tail call void @llvm.objc.release(i8* %c)
+; CHECK-NEXT:    ret i8* %c
+define i8* @elide_with_claimRV_bitcast(i32* %x) nounwind {
+entry:
+  %a = bitcast i32* %x to i8*
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %c = bitcast i32* %x to i8*
+  %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %c) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_phi(
+; CHECK-NOT:   define
+; CHECK:       phis:
+; CHECK-NEXT:    %c = phi i8*
+; CHECK-NEXT:    tail call void @llvm.objc.release(i8* %c)
+; CHECK-NEXT:    ret i8* %c
+define i8* @elide_with_claimRV_phi(i8* %x) nounwind {
+entry:
+  br label %phis
+
+phis:
+  %a = phi i8* [ %x, %entry ]
+  %c = phi i8* [ %x, %entry ]
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %c) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_splitByRetain(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %b = call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.retain(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b)
+define i8* @elide_with_claimRV_splitByRetain(i8* %x) nounwind {
+entry:
+  ; Cleanup is blocked by other ARC intrinsics for ease of implementation; we
+  ; only delay processing AutoreleaseRV until the very next ARC intrinsic.  In
+  ; practice, it would be very strange for this to matter.
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.retain(i8* %x) nounwind
+  %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_splitByOpaque(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %b = call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    call void @opaque()
+; CHECK-NEXT:    %d = tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b)
+; CHECK-NEXT:    ret i8* %d
+define i8* @elide_with_claimRV_splitByOpaque(i8* %x) nounwind {
+entry:
+  ; Cleanup should get blocked by opaque calls.
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  call void @opaque() nounwind
+  %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_splitByLifetime(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0i8(i64 8, i8* %x)
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0i8(i64 8, i8* %x)
+; CHECK-NEXT:    tail call void @llvm.objc.release(i8* %x)
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_claimRV_splitByLifetime(i8* %x) nounwind {
+entry:
+  ; Cleanup should skip over lifetime intrinsics.
+  call void @llvm.lifetime.start(i64 8, i8* %x)
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  call void @llvm.lifetime.end(i64 8, i8* %x)
+  %d = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %d
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_wrongArg(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @llvm.objc.release(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %y)
+define i8* @elide_with_claimRV_wrongArg(i8* %x, i8* %y) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %y) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_wrongBB(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call i8* @llvm.objc.autorelease(i8* %x)
+; CHECK-NEXT:    br label %next
+; CHECK:       next:
+; CHECK-NEXT:    tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(
+; CHECK-NEXT:    ret i8*
+define i8* @elide_with_claimRV_wrongBB(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  br label %next
+
+next:
+  %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}
+
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_beforeAutoreleaseRV(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    tail call void @llvm.objc.release(i8* %x)
+; CHECK-NEXT:    tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %x)
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_claimRV_beforeAutoreleaseRV(i8* %x) nounwind {
+entry:
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %x) nounwind
+  %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  %d = call i8* @llvm.objc.autoreleaseReturnValue(i8* %c) nounwind
+  ret i8* %c
+}
+
+; CHECK-LABEL: define i8* @elide_with_claimRV_afterRetain(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret i8* %x
+define i8* @elide_with_claimRV_afterRetain(i8* %x) nounwind {
+entry:
+  %a = call i8* @llvm.objc.retain(i8* %x) nounwind
+  %b = call i8* @llvm.objc.autoreleaseReturnValue(i8* %a) nounwind
+  %c = call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %b) nounwind
+  ret i8* %c
+}

diff  --git a/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll b/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll
index 8b6480256552..a76a79295289 100644
--- a/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll
+++ b/llvm/test/Transforms/ObjCARC/unsafe-claim-rv.ll
@@ -40,8 +40,7 @@ if.end:                                           ; preds = %if.then, %entry
 
 ; CHECK: if.then
 ; CHECK: tail call i8* @llvm.objc.retain
-; CHECK-NEXT: call i8* @llvm.objc.autorelease
 ; CHECK: %Y.0 = phi
-; CHECK-NEXT: tail call i8* @llvm.objc.unsafeClaimAutoreleasedReturnValue(i8* %Y.0)
+; CHECK-NEXT: tail call void @llvm.objc.release
 ; CHECK-NEXT: tail call void @llvm.objc.release
 


        


More information about the llvm-commits mailing list