Skip to content

Commit 2c6c15d

Browse files
committed
[ObjCARC] Delete empty autoreleasepools with no autoreleases in them
Erase empty autorelease pools that have no autorelease in them
1 parent d265105 commit 2c6c15d

File tree

3 files changed

+253
-5
lines changed

3 files changed

+253
-5
lines changed

llvm/lib/Transforms/ObjCARC/ARCRuntimeEntryPoints.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ enum class ARCRuntimeEntryPointKind {
4646
UnsafeClaimRV,
4747
RetainAutorelease,
4848
RetainAutoreleaseRV,
49+
AutoreleasePoolPush,
50+
AutoreleasePoolPop,
4951
};
5052

5153
/// Declarations for ObjC runtime functions and constants. These are initialized
@@ -67,6 +69,8 @@ class ARCRuntimeEntryPoints {
6769
UnsafeClaimRV = nullptr;
6870
RetainAutorelease = nullptr;
6971
RetainAutoreleaseRV = nullptr;
72+
AutoreleasePoolPush = nullptr;
73+
AutoreleasePoolPop = nullptr;
7074
}
7175

7276
Function *get(ARCRuntimeEntryPointKind kind) {
@@ -101,6 +105,12 @@ class ARCRuntimeEntryPoints {
101105
case ARCRuntimeEntryPointKind::RetainAutoreleaseRV:
102106
return getIntrinsicEntryPoint(RetainAutoreleaseRV,
103107
Intrinsic::objc_retainAutoreleaseReturnValue);
108+
case ARCRuntimeEntryPointKind::AutoreleasePoolPush:
109+
return getIntrinsicEntryPoint(AutoreleasePoolPush,
110+
Intrinsic::objc_autoreleasePoolPush);
111+
case ARCRuntimeEntryPointKind::AutoreleasePoolPop:
112+
return getIntrinsicEntryPoint(AutoreleasePoolPop,
113+
Intrinsic::objc_autoreleasePoolPop);
104114
}
105115

106116
llvm_unreachable("Switch should be a covered switch.");
@@ -143,6 +153,12 @@ class ARCRuntimeEntryPoints {
143153
/// Declaration for objc_retainAutoreleaseReturnValue().
144154
Function *RetainAutoreleaseRV = nullptr;
145155

156+
/// Declaration for objc_autoreleasePoolPush().
157+
Function *AutoreleasePoolPush = nullptr;
158+
159+
/// Declaration for objc_autoreleasePoolPop().
160+
Function *AutoreleasePoolPop = nullptr;
161+
146162
Function *getIntrinsicEntryPoint(Function *&Decl, Intrinsic::ID IntID) {
147163
if (Decl)
148164
return Decl;

llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "llvm/Analysis/ObjCARCAnalysisUtils.h"
4040
#include "llvm/Analysis/ObjCARCInstKind.h"
4141
#include "llvm/Analysis/ObjCARCUtil.h"
42+
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
4243
#include "llvm/IR/BasicBlock.h"
4344
#include "llvm/IR/CFG.h"
4445
#include "llvm/IR/Constant.h"
@@ -132,11 +133,8 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) {
132133
//
133134
// The second retain and autorelease can be deleted.
134135

135-
// TODO: It should be possible to delete
136-
// objc_autoreleasePoolPush and objc_autoreleasePoolPop
137-
// pairs if nothing is actually autoreleased between them. Also, autorelease
138-
// calls followed by objc_autoreleasePoolPop calls (perhaps in ObjC++ code
139-
// after inlining) can be turned into plain release calls.
136+
// TODO: Autorelease calls followed by objc_autoreleasePoolPop calls (perhaps in
137+
// ObjC++ code after inlining) can be turned into plain release calls.
140138

141139
// TODO: Critical-edge splitting. If the optimial insertion point is
142140
// a critical edge, the current algorithm has to fail, because it doesn't
@@ -566,6 +564,8 @@ class ObjCARCOpt {
566564

567565
void OptimizeReturns(Function &F);
568566

567+
void OptimizeAutoreleasePools(Function &F);
568+
569569
template <typename PredicateT>
570570
static void cloneOpBundlesIf(CallBase *CI,
571571
SmallVectorImpl<OperandBundleDef> &OpBundles,
@@ -2473,6 +2473,11 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24732473
(1 << unsigned(ARCInstKind::AutoreleaseRV))))
24742474
OptimizeReturns(F);
24752475

2476+
// Optimizations for autorelease pools.
2477+
if (UsedInThisFunction & ((1 << unsigned(ARCInstKind::AutoreleasepoolPush)) |
2478+
(1 << unsigned(ARCInstKind::AutoreleasepoolPop))))
2479+
OptimizeAutoreleasePools(F);
2480+
24762481
// Gather statistics after optimization.
24772482
#ifndef NDEBUG
24782483
if (AreStatisticsEnabled()) {
@@ -2485,6 +2490,115 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24852490
return Changed;
24862491
}
24872492

2493+
/// Optimize autorelease pools by eliminating empty push/pop pairs.
2494+
void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
2495+
LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n");
2496+
2497+
OptimizationRemarkEmitter ORE(&F);
2498+
2499+
// Process each basic block independently.
2500+
// TODO: Can we optimize inter-block autorelease pool pairs?
2501+
// This would involve tracking autorelease pool state across blocks.
2502+
for (BasicBlock &BB : F) {
2503+
// Use a stack to track nested autorelease pools
2504+
SmallVector<std::pair<CallInst *, bool>, 4>
2505+
PoolStack; // {push_inst, has_autorelease_in_scope}
2506+
2507+
// Use early_inc_range to safely iterate while potentially erasing instructions
2508+
for (Instruction &Inst : llvm::make_early_inc_range(BB)) {
2509+
ARCInstKind Class = GetBasicARCInstKind(&Inst);
2510+
2511+
switch (Class) {
2512+
case ARCInstKind::AutoreleasepoolPush: {
2513+
// Start tracking a new autorelease pool scope
2514+
auto *Push = cast<CallInst>(&Inst);
2515+
PoolStack.push_back(
2516+
{Push, false}); // {push_inst, has_autorelease_in_scope}
2517+
LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n");
2518+
break;
2519+
}
2520+
2521+
case ARCInstKind::AutoreleasepoolPop: {
2522+
auto *Pop = cast<CallInst>(&Inst);
2523+
2524+
if (PoolStack.empty())
2525+
break;
2526+
2527+
auto &TopPool = PoolStack.back();
2528+
CallInst *PendingPush = TopPool.first;
2529+
bool HasAutoreleaseInScope = TopPool.second;
2530+
2531+
// Pop the stack - remove this pool scope
2532+
PoolStack.pop_back();
2533+
2534+
// Early exit if this pop doesn't match the pending push
2535+
if (Pop->getArgOperand(0)->stripPointerCasts() != PendingPush)
2536+
break;
2537+
2538+
// Early exit if there were autoreleases in this scope
2539+
if (HasAutoreleaseInScope)
2540+
break;
2541+
2542+
// Optimize: eliminate this empty autorelease pool pair
2543+
ORE.emit([&]() {
2544+
return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination",
2545+
PendingPush)
2546+
<< "eliminated empty autorelease pool pair";
2547+
});
2548+
2549+
// Replace all uses of push with poison before deletion
2550+
PendingPush->replaceAllUsesWith(PoisonValue::get(PendingPush->getType()));
2551+
2552+
// Delete the instructions immediately
2553+
PendingPush->eraseFromParent();
2554+
Pop->eraseFromParent();
2555+
2556+
Changed = true;
2557+
++NumNoops;
2558+
break;
2559+
}
2560+
case ARCInstKind::CallOrUser:
2561+
case ARCInstKind::Call:
2562+
case ARCInstKind::Autorelease:
2563+
case ARCInstKind::AutoreleaseRV:
2564+
case ARCInstKind::FusedRetainAutorelease:
2565+
case ARCInstKind::FusedRetainAutoreleaseRV:
2566+
case ARCInstKind::LoadWeak: {
2567+
// Track that we have autorelease calls in the current pool scope
2568+
if (!PoolStack.empty()) {
2569+
PoolStack.back().second = true; // Set has_autorelease_in_scope = true
2570+
LLVM_DEBUG(
2571+
dbgs()
2572+
<< "Found autorelease or potiential autorelease in pool scope: "
2573+
<< Inst << "\n");
2574+
}
2575+
break;
2576+
}
2577+
2578+
// Enumerate all remaining ARCInstKind cases explicitly
2579+
case ARCInstKind::Retain:
2580+
case ARCInstKind::RetainRV:
2581+
case ARCInstKind::UnsafeClaimRV:
2582+
case ARCInstKind::RetainBlock:
2583+
case ARCInstKind::Release:
2584+
case ARCInstKind::NoopCast:
2585+
case ARCInstKind::LoadWeakRetained:
2586+
case ARCInstKind::StoreWeak:
2587+
case ARCInstKind::InitWeak:
2588+
case ARCInstKind::MoveWeak:
2589+
case ARCInstKind::CopyWeak:
2590+
case ARCInstKind::DestroyWeak:
2591+
case ARCInstKind::StoreStrong:
2592+
case ARCInstKind::IntrinsicUser:
2593+
case ARCInstKind::User:
2594+
case ARCInstKind::None:
2595+
// These instruction kinds don't affect autorelease pool optimization
2596+
break;
2597+
}
2598+
}
2599+
}
2600+
}
2601+
24882602
/// @}
24892603
///
24902604

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; Test for autorelease pool optimizations
3+
; RUN: opt -passes=objc-arc < %s -S | FileCheck %s
4+
5+
declare ptr @llvm.objc.autoreleasePoolPush()
6+
declare void @llvm.objc.autoreleasePoolPop(ptr)
7+
declare ptr @llvm.objc.autorelease(ptr)
8+
declare ptr @llvm.objc.retain(ptr)
9+
declare ptr @create_object()
10+
declare void @use_object(ptr)
11+
declare ptr @object_with_thing()
12+
declare void @opaque_callee()
13+
14+
; Test 1: Empty autorelease pool should be eliminated
15+
define void @test_empty_pool() {
16+
; CHECK-LABEL: define void @test_empty_pool() {
17+
; CHECK-NEXT: ret void
18+
;
19+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
20+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
21+
ret void
22+
}
23+
24+
; Test 2: Pool with only release should be removed
25+
define void @test_autorelease_to_release() {
26+
; CHECK-LABEL: define void @test_autorelease_to_release() {
27+
; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object()
28+
; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0:[0-9]+]], !clang.imprecise_release [[META0:![0-9]+]]
29+
; CHECK-NEXT: ret void
30+
;
31+
%obj = call ptr @create_object()
32+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
33+
call ptr @llvm.objc.autorelease(ptr %obj)
34+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
35+
ret void
36+
}
37+
38+
; Test 3: Pool with autoreleases should not be optimized
39+
define void @test_multiple_autoreleases() {
40+
; CHECK-LABEL: define void @test_multiple_autoreleases() {
41+
; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object()
42+
; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object()
43+
; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
44+
; CHECK-NEXT: call void @use_object(ptr [[OBJ1]])
45+
; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ1]]) #[[ATTR0]]
46+
; CHECK-NEXT: call void @use_object(ptr [[OBJ2]])
47+
; CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ2]]) #[[ATTR0]]
48+
; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
49+
; CHECK-NEXT: ret void
50+
;
51+
%obj1 = call ptr @create_object()
52+
%obj2 = call ptr @create_object()
53+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
54+
call void @use_object(ptr %obj1)
55+
call ptr @llvm.objc.autorelease(ptr %obj1)
56+
call void @use_object(ptr %obj2)
57+
call ptr @llvm.objc.autorelease(ptr %obj2)
58+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
59+
ret void
60+
}
61+
62+
; Test 4: Pool with calls should not be optimized
63+
define void @test_calls() {
64+
; CHECK-LABEL: define void @test_calls() {
65+
; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
66+
; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @object_with_thing()
67+
; CHECK-NEXT: call void @use_object(ptr [[OBJ1]])
68+
; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
69+
; CHECK-NEXT: ret void
70+
;
71+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
72+
%obj1 = call ptr @object_with_thing()
73+
call void @use_object(ptr %obj1)
74+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
75+
ret void
76+
}
77+
78+
; Test 5: Pool with opaque call should not be optimized
79+
define void @test_opaque_call() {
80+
; CHECK-LABEL: define void @test_opaque_call() {
81+
; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
82+
; CHECK-NEXT: call void @opaque_callee()
83+
; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
84+
; CHECK-NEXT: ret void
85+
;
86+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
87+
call void @opaque_callee()
88+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
89+
ret void
90+
}
91+
92+
; Test 6: Nested empty pools should be eliminated
93+
define void @test_nested_empty_pools() {
94+
; CHECK-LABEL: define void @test_nested_empty_pools() {
95+
; CHECK-NEXT: ret void
96+
;
97+
%pool1 = call ptr @llvm.objc.autoreleasePoolPush()
98+
%pool2 = call ptr @llvm.objc.autoreleasePoolPush()
99+
call void @llvm.objc.autoreleasePoolPop(ptr %pool2)
100+
call void @llvm.objc.autoreleasePoolPop(ptr %pool1)
101+
ret void
102+
}
103+
104+
; Test 7: Empty pool with cast should be eliminated
105+
define void @test_empty_pool_with_cast() {
106+
; CHECK-LABEL: define void @test_empty_pool_with_cast() {
107+
; CHECK-NEXT: [[CAST:%.*]] = bitcast ptr poison to ptr
108+
; CHECK-NEXT: ret void
109+
;
110+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
111+
%cast = bitcast ptr %pool to ptr
112+
call void @llvm.objc.autoreleasePoolPop(ptr %cast)
113+
ret void
114+
}
115+
116+
;.
117+
; CHECK: [[META0]] = !{}
118+
;.

0 commit comments

Comments
 (0)