Skip to content

Commit d2dedcd

Browse files
authored
[ObjCARC] Delete empty autoreleasepools with no autoreleases in them (#144788)
Erase empty autorelease pools that have no autorelease in them
1 parent 5ebbc25 commit d2dedcd

File tree

3 files changed

+522
-5
lines changed

3 files changed

+522
-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: 187 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,183 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
24852490
return Changed;
24862491
}
24872492

2493+
/// Interprocedurally determine if calls made by the given call site can
2494+
/// possibly produce autoreleases.
2495+
bool MayAutorelease(const CallBase &CB, unsigned Depth = 0) {
2496+
if (CB.onlyReadsMemory())
2497+
return false;
2498+
2499+
// This recursion depth limit is arbitrary. It's just great
2500+
// enough to cover known interesting testcases.
2501+
if (Depth > 5)
2502+
return true;
2503+
2504+
if (const Function *Callee = CB.getCalledFunction()) {
2505+
if (!Callee->hasExactDefinition())
2506+
return true;
2507+
for (const BasicBlock &BB : *Callee) {
2508+
for (const Instruction &I : BB) {
2509+
// TODO: Ignore all instructions between autorelease pools
2510+
ARCInstKind InstKind = GetBasicARCInstKind(&I);
2511+
switch (InstKind) {
2512+
case ARCInstKind::Autorelease:
2513+
case ARCInstKind::AutoreleaseRV:
2514+
case ARCInstKind::FusedRetainAutorelease:
2515+
case ARCInstKind::FusedRetainAutoreleaseRV:
2516+
case ARCInstKind::LoadWeak:
2517+
// These may produce autoreleases
2518+
return true;
2519+
2520+
case ARCInstKind::Retain:
2521+
case ARCInstKind::RetainRV:
2522+
case ARCInstKind::UnsafeClaimRV:
2523+
case ARCInstKind::RetainBlock:
2524+
case ARCInstKind::Release:
2525+
case ARCInstKind::NoopCast:
2526+
case ARCInstKind::LoadWeakRetained:
2527+
case ARCInstKind::StoreWeak:
2528+
case ARCInstKind::InitWeak:
2529+
case ARCInstKind::MoveWeak:
2530+
case ARCInstKind::CopyWeak:
2531+
case ARCInstKind::DestroyWeak:
2532+
case ARCInstKind::StoreStrong:
2533+
case ARCInstKind::AutoreleasepoolPush:
2534+
case ARCInstKind::AutoreleasepoolPop:
2535+
// These ObjC runtime functions don't produce autoreleases
2536+
break;
2537+
2538+
case ARCInstKind::CallOrUser:
2539+
case ARCInstKind::Call:
2540+
// For non-ObjC function calls, recursively analyze
2541+
if (MayAutorelease(cast<CallBase>(I), Depth + 1))
2542+
return true;
2543+
break;
2544+
2545+
case ARCInstKind::IntrinsicUser:
2546+
case ARCInstKind::User:
2547+
case ARCInstKind::None:
2548+
// These are not relevant for autorelease analysis
2549+
break;
2550+
}
2551+
}
2552+
}
2553+
return false;
2554+
}
2555+
2556+
return true;
2557+
}
2558+
2559+
/// Optimize autorelease pools by eliminating empty push/pop pairs.
2560+
void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
2561+
LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n");
2562+
2563+
OptimizationRemarkEmitter ORE(&F);
2564+
2565+
// Process each basic block independently.
2566+
// TODO: Can we optimize inter-block autorelease pool pairs?
2567+
// This would involve tracking autorelease pool state across blocks.
2568+
for (BasicBlock &BB : F) {
2569+
// Use a stack to track nested autorelease pools
2570+
SmallVector<std::pair<CallInst *, bool>, 4>
2571+
PoolStack; // {push_inst, has_autorelease_in_scope}
2572+
2573+
for (Instruction &Inst : llvm::make_early_inc_range(BB)) {
2574+
ARCInstKind Class = GetBasicARCInstKind(&Inst);
2575+
2576+
switch (Class) {
2577+
case ARCInstKind::AutoreleasepoolPush: {
2578+
// Start tracking a new autorelease pool scope
2579+
auto *Push = cast<CallInst>(&Inst);
2580+
PoolStack.push_back(
2581+
{Push, false}); // {push_inst, has_autorelease_in_scope}
2582+
LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n");
2583+
break;
2584+
}
2585+
2586+
case ARCInstKind::AutoreleasepoolPop: {
2587+
auto *Pop = cast<CallInst>(&Inst);
2588+
2589+
if (PoolStack.empty())
2590+
break;
2591+
2592+
auto &TopPool = PoolStack.back();
2593+
CallInst *PendingPush = TopPool.first;
2594+
bool HasAutoreleaseInScope = TopPool.second;
2595+
2596+
// Pop the stack - remove this pool scope
2597+
PoolStack.pop_back();
2598+
2599+
// Bail if this pop doesn't match the pending push
2600+
if (Pop->getArgOperand(0)->stripPointerCasts() != PendingPush)
2601+
break;
2602+
2603+
// Bail if there were autoreleases in this scope
2604+
if (HasAutoreleaseInScope)
2605+
break;
2606+
2607+
// Optimize: eliminate this empty autorelease pool pair
2608+
ORE.emit([&]() {
2609+
return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination",
2610+
PendingPush)
2611+
<< "eliminated empty autorelease pool pair";
2612+
});
2613+
2614+
// Replace all uses of push with poison before deletion
2615+
PendingPush->replaceAllUsesWith(
2616+
PoisonValue::get(PendingPush->getType()));
2617+
2618+
Pop->eraseFromParent();
2619+
PendingPush->eraseFromParent();
2620+
2621+
Changed = true;
2622+
++NumNoops;
2623+
break;
2624+
}
2625+
case ARCInstKind::CallOrUser:
2626+
case ARCInstKind::Call:
2627+
if (!MayAutorelease(cast<CallBase>(Inst)))
2628+
break;
2629+
LLVM_FALLTHROUGH;
2630+
case ARCInstKind::Autorelease:
2631+
case ARCInstKind::AutoreleaseRV:
2632+
case ARCInstKind::FusedRetainAutorelease:
2633+
case ARCInstKind::FusedRetainAutoreleaseRV:
2634+
case ARCInstKind::LoadWeak: {
2635+
// Track that we have autorelease calls in the current pool scope
2636+
if (!PoolStack.empty()) {
2637+
PoolStack.back().second = true; // Set has_autorelease_in_scope = true
2638+
LLVM_DEBUG(
2639+
dbgs()
2640+
<< "Found autorelease or potential autorelease in pool scope: "
2641+
<< Inst << "\n");
2642+
}
2643+
break;
2644+
}
2645+
2646+
// Enumerate all remaining ARCInstKind cases explicitly
2647+
case ARCInstKind::Retain:
2648+
case ARCInstKind::RetainRV:
2649+
case ARCInstKind::UnsafeClaimRV:
2650+
case ARCInstKind::RetainBlock:
2651+
case ARCInstKind::Release:
2652+
case ARCInstKind::NoopCast:
2653+
case ARCInstKind::LoadWeakRetained:
2654+
case ARCInstKind::StoreWeak:
2655+
case ARCInstKind::InitWeak:
2656+
case ARCInstKind::MoveWeak:
2657+
case ARCInstKind::CopyWeak:
2658+
case ARCInstKind::DestroyWeak:
2659+
case ARCInstKind::StoreStrong:
2660+
case ARCInstKind::IntrinsicUser:
2661+
case ARCInstKind::User:
2662+
case ARCInstKind::None:
2663+
// These instruction kinds don't affect autorelease pool optimization
2664+
break;
2665+
}
2666+
}
2667+
}
2668+
}
2669+
24882670
/// @}
24892671
///
24902672

0 commit comments

Comments
 (0)