Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ObjCARC] Allow removal of autoreleasepools #85493

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

AtariDreams
Copy link
Contributor

We did this for inserted pools, but we can do this with user-inserted ones if we can prove they do not.

@llvmbot
Copy link
Collaborator

llvmbot commented Mar 16, 2024

@llvm/pr-subscribers-llvm-transforms

Author: AtariDreams (AtariDreams)

Changes

We did this for inserted pools, but we can do this with user-inserted ones if we can prove they do not.


Full diff: https://github.com/llvm/llvm-project/pull/85493.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/ObjCARC/ObjCARCAPElim.cpp (+5-4)
  • (modified) llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp (+77-1)
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..c0a0863816d120 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -521,6 +521,9 @@ class ObjCARCOpt {
                                         Instruction *AutoreleaseRV,
                                         const Value *&AutoreleaseRVArg);
 
+  /// Remove any autorelease pools if nothing is released between them
+  void ObjCARCOpt::OptimizeAutoreleasePools(Function &F);
+
   void CheckForCFGHazards(const BasicBlock *BB,
                           DenseMap<const BasicBlock *, BBState> &BBStates,
                           BBState &MyStates) const;
@@ -756,6 +759,73 @@ 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
+void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
+  Instruction *Push = nullptr;
+  for (BasicBlock &BB : F) {
+    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.
+      bool foundAnyInBlock = false;
+      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 (Push && cast<CallInst>(&Inst)->getArgOperand(0) == Push &&
+            foundAnyInBlock) {
+          Changed = 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;
+      }
+    }
+  }
+}
+
 /// Visit each call, one at a time, and make simplifications without doing any
 /// additional analysis.
 void ObjCARCOpt::OptimizeIndividualCalls(Function &F) {
@@ -2485,7 +2555,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);

@AtariDreams AtariDreams marked this pull request as draft March 16, 2024 02:28
@AtariDreams AtariDreams marked this pull request as ready for review March 16, 2024 02:40
@AtariDreams AtariDreams force-pushed the autoreleaseARC branch 2 times, most recently from 3de666c to 252a83e Compare March 16, 2024 03:16
We did this for inserted pools, but we can do this with user-inserted ones if we can prove they do not.
@fhahn
Copy link
Contributor

fhahn commented Mar 18, 2024

Could you add some tests for the new functionality?

@fhahn fhahn requested review from rjmccall and ahatanak March 18, 2024 12:39
break;
case ARCInstKind::CallOrUser:
if (MayAutorelease(cast<CallBase>(Inst)))
Push = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please only do this analysis if there's actually a push we're considering optimizing.

@@ -615,15 +617,15 @@ ObjCARCOpt::OptimizeRetainRVCall(Function &F, Instruction *RetainRV) {
while (IsNoopInstruction(&*I))
++I;
if (&*I == RetainRV)
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make changes to OptimizeRetainRVCall in a separate patch? The function always returns false, so it'll be a NFC patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants