39
39
#include " llvm/Analysis/ObjCARCAnalysisUtils.h"
40
40
#include " llvm/Analysis/ObjCARCInstKind.h"
41
41
#include " llvm/Analysis/ObjCARCUtil.h"
42
+ #include " llvm/Analysis/OptimizationRemarkEmitter.h"
42
43
#include " llvm/IR/BasicBlock.h"
43
44
#include " llvm/IR/CFG.h"
44
45
#include " llvm/IR/Constant.h"
@@ -132,11 +133,8 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) {
132
133
//
133
134
// The second retain and autorelease can be deleted.
134
135
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.
140
138
141
139
// TODO: Critical-edge splitting. If the optimial insertion point is
142
140
// a critical edge, the current algorithm has to fail, because it doesn't
@@ -566,6 +564,8 @@ class ObjCARCOpt {
566
564
567
565
void OptimizeReturns (Function &F);
568
566
567
+ void OptimizeAutoreleasePools (Function &F);
568
+
569
569
template <typename PredicateT>
570
570
static void cloneOpBundlesIf (CallBase *CI,
571
571
SmallVectorImpl<OperandBundleDef> &OpBundles,
@@ -2473,6 +2473,11 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
2473
2473
(1 << unsigned (ARCInstKind::AutoreleaseRV))))
2474
2474
OptimizeReturns (F);
2475
2475
2476
+ // Optimizations for autorelease pools.
2477
+ if (UsedInThisFunction & ((1 << unsigned (ARCInstKind::AutoreleasepoolPush)) |
2478
+ (1 << unsigned (ARCInstKind::AutoreleasepoolPop))))
2479
+ OptimizeAutoreleasePools (F);
2480
+
2476
2481
// Gather statistics after optimization.
2477
2482
#ifndef NDEBUG
2478
2483
if (AreStatisticsEnabled ()) {
@@ -2485,6 +2490,163 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
2485
2490
return Changed;
2486
2491
}
2487
2492
2493
+ // / Helper function to recursively check if a value eventually leads to the
2494
+ // / target instruction through pointer casts and uses, up to a specified depth.
2495
+ static bool checkLeadsToTarget (Value *Val, User *Target, unsigned MaxDepth,
2496
+ SmallPtrSet<Value *, 8 > &Visited) {
2497
+ if (MaxDepth == 0 )
2498
+ return false ;
2499
+
2500
+ // Avoid infinite recursion by tracking visited values
2501
+ if (!Visited.insert (Val).second )
2502
+ return false ;
2503
+
2504
+ for (User *U : Val->users ()) {
2505
+ if (U == Target)
2506
+ return true ;
2507
+
2508
+ // For pointer casts, recursively check their users
2509
+ if (isa<CastInst>(U)) {
2510
+ if (checkLeadsToTarget (U, Target, MaxDepth - 1 , Visited)) {
2511
+ return true ;
2512
+ }
2513
+ }
2514
+ }
2515
+
2516
+ return false ;
2517
+ }
2518
+
2519
+ // / Optimize autorelease pools by eliminating empty push/pop pairs.
2520
+ void ObjCARCOpt::OptimizeAutoreleasePools (Function &F) {
2521
+ LLVM_DEBUG (dbgs () << " \n == ObjCARCOpt::OptimizeAutoreleasePools ==\n " );
2522
+
2523
+ OptimizationRemarkEmitter ORE (&F);
2524
+
2525
+ // Track empty autorelease pool push/pop pairs
2526
+ SmallVector<std::pair<CallInst *, CallInst *>, 4 > EmptyPoolPairs;
2527
+
2528
+ // Process each basic block independently.
2529
+ // TODO: Can we optimize inter-block autorelease pool pairs?
2530
+ // This would involve tracking autorelease pool state across blocks.
2531
+ for (BasicBlock &BB : F) {
2532
+ // Use a stack to track nested autorelease pools
2533
+ SmallVector<std::pair<CallInst *, bool >, 4 >
2534
+ PoolStack; // {push_inst, has_autorelease_in_scope}
2535
+
2536
+ for (Instruction &Inst : BB) {
2537
+ ARCInstKind Class = GetBasicARCInstKind (&Inst);
2538
+
2539
+ switch (Class) {
2540
+ case ARCInstKind::AutoreleasepoolPush: {
2541
+ // Start tracking a new autorelease pool scope
2542
+ auto *Push = cast<CallInst>(&Inst);
2543
+ PoolStack.push_back (
2544
+ {Push, false }); // {push_inst, has_autorelease_in_scope}
2545
+ LLVM_DEBUG (dbgs () << " Found autorelease pool push: " << *Push << " \n " );
2546
+ break ;
2547
+ }
2548
+
2549
+ case ARCInstKind::AutoreleasepoolPop: {
2550
+ auto *Pop = cast<CallInst>(&Inst);
2551
+
2552
+ if (PoolStack.empty ())
2553
+ break ;
2554
+
2555
+ auto &TopPool = PoolStack.back ();
2556
+ CallInst *PendingPush = TopPool.first ;
2557
+ bool HasAutoreleaseInScope = TopPool.second ;
2558
+
2559
+ // Pop the stack - remove this pool scope
2560
+ PoolStack.pop_back ();
2561
+
2562
+ // Early exit if this pop doesn't match the pending push
2563
+ if (Pop->getArgOperand (0 )->stripPointerCasts () != PendingPush)
2564
+ break ;
2565
+
2566
+ // Early exit if there were autoreleases in this scope
2567
+ if (HasAutoreleaseInScope)
2568
+ break ;
2569
+
2570
+ // Optimize: eliminate this empty autorelease pool pair
2571
+ ORE.emit ([&]() {
2572
+ return OptimizationRemark (DEBUG_TYPE, " AutoreleasePoolElimination" ,
2573
+ PendingPush)
2574
+ << " eliminated empty autorelease pool pair" ;
2575
+ });
2576
+
2577
+ // Store the pair for careful deletion later
2578
+ EmptyPoolPairs.push_back ({PendingPush, Pop});
2579
+
2580
+ ++NumNoops;
2581
+ break ;
2582
+ }
2583
+ case ARCInstKind::CallOrUser:
2584
+ case ARCInstKind::Call:
2585
+ case ARCInstKind::Autorelease:
2586
+ case ARCInstKind::AutoreleaseRV:
2587
+ case ARCInstKind::FusedRetainAutorelease:
2588
+ case ARCInstKind::FusedRetainAutoreleaseRV:
2589
+ case ARCInstKind::LoadWeak: {
2590
+ // Track that we have autorelease calls in the current pool scope
2591
+ if (!PoolStack.empty ()) {
2592
+ PoolStack.back ().second = true ; // Set has_autorelease_in_scope = true
2593
+ LLVM_DEBUG (
2594
+ dbgs ()
2595
+ << " Found autorelease or potiential autorelease in pool scope: "
2596
+ << Inst << " \n " );
2597
+ }
2598
+ break ;
2599
+ }
2600
+
2601
+ // Enumerate all remaining ARCInstKind cases explicitly
2602
+ case ARCInstKind::Retain:
2603
+ case ARCInstKind::RetainRV:
2604
+ case ARCInstKind::UnsafeClaimRV:
2605
+ case ARCInstKind::RetainBlock:
2606
+ case ARCInstKind::Release:
2607
+ case ARCInstKind::NoopCast:
2608
+ case ARCInstKind::LoadWeakRetained:
2609
+ case ARCInstKind::StoreWeak:
2610
+ case ARCInstKind::InitWeak:
2611
+ case ARCInstKind::MoveWeak:
2612
+ case ARCInstKind::CopyWeak:
2613
+ case ARCInstKind::DestroyWeak:
2614
+ case ARCInstKind::StoreStrong:
2615
+ case ARCInstKind::IntrinsicUser:
2616
+ case ARCInstKind::User:
2617
+ case ARCInstKind::None:
2618
+ // These instruction kinds don't affect autorelease pool optimization
2619
+ break ;
2620
+ }
2621
+ }
2622
+ }
2623
+
2624
+ // Handle empty pool pairs carefully to avoid use-after-delete
2625
+ SmallVector<CallInst *, 8 > DeadInsts;
2626
+ for (auto &Pair : EmptyPoolPairs) {
2627
+ CallInst *Push = Pair.first ;
2628
+ CallInst *Pop = Pair.second ;
2629
+
2630
+ // Replace all uses of push with poison before deletion
2631
+ Push->replaceAllUsesWith (PoisonValue::get (Push->getType ()));
2632
+
2633
+ LLVM_DEBUG (dbgs () << " Erasing empty pool pair: " << *Push << " and " << *Pop
2634
+ << " \n " );
2635
+ DeadInsts.push_back (Pop);
2636
+ DeadInsts.push_back (Push);
2637
+ }
2638
+
2639
+ // Remove the pairs
2640
+ if (!DeadInsts.empty ()) {
2641
+ Changed = true ;
2642
+
2643
+ for (CallInst *DeadInst : DeadInsts) {
2644
+ LLVM_DEBUG (dbgs () << " Erasing dead instruction: " << *DeadInst << " \n " );
2645
+ DeadInst->eraseFromParent ();
2646
+ }
2647
+ }
2648
+ }
2649
+
2488
2650
// / @}
2489
2651
// /
2490
2652
0 commit comments