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

via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 19 08:54:17 PDT 2025


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

>From 2d1be00cda55c4ef885053dc71c3428c34b01e65 Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Wed, 18 Jun 2025 16:05:44 -0400
Subject: [PATCH 1/3] [ObjCARC] Delete empty autoreleasepools with no
 autoreleases in them

Erase empty autorelease pools that have no autorelease in them
---
 .../ObjCARC/ARCRuntimeEntryPoints.h           |  16 ++
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp   | 206 +++++++++++++++++-
 .../ObjCARC/test_autorelease_pool.ll          | 106 +++++++++
 3 files changed, 323 insertions(+), 5 deletions(-)
 create mode 100644 llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll

diff --git a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h
index 3fa844eda21cf..6135c7b938a3e 100644
--- a/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h
+++ b/llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h
@@ -46,6 +46,8 @@ enum class ARCRuntimeEntryPointKind {
   UnsafeClaimRV,
   RetainAutorelease,
   RetainAutoreleaseRV,
+  AutoreleasePoolPush,
+  AutoreleasePoolPop,
 };
 
 /// Declarations for ObjC runtime functions and constants. These are initialized
@@ -67,6 +69,8 @@ class ARCRuntimeEntryPoints {
     UnsafeClaimRV = nullptr;
     RetainAutorelease = nullptr;
     RetainAutoreleaseRV = nullptr;
+    AutoreleasePoolPush = nullptr;
+    AutoreleasePoolPop = nullptr;
   }
 
   Function *get(ARCRuntimeEntryPointKind kind) {
@@ -101,6 +105,12 @@ class ARCRuntimeEntryPoints {
     case ARCRuntimeEntryPointKind::RetainAutoreleaseRV:
       return getIntrinsicEntryPoint(RetainAutoreleaseRV,
                                 Intrinsic::objc_retainAutoreleaseReturnValue);
+    case ARCRuntimeEntryPointKind::AutoreleasePoolPush:
+      return getIntrinsicEntryPoint(AutoreleasePoolPush,
+                                    Intrinsic::objc_autoreleasePoolPush);
+    case ARCRuntimeEntryPointKind::AutoreleasePoolPop:
+      return getIntrinsicEntryPoint(AutoreleasePoolPop,
+                                    Intrinsic::objc_autoreleasePoolPop);
     }
 
     llvm_unreachable("Switch should be a covered switch.");
@@ -143,6 +153,12 @@ class ARCRuntimeEntryPoints {
   /// Declaration for objc_retainAutoreleaseReturnValue().
   Function *RetainAutoreleaseRV = nullptr;
 
+  /// Declaration for objc_autoreleasePoolPush().
+  Function *AutoreleasePoolPush = nullptr;
+
+  /// Declaration for objc_autoreleasePoolPop().
+  Function *AutoreleasePoolPop = nullptr;
+
   Function *getIntrinsicEntryPoint(Function *&Decl, Intrinsic::ID IntID) {
     if (Decl)
       return Decl;
diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index 5eb3f51d38945..c8e5c51045dd3 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -39,6 +39,7 @@
 #include "llvm/Analysis/ObjCARCAnalysisUtils.h"
 #include "llvm/Analysis/ObjCARCInstKind.h"
 #include "llvm/Analysis/ObjCARCUtil.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/CFG.h"
 #include "llvm/IR/Constant.h"
@@ -132,11 +133,8 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) {
 //
 // The second retain and autorelease can be deleted.
 
-// TODO: It should be possible to delete
-// objc_autoreleasePoolPush and objc_autoreleasePoolPop
-// pairs if nothing is actually autoreleased between them. Also, autorelease
-// calls followed by objc_autoreleasePoolPop calls (perhaps in ObjC++ code
-// after inlining) can be turned into plain release calls.
+// TODO: Autorelease calls followed by objc_autoreleasePoolPop calls (perhaps in
+// ObjC++ code after inlining) can be turned into plain release calls.
 
 // TODO: Critical-edge splitting. If the optimial insertion point is
 // a critical edge, the current algorithm has to fail, because it doesn't
@@ -566,6 +564,8 @@ class ObjCARCOpt {
 
   void OptimizeReturns(Function &F);
 
+  void OptimizeAutoreleasePools(Function &F);
+
   template <typename PredicateT>
   static void cloneOpBundlesIf(CallBase *CI,
                                SmallVectorImpl<OperandBundleDef> &OpBundles,
@@ -2473,6 +2473,11 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
                             (1 << unsigned(ARCInstKind::AutoreleaseRV))))
     OptimizeReturns(F);
 
+  // Optimizations for autorelease pools.
+  if (UsedInThisFunction & ((1 << unsigned(ARCInstKind::AutoreleasepoolPush)) |
+                            (1 << unsigned(ARCInstKind::AutoreleasepoolPop))))
+    OptimizeAutoreleasePools(F);
+
   // Gather statistics after optimization.
 #ifndef NDEBUG
   if (AreStatisticsEnabled()) {
@@ -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:
+      case ARCInstKind::LoadWeakRetained:
+      case ARCInstKind::StoreWeak:
+      case ARCInstKind::InitWeak:
+      case ARCInstKind::LoadWeak:
+      case ARCInstKind::MoveWeak:
+      case ARCInstKind::CopyWeak:
+      case ARCInstKind::DestroyWeak:
+      case ARCInstKind::StoreStrong:
+      case ARCInstKind::IntrinsicUser:
+      case ARCInstKind::User:
+      case ARCInstKind::None:
+        // These instruction kinds don't affect autorelease pool optimization
+        break;
+      }
+    }
+  }
+
+  // Handle empty pool pairs carefully to avoid use-after-delete
+  SmallVector<CallInst *, 8> DeadInsts;
+  for (auto &Pair : EmptyPoolPairs) {
+    CallInst *Push = Pair.first;
+    CallInst *Pop = Pair.second;
+
+    // Replace all uses of push with poison before deletion
+    Push->replaceAllUsesWith(PoisonValue::get(Push->getType()));
+
+    LLVM_DEBUG(dbgs() << "Erasing empty pool pair: " << *Push << " and " << *Pop
+                      << "\n");
+    DeadInsts.push_back(Pop);
+    DeadInsts.push_back(Push);
+  }
+
+  // Remove the pairs
+  if (!DeadInsts.empty()) {
+    Changed = true;
+    for (CallInst *DeadInst : DeadInsts) {
+      LLVM_DEBUG(dbgs() << "Erasing dead instruction: " << *DeadInst << "\n");
+      DeadInst->eraseFromParent();
+    }
+
+    ORE.emit([&]() {
+      return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination", &F)
+             << "eliminated " << ore::NV("NumPairs", EmptyPoolPairs.size())
+             << " empty autorelease pool pair(s)";
+    });
+  }
+}
+
 /// @}
 ///
 
diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
new file mode 100644
index 0000000000000..f1388a003ea01
--- /dev/null
+++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
@@ -0,0 +1,106 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; Test for autorelease pool optimizations
+; RUN: opt -passes=objc-arc < %s -S | FileCheck %s
+
+declare ptr @llvm.objc.autoreleasePoolPush()
+declare void @llvm.objc.autoreleasePoolPop(ptr)
+declare ptr @llvm.objc.autorelease(ptr)
+declare ptr @llvm.objc.retain(ptr)
+declare ptr @create_object()
+declare void @use_object(ptr)
+declare ptr @object_with_thing()
+declare void @opaque_callee()
+
+; Test 1: Empty autorelease pool should be eliminated
+define void @test_empty_pool() {
+; CHECK-LABEL: define void @test_empty_pool() {
+; CHECK-NEXT:    ret void
+;
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+; Test 2: Pool with only release should be removed
+define void @test_autorelease_to_release() {
+; CHECK-LABEL: define void @test_autorelease_to_release() {
+; CHECK-NEXT:    [[OBJ:%.*]] = call ptr @create_object()
+; CHECK-NEXT:    call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0:[0-9]+]], !clang.imprecise_release [[META0:![0-9]+]]
+; CHECK-NEXT:    ret void
+;
+  %obj = call ptr @create_object()
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  call ptr @llvm.objc.autorelease(ptr %obj)
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+; Test 3: Pool with autoreleases should not be optimized
+define void @test_multiple_autoreleases() {
+; CHECK-LABEL: define void @test_multiple_autoreleases() {
+; CHECK-NEXT:    [[OBJ1:%.*]] = call ptr @create_object()
+; CHECK-NEXT:    [[OBJ2:%.*]] = call ptr @create_object()
+; CHECK-NEXT:    [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT:    call void @use_object(ptr [[OBJ1]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ1]]) #[[ATTR0]]
+; CHECK-NEXT:    call void @use_object(ptr [[OBJ2]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ2]]) #[[ATTR0]]
+; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  %obj1 = call ptr @create_object()
+  %obj2 = call ptr @create_object()
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  call void @use_object(ptr %obj1)
+  call ptr @llvm.objc.autorelease(ptr %obj1)
+  call void @use_object(ptr %obj2)
+  call ptr @llvm.objc.autorelease(ptr %obj2)
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+; Test 4: Pool with calls should not be optimized
+define void @test_calls() {
+; CHECK-LABEL: define void @test_calls() {
+; CHECK-NEXT:    [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT:    [[OBJ1:%.*]] = call ptr @object_with_thing()
+; CHECK-NEXT:    call void @use_object(ptr [[OBJ1]])
+; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  %obj1 = call ptr @object_with_thing()
+  call void @use_object(ptr %obj1)
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+; Test 5: Pool with opaque call should not be optimized
+define void @test_opaque_call() {
+; CHECK-LABEL: define void @test_opaque_call() {
+; CHECK-NEXT:    [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT:    call void @opaque_callee()
+; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  call void @opaque_callee()
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+; Test 6: Nested empty pools should be eliminated
+define void @test_nested_empty_pools() {
+; CHECK-LABEL: define void @test_nested_empty_pools() {
+; CHECK-NEXT:    ret void
+;
+  %pool1 = call ptr @llvm.objc.autoreleasePoolPush()
+  %pool2 = call ptr @llvm.objc.autoreleasePoolPush()
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool2)
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool1)
+  ret void
+}
+
+;.
+; CHECK: [[META0]] = !{}
+;.

>From fed45ffd19a318aa18523c9db6abe6faefbc2277 Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Thu, 19 Jun 2025 11:43:54 -0400
Subject: [PATCH 2/3] Jon's suggestions

---
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 74 +++++++--------------
 1 file changed, 23 insertions(+), 51 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index c8e5c51045dd3..6a2520129648e 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -2507,7 +2507,7 @@ static bool checkLeadsToTarget(Value *Val, User *Target, unsigned MaxDepth,
 
     // For pointer casts, recursively check their users
     if (isa<CastInst>(U)) {
-      if (checkLeadsToTarget(cast<Value>(U), Target, MaxDepth - 1, Visited)) {
+      if (checkLeadsToTarget(U, Target, MaxDepth - 1, Visited)) {
         return true;
       }
     }
@@ -2559,54 +2559,22 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
         // 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)
+        // Early exit if this pop doesn't match the pending push
+        if (Pop->getArgOperand(0)->stripPointerCasts() != PendingPush)
           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;
-          }
-        }
+        // Early exit if there were autoreleases in this scope
+        if (HasAutoreleaseInScope)
+          break;
 
-        if (!HasAutoreleaseInScope && SafeToOptimize) {
-          LLVM_DEBUG(dbgs() << "Eliminating empty autorelease pool pair: "
-                            << *PendingPush << " and " << *Pop << "\n");
+        // Optimize: eliminate this empty autorelease pool pair
+        LLVM_DEBUG(dbgs() << "Eliminating empty autorelease pool pair: "
+                          << *PendingPush << " and " << *Pop << "\n");
 
-          // Store the pair for careful deletion later
-          EmptyPoolPairs.push_back({PendingPush, Pop});
+        // Store the pair for careful deletion later
+        EmptyPoolPairs.push_back({PendingPush, Pop});
 
-          ++NumNoops;
-        }
+        ++NumNoops;
         break;
       }
       case ARCInstKind::CallOrUser:
@@ -2665,19 +2633,23 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
     DeadInsts.push_back(Push);
   }
 
-  // Remove the pairs
+  // Remove the pairs and emit individual remarks
   if (!DeadInsts.empty()) {
     Changed = true;
+    
+    // Emit one remark per eliminated pair for better source location tracking
+    for (auto &Pair : EmptyPoolPairs) {
+      CallInst *Push = Pair.first;
+      ORE.emit([&]() {
+        return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination", Push)
+               << "eliminated empty autorelease pool pair";
+      });
+    }
+    
     for (CallInst *DeadInst : DeadInsts) {
       LLVM_DEBUG(dbgs() << "Erasing dead instruction: " << *DeadInst << "\n");
       DeadInst->eraseFromParent();
     }
-
-    ORE.emit([&]() {
-      return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination", &F)
-             << "eliminated " << ore::NV("NumPairs", EmptyPoolPairs.size())
-             << " empty autorelease pool pair(s)";
-    });
   }
 }
 

>From e4ea8b59b959adb1323fc87b88430be532ced55e Mon Sep 17 00:00:00 2001
From: Rose <gfunni234 at gmail.com>
Date: Thu, 19 Jun 2025 11:54:05 -0400
Subject: [PATCH 3/3] LoadWeak, FusedRetainAutorelease, and
 FusedRetainAutoreleaseRV can autorelease

So prevent optimization when we see those to be safe.
---
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index 6a2520129648e..874549a44d860 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -2580,7 +2580,10 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
       case ARCInstKind::CallOrUser:
       case ARCInstKind::Call:
       case ARCInstKind::Autorelease:
-      case ARCInstKind::AutoreleaseRV: {
+      case ARCInstKind::AutoreleaseRV:
+      case ARCInstKind::FusedRetainAutorelease:
+      case ARCInstKind::FusedRetainAutoreleaseRV:
+      case ARCInstKind::LoadWeak: {
         // Track that we have autorelease calls in the current pool scope
         if (!PoolStack.empty()) {
           PoolStack.back().second = true; // Set has_autorelease_in_scope = true
@@ -2599,12 +2602,9 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
       case ARCInstKind::RetainBlock:
       case ARCInstKind::Release:
       case ARCInstKind::NoopCast:
-      case ARCInstKind::FusedRetainAutorelease:
-      case ARCInstKind::FusedRetainAutoreleaseRV:
       case ARCInstKind::LoadWeakRetained:
       case ARCInstKind::StoreWeak:
       case ARCInstKind::InitWeak:
-      case ARCInstKind::LoadWeak:
       case ARCInstKind::MoveWeak:
       case ARCInstKind::CopyWeak:
       case ARCInstKind::DestroyWeak:



More information about the llvm-commits mailing list