Skip to content

Commit 2c0b79e

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

File tree

3 files changed

+205
-5
lines changed

3 files changed

+205
-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: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,8 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) {
132132
//
133133
// The second retain and autorelease can be deleted.
134134

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.
135+
// TODO: Autorelease calls followed by objc_autoreleasePoolPop calls (perhaps in
136+
// ObjC++ code after inlining) can be turned into plain release calls.
140137

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

567564
void OptimizeReturns(Function &F);
568565

566+
void OptimizeAutoreleasePools(Function &F);
567+
569568
template <typename PredicateT>
570569
static void cloneOpBundlesIf(CallBase *CI,
571570
SmallVectorImpl<OperandBundleDef> &OpBundles,
@@ -2473,6 +2472,11 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24732472
(1 << unsigned(ARCInstKind::AutoreleaseRV))))
24742473
OptimizeReturns(F);
24752474

2475+
// Optimizations for autorelease pools.
2476+
if (UsedInThisFunction & ((1 << unsigned(ARCInstKind::AutoreleasepoolPush)) |
2477+
(1 << unsigned(ARCInstKind::AutoreleasepoolPop))))
2478+
OptimizeAutoreleasePools(F);
2479+
24762480
// Gather statistics after optimization.
24772481
#ifndef NDEBUG
24782482
if (AreStatisticsEnabled()) {
@@ -2485,6 +2489,107 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24852489
return Changed;
24862490
}
24872491

