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