[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