2492+
/// Optimize autorelease pools by eliminating empty push/pop pairs.
2493+
void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
2494+
LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n");
2495+
2496+
// Track empty autorelease pool push/pop pairs
2497+
SmallVector<std::pair<CallInst *, CallInst *>, 4> EmptyPoolPairs;
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+
CallInst *PendingPush = nullptr;
2504+
bool HasAutoreleaseInScope = false;
2505+
2506+
for (Instruction &Inst : BB) {
2507+
ARCInstKind Class = GetBasicARCInstKind(&Inst);
2508+
2509+
switch (Class) {
2510+
case ARCInstKind::AutoreleasepoolPush: {
2511+
// Start tracking a new autorelease pool scope
2512+
PendingPush = cast<CallInst>(&Inst);
2513+
HasAutoreleaseInScope = false;
2514+
LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *PendingPush
2515+
<< "\n");
2516+
break;
2517+
}
2518+
2519+
case ARCInstKind::AutoreleasepoolPop: {
2520+
CallInst *Pop = cast<CallInst>(&Inst);
2521+
2522+
if (PendingPush) {
2523+
// Check if this pop matches the pending push by comparing the token
2524+
Value *PopArg = Pop->getArgOperand(0);
2525+
bool IsMatchingPop = (PopArg == PendingPush);
2526+
2527+
// Also handle bitcast case
2528+
if (!IsMatchingPop && isa<BitCastInst>(PopArg)) {
2529+
Value *BitcastSrc = cast<BitCastInst>(PopArg)->getOperand(0);
2530+
IsMatchingPop = (BitcastSrc == PendingPush);
2531+
}
2532+
2533+
if (IsMatchingPop && !HasAutoreleaseInScope) {
2534+
LLVM_DEBUG(dbgs() << "Eliminating empty autorelease pool pair: "
2535+
<< *PendingPush << " and " << *Pop << "\n");
2536+
2537+
// Store the pair for careful deletion later
2538+
EmptyPoolPairs.push_back({PendingPush, Pop});
2539+
2540+
Changed = true;
2541+
++NumNoops;
2542+
}
2543+
}
2544+
2545+
PendingPush = nullptr;
2546+
HasAutoreleaseInScope = false;
2547+
break;
2548+
}
2549+
case ARCInstKind::CallOrUser:
2550+
case ARCInstKind::Call:
2551+
case ARCInstKind::Autorelease:
2552+
case ARCInstKind::AutoreleaseRV: {
2553+
// Track that we have autorelease calls in the current pool scope
2554+
if (PendingPush) {
2555+
HasAutoreleaseInScope = true;
2556+
LLVM_DEBUG(
2557+
dbgs()
2558+
<< "Found autorelease or potiential autorelease in pool scope: "
2559+
<< Inst << "\n");
2560+
}
2561+
break;
2562+
}
2563+
2564+
default:
2565+
break;
2566+
}
2567+
}
2568+
}
2569+
2570+
// Handle empty pool pairs carefully to avoid use-after-delete
2571+
SmallVector<CallInst *, 8> DeadInsts;
2572+
for (auto &Pair : EmptyPoolPairs) {
2573+
CallInst *Push = Pair.first;
2574+
CallInst *Pop = Pair.second;
2575+
2576+
// Replace the pop's argument with poison to break dependencies
2577+
Value *PoisonToken = PoisonValue::get(Push->getType());
2578+
Pop->setArgOperand(0, PoisonToken);
2579+
2580+
LLVM_DEBUG(dbgs() << "Erasing empty pool pair: " << *Push << " and " << *Pop
2581+
<< "\n");
2582+
DeadInsts.push_back(Pop);
2583+
DeadInsts.push_back(Push);
2584+
}
2585+
2586+
// Remove the pairs
2587+
for (CallInst *DeadInst : DeadInsts) {
2588+
LLVM_DEBUG(dbgs() << "Erasing dead instruction: " << *DeadInst << "\n");
2589+
DeadInst->eraseFromParent();
2590+
}
2591+
}
2592+
24882593
/// @}
24892594
///
24902595

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
13+
; Test 1: Empty autorelease pool should be eliminated
14+
define void @test_empty_pool() {
15+
; CHECK-LABEL: define void @test_empty_pool() {
16+
; CHECK-NEXT: ret void
17+
;
18+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
19+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
20+
ret void
21+
}
22+
23+
; Test 2: Pool with only release should be removed
24+
define void @test_autorelease_to_release() {
25+
; CHECK-LABEL: define void @test_autorelease_to_release() {
26+
; CHECK-NEXT: [[OBJ:%.*]] = call ptr @create_object()
27+
; CHECK-NEXT: call void @llvm.objc.release(ptr [[OBJ]]) #[[ATTR0:[0-9]+]], !clang.imprecise_release [[META0:![0-9]+]]
28+
; CHECK-NEXT: ret void
29+
;
30+
%obj = call ptr @create_object()
31+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
32+
call ptr @llvm.objc.autorelease(ptr %obj)
33+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
34+
ret void
35+
}
36+
37+
; Test 3: Pool with autoreleases should not be optimized
38+
define void @test_multiple_autoreleases() {
39+
; CHECK-LABEL: define void @test_multiple_autoreleases() {
40+
; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @create_object()
41+
; CHECK-NEXT: [[OBJ2:%.*]] = call ptr @create_object()
42+
; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
43+
; CHECK-NEXT: call void @use_object(ptr [[OBJ1]])
44+
; CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ1]]) #[[ATTR0]]
45+
; CHECK-NEXT: call void @use_object(ptr [[OBJ2]])
46+
; CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.objc.autorelease(ptr [[OBJ2]]) #[[ATTR0]]
47+
; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
48+
; CHECK-NEXT: ret void
49+
;
50+
%obj1 = call ptr @create_object()
51+
%obj2 = call ptr @create_object()
52+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
53+
call void @use_object(ptr %obj1)
54+
call ptr @llvm.objc.autorelease(ptr %obj1)
55+
call void @use_object(ptr %obj2)
56+
call ptr @llvm.objc.autorelease(ptr %obj2)
57+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
58+
ret void
59+
}
60+
61+
; Test 4: Pool with calls should not be optimized
62+
define void @test_calls() {
63+
; CHECK-LABEL: define void @test_calls() {
64+
; CHECK-NEXT: [[POOL:%.*]] = call ptr @llvm.objc.autoreleasePoolPush() #[[ATTR0]]
65+
; CHECK-NEXT: [[OBJ1:%.*]] = call ptr @object_with_thing()
66+
; CHECK-NEXT: call void @use_object(ptr [[OBJ1]])
67+
; CHECK-NEXT: call void @llvm.objc.autoreleasePoolPop(ptr [[POOL]]) #[[ATTR0]]
68+
; CHECK-NEXT: ret void
69+
;
70+
%pool = call ptr @llvm.objc.autoreleasePoolPush()
71+
%obj1 = call ptr @object_with_thing()
72+
call void @use_object(ptr %obj1)
73+
call void @llvm.objc.autoreleasePoolPop(ptr %pool)
74+
ret void
75+
}
76+
77+
;.
78+
; CHECK: [[META0]] = !{}
79+
;.

0 commit comments

Comments
 (0)