[llvm] [ObjCARC] Allow removal of autoreleasepools (PR #85493)

via llvm-commits llvm-commits at lists.llvm.org
Sat Mar 16 17:04:44 PDT 2024


https://github.com/AtariDreams updated https://github.com/llvm/llvm-project/pull/85493

>From d244ebb9bc6bc58a7bcc50fc9f6f2c14971d82f0 Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Fri, 15 Mar 2024 21:58:26 -0400
Subject: [PATCH] [ObjCARC] Allow removal of autoreleasepools

We did this for inserted pools, but we can do this with user-inserted ones if we can prove they do not.
---
 llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp |   9 +-
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp   | 100 ++++++++++++++++--
 2 files changed, 95 insertions(+), 14 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp
index dceb2ebb1863e7..ee8febe4033b02 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp
@@ -46,13 +46,14 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
   if (const Function *Callee = CB.getCalledFunction()) {
     if (!Callee->hasExactDefinition())
       return true;
+    // This recursion depth limit is big enough to cover the vast majority
+    // of cases this will ever require.
+    if (Depth > 64)
+      return true;
     for (const BasicBlock &BB : *Callee) {
       for (const Instruction &I : BB)
         if (const CallBase *JCB = dyn_cast<CallBase>(&I))
-          // This recursion depth limit is arbitrary. It's just great
-          // enough to cover known interesting testcases.
-          if (Depth < 3 && !JCB->onlyReadsMemory() &&
-              MayAutorelease(*JCB, Depth + 1))
+          if (!JCB->onlyReadsMemory() && MayAutorelease(*JCB, Depth + 1))
             return true;
     }
     return false;
diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index 72e860d7dcfa61..d327657bcc500b 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -503,7 +503,7 @@ class ObjCARCOpt {
 
   DenseMap<BasicBlock *, ColorVector> BlockEHColors;
 
-  bool OptimizeRetainRVCall(Function &F, Instruction *RetainRV);
+  void OptimizeRetainRVCall(Function &F, Instruction *RetainRV);
   void OptimizeAutoreleaseRVCall(Function &F, Instruction *AutoreleaseRV,
                                  ARCInstKind &Class);
   void OptimizeIndividualCalls(Function &F);
@@ -521,6 +521,9 @@ class ObjCARCOpt {
                                         Instruction *AutoreleaseRV,
                                         const Value *&AutoreleaseRVArg);
 
+  /// Remove any autorelease pools if nothing is released between them
+  bool OptimizeAutoreleasePools(Function &F);
+
   void CheckForCFGHazards(const BasicBlock *BB,
                           DenseMap<const BasicBlock *, BBState> &BBStates,
                           BBState &MyStates) const;
@@ -604,8 +607,7 @@ class ObjCARCOpt {
 
 /// Turn objc_retainAutoreleasedReturnValue into objc_retain if the operand is
 /// not a return value.
-bool
-ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
+void ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
   // Check for the argument being from an immediately preceding call or invoke.
   const Value *Arg = GetArgRCIdentityRoot(RetainRV);
   if (const Instruction *Call = dyn_cast<CallBase>(Arg)) {
@@ -615,7 +617,7 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
       while (IsNoopInstruction(&*I))
         ++I;
       if (&*I == RetainRV)
-        return false;
+        return;
     } else if (const InvokeInst *II = dyn_cast<InvokeInst>(Call)) {
       BasicBlock *RetainRVParent = RetainRV->getParent();
       if (II->getNormalDest() == RetainRVParent) {
@@ -623,7 +625,7 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
         while (IsNoopInstruction(&*I))
           ++I;
         if (&*I == RetainRV)
-          return false;
+          return;
       }
     }
   }
@@ -644,8 +646,6 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
   cast<CallInst>(RetainRV)->setCalledFunction(NewDecl);
 
   LLVM_DEBUG(dbgs() << "New = " << *RetainRV << "\n");
-
-  return false;
 }
 
 bool ObjCARCOpt::OptimizeInlinedAutoreleaseRVCall(
@@ -756,6 +756,76 @@ void ObjCARCOpt::OptimizeAutoreleaseRVCall(Function &F,
   LLVM_DEBUG(dbgs() << "New: " << *AutoreleaseRV << "\n");
 }
 
+/// Interprocedurally determine if calls made by the given call site can
+/// possibly produce autoreleases.
+bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
+  if (const Function *Callee = CB.getCalledFunction()) {
+    if (!Callee->hasExactDefinition())
+      return true;
+    // This recursion depth limit is big enough to cover the vast majority
+    // of cases this will ever require.
+    if (Depth > 64)
+      return true;
+    for (const BasicBlock &BB : *Callee) {
+      for (const Instruction &I : BB)
+        if (const CallBase *JCB = dyn_cast<CallBase>(&I))
+          if (!JCB->onlyReadsMemory() && MayAutorelease(*JCB, Depth + 1))
+            return true;
+    }
+    return false;
+  }
+
+  return true;
+}
+
+// Remove autorelease pools if nothing is in between then
+bool ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
+  bool didChange = false;
+  Instruction *Push = nullptr;
+  for (BasicBlock &BB : F) {
+    bool foundAnyInBlock = false;
+    for (Instruction &Inst : BB) {
+      // FIXME: For now, only support pairs that are in the same block.
+      // It is possible to do this across other blocks, but only if you can
+      // prove every single pop call site corresponds to the push via going
+      // through each successor and checking every single one, and I don't even
+      // know if the push call could be split across call sites.
+      switch (GetBasicARCInstKind(&Inst)) {
+      case ARCInstKind::AutoreleasepoolPush:
+        Push = &Inst;
+        foundAnyInBlock = true;
+        break;
+      case ARCInstKind::AutoreleasepoolPop:
+        // If this pop matches a push and nothing in between can autorelease,
+        // zap the pair.
+        if (cast<CallInst>(&Inst)->getArgOperand(0) == Push &&
+            foundAnyInBlock) {
+          Changed = true;
+          didChange = true;
+          LLVM_DEBUG(dbgs()
+                     << "ObjCARCAPElim::OptimizeBB: Zapping push pop "
+                        "autorelease pair:\n"
+                        "                           Pop: "
+                     << Inst << "\n"
+                     << "                           Push: " << *Push << "\n");
+
+          Inst.eraseFromParent();
+          Push->eraseFromParent();
+        }
+        Push = nullptr;
+        break;
+      case ARCInstKind::CallOrUser:
+        if (MayAutorelease(cast<CallBase>(Inst)))
+          Push = nullptr;
+        break;
+      default:
+        break;
+      }
+    }
+  }
+  return didChange;
+}
+
 /// Visit each call, one at a time, and make simplifications without doing any
 /// additional analysis.
 void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
@@ -970,12 +1040,16 @@ void ObjCARCOpt::OptimizeIndividualCallImpl(Function &F, Instruction *Inst,
     break;
   }
   case ARCInstKind::RetainRV:
-    if (OptimizeRetainRVCall(F, Inst))
-      return;
+    OptimizeRetainRVCall(F, Inst);
     break;
   case ARCInstKind::AutoreleaseRV:
     OptimizeAutoreleaseRVCall(F, Inst, Class);
     break;
+  case ARCInstKind::AutoreleasepoolPush:
+  case ARCInstKind::AutoreleasepoolPop:
+    if (OptimizeAutoreleasePools(F))
+      return;
+    break;
   }
 
   // objc_autorelease(x) -> objc_release(x) if x is otherwise unused.
@@ -2485,7 +2559,13 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
                             (1 << unsigned(ARCInstKind::AutoreleaseRV))))
     OptimizeReturns(F);
 
-  // Gather statistics after optimization.
+  if (UsedInThisFunction & (1 << unsigned(ARCInstKind::AutoreleasepoolPush)))
+    // Normally should not happen but there has been code where manual calls to
+    // objc autorelease have been made.
+    if (UsedInThisFunction & (1 << unsigned(ARCInstKind::AutoreleasepoolPop)))
+      OptimizeAutoreleasePools(F);
+
+      // Gather statistics after optimization.
 #ifndef NDEBUG
   if (AreStatisticsEnabled()) {
     GatherStatistics(F, true);



More information about the llvm-commits mailing list