[llvm] 533470e - [ObjCARC] Improve empty autorelease pool elimination in OptimizeAutoreleasePools (#200310)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 8 18:17:44 PDT 2026
Author: AZero13
Date: 2026-07-08T18:17:39-07:00
New Revision: 533470ead993440e3cd79c48d77a21218e165ff2
URL: https://github.com/llvm/llvm-project/commit/533470ead993440e3cd79c48d77a21218e165ff2
DIFF: https://github.com/llvm/llvm-project/commit/533470ead993440e3cd79c48d77a21218e165ff2.diff
LOG: [ObjCARC] Improve empty autorelease pool elimination in OptimizeAutoreleasePools (#200310)
Verify push/pop pairing before popping the stack, clear the pool stack
on mismatch, and erase the push before the pop for consistency.
Added:
Modified:
llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index a32f824299b09..f92e5bbdf0111 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -2488,7 +2488,7 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
/// Interprocedurally determine if calls made by the given call site can
/// possibly produce autoreleases.
-bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
+static bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
if (CB.onlyReadsMemory())
return false;
@@ -2500,10 +2500,11 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
if (const Function *Callee = CB.getCalledFunction()) {
if (!Callee->hasExactDefinition())
return true;
+
for (const BasicBlock &BB : *Callee) {
- // 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.
+ // Track nested autorelease pools within a basic block. Autoreleases
+ // inside a pool are drained before the pool ends; only effects at block
+ // scope (empty stack) or in a pool not closed in the block matter.
SmallVector<bool, 4> PoolStack;
for (const Instruction &I : BB) {
ARCInstKind InstKind = GetBasicARCInstKind(&I);
@@ -2561,6 +2562,8 @@ bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
break;
}
}
+ // If the block ended with an un-popped pool containing an autorelease,
+ // that autorelease escapes the block.
if (!PoolStack.empty() && llvm::is_contained(PoolStack, true))
return true;
}
@@ -2580,9 +2583,9 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
// 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}
+ // Stack tracks nested autorelease pools: {push_inst,
+ // has_autorelease_in_scope}
+ SmallVector<std::pair<CallInst *, bool>, 4> PoolStack;
for (Instruction &Inst : llvm::make_early_inc_range(BB)) {
ARCInstKind Class = GetBasicARCInstKind(&Inst);
@@ -2591,8 +2594,7 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
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}
+ PoolStack.push_back({Push, false});
LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n");
break;
}
@@ -2600,37 +2602,49 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
case ARCInstKind::AutoreleasepoolPop: {
auto *Pop = cast<CallInst>(&Inst);
+ // Skip if no matching push found
if (PoolStack.empty())
break;
- auto &TopPool = PoolStack.back();
- CallInst *PendingPush = TopPool.first;
- bool HasAutoreleaseInScope = TopPool.second;
+ // Get the matching push and whether autoreleases were present
+ CallInst *MatchingPush = PoolStack.back().first;
+ bool HadAutoreleaseInScope = PoolStack.back().second;
+
+ // Verify this pop matches the push (handle pointer casts).
+ // The pop's argument should be the push result, possibly cast.
+ if (Pop->getArgOperand(0)->stripPointerCasts() != MatchingPush) {
+ // Mismatched pop.
+ // We can't trust the stack anymore, invalidating optimization for
+ // this block.
+ PoolStack.clear();
+ LLVM_DEBUG(dbgs() << "Autorelease pool mismatch: pop argument "
+ << *Pop->getArgOperand(0)
+ << " does not match most recent push "
+ << *MatchingPush << "\n");
+ break;
+ }
// Pop the stack - remove this pool scope
PoolStack.pop_back();
- // Bail if this pop doesn't match the pending push
- if (Pop->getArgOperand(0)->stripPointerCasts() != PendingPush)
- break;
-
- // Bail if there were autoreleases in this scope
- if (HasAutoreleaseInScope)
+ // Only eliminate pools that had no autoreleases in their scope.
+ if (HadAutoreleaseInScope)
break;
- // Optimize: eliminate this empty autorelease pool pair
+ // Emit the remark before erasing the instructions
ORE.emit([&]() {
return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination",
- PendingPush)
+ MatchingPush)
<< "eliminated empty autorelease pool pair";
});
- // Replace all uses of push with poison before deletion
- PendingPush->replaceAllUsesWith(
- PoisonValue::get(PendingPush->getType()));
+ // Replace all uses of push with poison before deletion, as Pop still
+ // holds a Use of it.
+ MatchingPush->replaceAllUsesWith(
+ PoisonValue::get(MatchingPush->getType()));
+ MatchingPush->eraseFromParent();
Pop->eraseFromParent();
- PendingPush->eraseFromParent();
Changed = true;
++NumNoops;
@@ -2638,6 +2652,7 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
}
case ARCInstKind::CallOrUser:
case ARCInstKind::Call:
+ // Check if this call might produce autoreleases
if (!MayAutorelease(cast<CallBase>(Inst)))
break;
[[fallthrough]];
@@ -2646,9 +2661,9 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
case ARCInstKind::FusedRetainAutorelease:
case ARCInstKind::FusedRetainAutoreleaseRV:
case ARCInstKind::LoadWeak: {
- // Track that we have autorelease calls in the current pool scope
+ // Mark that we have autorelease operations in the current pool scope
if (!PoolStack.empty()) {
- PoolStack.back().second = true; // Set has_autorelease_in_scope = true
+ PoolStack.back().second = true;
LLVM_DEBUG(
dbgs()
<< "Found autorelease or potential autorelease in pool scope: "
diff --git a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
index d94ad884b60c4..a25d85a76d1b0 100644
--- a/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
+++ b/llvm/test/Transforms/ObjCARC/test_autorelease_pool.ll
@@ -343,6 +343,39 @@ define void @test_cross_function_inner_pool_callee() {
ret void
}
+; Mismatched pop clears the pool stack and prevents outer pools from being optimized
+define void @test_mismatched_pools_outer(ptr %p) {
+; CHECK-LABEL: define void @test_mismatched_pools_outer(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[OUTER_POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT: [[INNER_POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[P]]) #[[ATTR0]]
+; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[OUTER_POOL]]) #[[ATTR0]]
+; CHECK-NEXT: ret void
+;
+ %outer_pool = call ptr @llvm.objc.autoreleasePoolPush()
+ %inner_pool = call ptr @llvm.objc.autoreleasePoolPush()
+ call void @llvm.objc.autoreleasePoolPop(ptr %p)
+ call void @llvm.objc.autoreleasePoolPop(ptr %outer_pool)
+ ret void
+}
+
+; Mismatched pop via exotic casts clears the pool stack and bails out safely
+define void @test_exotic_cast_bailout() {
+; CHECK-LABEL: define void @test_exotic_cast_bailout() {
+; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
+; CHECK-NEXT: [[INT_VAL:%.*]] = ptrtoint ptr [[POOL]] to i64
+; CHECK-NEXT: [[PTR_VAL:%.*]] = inttoptr i64 [[INT_VAL]] to ptr
+; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[PTR_VAL]]) #[[ATTR0]]
+; CHECK-NEXT: ret void
+;
+ %pool = call ptr @llvm.objc.autoreleasePoolPush()
+ %int_val = ptrtoint ptr %pool to i64
+ %ptr_val = inttoptr i64 %int_val to ptr
+ call void @llvm.objc.autoreleasePoolPop(ptr %ptr_val)
+ ret void
+}
+
;.
; CHECK: [[META0]] = !{}
;.
More information about the llvm-commits
mailing list