[llvm] [ObjCARC] Optimize MayAutorelease by skipping over pools (PR #188583)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 3 15:31:00 PDT 2026


https://github.com/SiliconA-Z updated https://github.com/llvm/llvm-project/pull/188583

>From 49fbdf090b3ade915ab42bd7a87ad10d1f32f7e4 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Wed, 25 Mar 2026 20:42:12 -0400
Subject: [PATCH 1/3] Pre-commit test (NFC)

---
 .../ObjCARC/test_autorelease_pool.ll          | 31 +++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
index 896717f92146f..714c46f4b15df 100644
--- a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
+++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
@@ -314,6 +314,37 @@ define ptr @function_that_might_autorelease() {
   ret ptr %autoreleased
 }
 
+; Cross-function: callee has its own inner pool containing an autorelease.
+; The caller's pool should be optimizable since the callee's autorelease
+; is contained within the callee's own pool.
+define void @test_cross_function_inner_pool_caller() {
+; CHECK-LABEL: define void @test_cross_function_inner_pool_caller() {
+; CHECK-NEXT:    [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT:    call void @test_cross_function_inner_pool_callee()
+; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  %pool = call ptr @llvm.objc.autoreleasePoolPush()
+  call void @test_cross_function_inner_pool_callee()
+  call void @llvm.objc.autoreleasePoolPop(ptr %pool)
+  ret void
+}
+
+define void @test_cross_function_inner_pool_callee() {
+; CHECK-LABEL: define void @test_cross_function_inner_pool_callee() {
+; CHECK-NEXT:    [[INNER_POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT:    [[OBJ:%.*]] = call ptr @create_object()
+; CHECK-NEXT:    call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0]], !clang.imprecise_release [[META0]]
+; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[INNER_POOL]]) #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+  %inner_pool = call ptr @llvm.objc.autoreleasePoolPush()
+  %obj = call ptr @create_object()
+  %ar = call ptr @llvm.objc.autorelease(ptr %obj)
+  call void @llvm.objc.autoreleasePoolPop(ptr %inner_pool)
+  ret void
+}
+
 ;.
 ; CHECK: [[META0]] = !{}
 ;.

>From 010203b4674d42af50b601103fe928d5be8d0c0f Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 27 Mar 2026 22:03:27 -0400
Subject: [PATCH 2/3] [ObjCARC] Optimize MayAutorelease by skipping over pools

Just a lot less scanning.
---
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp   | 30 ++++++++++++++++---
 .../ObjCARC/test_autorelease_pool.ll          |  2 --
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index 69c91b4327e5b..3d07052a17585 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -2502,10 +2502,34 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
     if (!Callee->hasExactDefinition())
       return true;
     for (const BasicBlock &BB : *Callee) {
-      for (const Instruction &I : BB) {
-        // TODO: Ignore all instructions between autorelease pools
+      for (auto It = BB.begin(), E = BB.end(); It != E; ++It) {
+        const Instruction &I = *It;
         ARCInstKind InstKind = GetBasicARCInstKind(&I);
         switch (InstKind) {
+        case ARCInstKind::AutoreleasepoolPush: {
+          // Skip over nested autorelease pools - autoreleases inside are
+          // drained by the nested pool and don't affect whether this function
+          // may autorelease.
+          int PoolDepth = 1;
+          auto J = std::next(It);
+          for (; J != E && PoolDepth > 0; ++J) {
+            ARCInstKind NestedKind = GetBasicARCInstKind(&*J);
+            if (NestedKind == ARCInstKind::AutoreleasepoolPush)
+              ++PoolDepth;
+            else if (NestedKind == ARCInstKind::AutoreleasepoolPop)
+              --PoolDepth;
+          }
+          // If we found the matching pop, skip to after it
+          if (PoolDepth == 0)
+            It = std::prev(J); // Will be incremented by loop to point after pop
+          // Unmatched push - continue scanning
+          break;
+        }
+
+        case ARCInstKind::AutoreleasepoolPop:
+          // Skip pop instructions (we only process them when matching a push)
+          break;
+
         case ARCInstKind::Autorelease:
         case ARCInstKind::AutoreleaseRV:
         case ARCInstKind::FusedRetainAutorelease:
@@ -2527,8 +2551,6 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
         case ARCInstKind::CopyWeak:
         case ARCInstKind::DestroyWeak:
         case ARCInstKind::StoreStrong:
-        case ARCInstKind::AutoreleasepoolPush:
-        case ARCInstKind::AutoreleasepoolPop:
           // These ObjC runtime functions don't produce autoreleases
           break;
 
diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
index 714c46f4b15df..d94ad884b60c4 100644
--- a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
+++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
@@ -319,9 +319,7 @@ define ptr @function_that_might_autorelease() {
 ; is contained within the callee's own pool.
 define void @test_cross_function_inner_pool_caller() {
 ; CHECK-LABEL: define void @test_cross_function_inner_pool_caller() {
-; CHECK-NEXT:    [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
 ; CHECK-NEXT:    call void @test_cross_function_inner_pool_callee()
-; CHECK-NEXT:    call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
 ; CHECK-NEXT:    ret void
 ;
   %pool = call ptr @llvm.objc.autoreleasePoolPush()

>From fc5f4e396947d1ed42a7a407a81fd439054c6e37 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 3 Apr 2026 17:39:47 -0400
Subject: [PATCH 3/3] Make it stack based

---
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 47 ++++++++++-----------
 1 file changed, 22 insertions(+), 25 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index 3d07052a17585..4971d98a34909 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -2502,32 +2502,20 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
     if (!Callee->hasExactDefinition())
       return true;
     for (const BasicBlock &BB : *Callee) {
-      for (auto It = BB.begin(), E = BB.end(); It != E; ++It) {
-        const Instruction &I = *It;
+      // Track nested autorelease pools in a single pass. Autoreleases inside a
+      // pool are drained before the pool ends; only effects at function scope
+      // (empty stack) or in a pool not closed in this block matter.
+      SmallVector<bool, 4> PoolStack;
+      for (const Instruction &I : BB) {
         ARCInstKind InstKind = GetBasicARCInstKind(&I);
         switch (InstKind) {
-        case ARCInstKind::AutoreleasepoolPush: {
-          // Skip over nested autorelease pools - autoreleases inside are
-          // drained by the nested pool and don't affect whether this function
-          // may autorelease.
-          int PoolDepth = 1;
-          auto J = std::next(It);
-          for (; J != E && PoolDepth > 0; ++J) {
-            ARCInstKind NestedKind = GetBasicARCInstKind(&*J);
-            if (NestedKind == ARCInstKind::AutoreleasepoolPush)
-              ++PoolDepth;
-            else if (NestedKind == ARCInstKind::AutoreleasepoolPop)
-              --PoolDepth;
-          }
-          // If we found the matching pop, skip to after it
-          if (PoolDepth == 0)
-            It = std::prev(J); // Will be incremented by loop to point after pop
-          // Unmatched push - continue scanning
+        case ARCInstKind::AutoreleasepoolPush:
+          PoolStack.push_back(false);
           break;
-        }
 
         case ARCInstKind::AutoreleasepoolPop:
-          // Skip pop instructions (we only process them when matching a push)
+          if (!PoolStack.empty())
+            PoolStack.pop_back();
           break;
 
         case ARCInstKind::Autorelease:
@@ -2536,7 +2524,10 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
         case ARCInstKind::FusedRetainAutoreleaseRV:
         case ARCInstKind::LoadWeak:
           // These may produce autoreleases
-          return true;
+          if (PoolStack.empty())
+            return true;
+          PoolStack.back() = true;
+          break;
 
         case ARCInstKind::Retain:
         case ARCInstKind::RetainRV:
@@ -2556,9 +2547,13 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
 
         case ARCInstKind::CallOrUser:
         case ARCInstKind::Call:
-          // For non-ObjC function calls, recursively analyze
-          if (MayAutorelease(cast<CallBase>(I), Depth + 1))
-            return true;
+          // For non-ObjC function calls, recursively analyze. Calls inside a
+          // pool are skipped the same way as explicit autorelease ops inside
+          // it.
+          if (PoolStack.empty()) {
+            if (MayAutorelease(cast<CallBase>(I), Depth + 1))
+              return true;
+          }
           break;
 
         case ARCInstKind::IntrinsicUser:
@@ -2568,6 +2563,8 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
           break;
         }
       }
+      if (!PoolStack.empty() && llvm::is_contained(PoolStack, true))
+        return true;
     }
     return false;
   }



More information about the llvm-commits mailing list