[llvm] [ObjCARC] Delete empty autoreleasepools with no autoreleases in them (PR #144788)

John McCall via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 18 20:11:19 PDT 2025


================
@@ -2485,6 +2490,197 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
   return Changed;
 }
 
+/// Helper function to recursively check if a value eventually leads to the
+/// target instruction through pointer casts and uses, up to a specified depth.
+static bool checkLeadsToTarget(Value *Val, User *Target, unsigned MaxDepth,
+                               SmallPtrSet<Value *, 8> &Visited) {
+  if (MaxDepth == 0)
+    return false;
+
+  // Avoid infinite recursion by tracking visited values
+  if (!Visited.insert(Val).second)
+    return false;
+
+  for (User *U : Val->users()) {
+    if (U == Target)
+      return true;
+
+    // For pointer casts, recursively check their users
+    if (isa<CastInst>(U)) {
+      if (checkLeadsToTarget(cast<Value>(U), Target, MaxDepth - 1, Visited)) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+/// Optimize autorelease pools by eliminating empty push/pop pairs.
+void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
+  LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n");
+
+  OptimizationRemarkEmitter ORE(&F);
+
+  // Track empty autorelease pool push/pop pairs
+  SmallVector<std::pair<CallInst *, CallInst *>, 4> EmptyPoolPairs;
+
+  // Process each basic block independently.
+  // TODO: Can we optimize inter-block autorelease pool pairs?
+  // This would involve tracking autorelease pool state across blocks.
+  for (BasicBlock &BB : F) {
+    // Use a stack to track nested autorelease pools
+    SmallVector<std::pair<CallInst *, bool>, 4>
+        PoolStack; // {push_inst, has_autorelease_in_scope}
+
+    for (Instruction &Inst : BB) {
+      ARCInstKind Class = GetBasicARCInstKind(&Inst);
+
+      switch (Class) {
+      case ARCInstKind::AutoreleasepoolPush: {
+        // Start tracking a new autorelease pool scope
+        auto *Push = cast<CallInst>(&Inst);
+        PoolStack.push_back(
+            {Push, false}); // {push_inst, has_autorelease_in_scope}
+        LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n");
+        break;
+      }
+
+      case ARCInstKind::AutoreleasepoolPop: {
+        auto *Pop = cast<CallInst>(&Inst);
+
+        if (PoolStack.empty())
+          break;
+
+        auto &TopPool = PoolStack.back();
+        CallInst *PendingPush = TopPool.first;
+        bool HasAutoreleaseInScope = TopPool.second;
+
+        // Pop the stack - remove this pool scope
+        PoolStack.pop_back();
+
+        // Check if this pop matches the pending push by comparing the token
+        Value *PopArg = Pop->getArgOperand(0);
+        bool IsMatchingPop = (PopArg == PendingPush);
+
+        // Also handle pointer casts by stripping them
+        if (!IsMatchingPop) {
+          Value *StrippedPopArg = PopArg->stripPointerCasts();
+          IsMatchingPop = (StrippedPopArg == PendingPush);
+        }
+
+        if (!IsMatchingPop)
+          break;
+
+        // Before adding to EmptyPoolPairs, verify ALL uses of the push
+        // eventually lead to this specific pop instruction
+        bool SafeToOptimize = true;
+        for (User *U : PendingPush->users()) {
+          // Check if this use eventually leads to our pop instruction
+          bool LeadsToExpectedPop = false;
+
+          if (U == Pop)
+            LeadsToExpectedPop = true;
+          else if (isa<CastInst>(U)) {
+            // For pointer casts, check if they eventually lead to the pop
+            // (up to 5 levels deep)
+            SmallPtrSet<Value *, 8> Visited;
+            if (checkLeadsToTarget(cast<Value>(U), Pop, 5, Visited)) {
+              LeadsToExpectedPop = true;
+            }
+          }
+
+          if (!LeadsToExpectedPop) {
+            SafeToOptimize = false;
+            LLVM_DEBUG(dbgs() << "Unsafe to optimize: push has unexpected use: "
+                              << *U << "\n");
+            break;
+          }
+        }
+
+        if (!HasAutoreleaseInScope && SafeToOptimize) {
+          LLVM_DEBUG(dbgs() << "Eliminating empty autorelease pool pair: "
+                            << *PendingPush << " and " << *Pop << "\n");
+
+          // Store the pair for careful deletion later
+          EmptyPoolPairs.push_back({PendingPush, Pop});
+
+          ++NumNoops;
+        }
+        break;
+      }
+      case ARCInstKind::CallOrUser:
+      case ARCInstKind::Call:
+      case ARCInstKind::Autorelease:
+      case ARCInstKind::AutoreleaseRV: {
+        // Track that we have autorelease calls in the current pool scope
+        if (!PoolStack.empty()) {
+          PoolStack.back().second = true; // Set has_autorelease_in_scope = true
+          LLVM_DEBUG(
+              dbgs()
+              << "Found autorelease or potiential autorelease in pool scope: "
+              << Inst << "\n");
+        }
+        break;
+      }
+
+      // Enumerate all remaining ARCInstKind cases explicitly
+      case ARCInstKind::Retain:
+      case ARCInstKind::RetainRV:
+      case ARCInstKind::UnsafeClaimRV:
+      case ARCInstKind::RetainBlock:
+      case ARCInstKind::Release:
+      case ARCInstKind::NoopCast:
+      case ARCInstKind::FusedRetainAutorelease:
+      case ARCInstKind::FusedRetainAutoreleaseRV:
----------------
rjmccall wrote:

`FusedRetainAutorelease` and `LoadWeak` can both autorelease. So can `FusedRetainAutoreleaseRV`, although really we should just never see that within an autorelease scope.

https://github.com/llvm/llvm-project/pull/144788


More information about the llvm-commits mailing list