@@ -2490,6 +2490,122 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
2490
2490
return Changed;
2491
2491
}
2492
2492
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
+ // If we come across an autoreleasepool push and a pop in the same block,
2509
+ // we can ignore the instructions between them.
2510
+ // TODO: Can we check inter-block autorelease pool pairs?
2511
+ // This would involve tracking autorelease pool state across blocks.
2512
+
2513
+ // First pass: find all push/pop pairs and their positions in this basic
2514
+ // block
2515
+ SmallVector<std::pair<unsigned , unsigned >, 4 >
2516
+ ShadowedRegions; // (push_pos, pop_pos)
2517
+ SmallVector<std::pair<unsigned , const Instruction*>, 4 > PushStack; // (index, push_inst)
2518
+
2519
+ unsigned InstIndex = 0 ;
2520
+ for (const Instruction &I : BB) {
2521
+ if (GetBasicARCInstKind (&I) == ARCInstKind::AutoreleasepoolPush) {
2522
+ PushStack.push_back ({InstIndex, &I});
2523
+ } else if (GetBasicARCInstKind (&I) == ARCInstKind::AutoreleasepoolPop) {
2524
+ if (!PushStack.empty ()) {
2525
+ auto [PushIndex, PushInst] = PushStack.back ();
2526
+ // Verify this pop actually pops the corresponding push
2527
+ const auto *PopCall = cast<CallInst>(&I);
2528
+ if (PopCall->getArgOperand (0 )->stripPointerCasts () == PushInst) {
2529
+ // Valid push/pop pair - add to shadowed regions
2530
+ ShadowedRegions.push_back ({PushIndex, InstIndex});
2531
+ PushStack.pop_back ();
2532
+ }
2533
+ // If pop doesn't match the most recent push, it might be for an outer pool
2534
+ // in a different basic block, so we leave the stack unchanged
2535
+ }
2536
+ }
2537
+ InstIndex++;
2538
+ }
2539
+
2540
+ // Second pass: check for autoreleases, ignoring shadowed regions
2541
+ InstIndex = 0 ;
2542
+ for (const Instruction &I : BB) {
2543
+ // Check if this instruction is in a shadowed region (between inner
2544
+ // push/pop pairs)
2545
+ bool InShadowedRegion = false ;
2546
+ for (auto [PushIndex, PopIndex] : ShadowedRegions) {
2547
+ if (InstIndex > PushIndex && InstIndex < PopIndex) {
2548
+ InShadowedRegion = true ;
2549
+ break ;
2550
+ }
2551
+ }
2552
+
2553
+ if (InShadowedRegion) {
2554
+ InstIndex++;
2555
+ continue ; // Skip instructions in shadowed regions - they don't affect
2556
+ // outer pools
2557
+ }
2558
+
2559
+ ARCInstKind InstKind = GetBasicARCInstKind (&I);
2560
+ switch (InstKind) {
2561
+ case ARCInstKind::Autorelease:
2562
+ case ARCInstKind::AutoreleaseRV:
2563
+ case ARCInstKind::FusedRetainAutorelease:
2564
+ case ARCInstKind::FusedRetainAutoreleaseRV:
2565
+ case ARCInstKind::LoadWeak:
2566
+ // These may produce autoreleases
2567
+ return true ;
2568
+
2569
+ case ARCInstKind::Retain:
2570
+ case ARCInstKind::RetainRV:
2571
+ case ARCInstKind::UnsafeClaimRV:
2572
+ case ARCInstKind::RetainBlock:
2573
+ case ARCInstKind::Release:
2574
+ case ARCInstKind::NoopCast:
2575
+ case ARCInstKind::LoadWeakRetained:
2576
+ case ARCInstKind::StoreWeak:
2577
+ case ARCInstKind::InitWeak:
2578
+ case ARCInstKind::MoveWeak:
2579
+ case ARCInstKind::CopyWeak:
2580
+ case ARCInstKind::DestroyWeak:
2581
+ case ARCInstKind::StoreStrong:
2582
+ case ARCInstKind::AutoreleasepoolPush:
2583
+ case ARCInstKind::AutoreleasepoolPop:
2584
+ // These ObjC runtime functions don't produce autoreleases
2585
+ break ;
2586
+
2587
+ case ARCInstKind::CallOrUser:
2588
+ case ARCInstKind::Call:
2589
+ // For non-ObjC function calls, recursively analyze
2590
+ if (MayAutorelease (cast<CallBase>(I), Depth + 1 ))
2591
+ return true ;
2592
+ break ;
2593
+
2594
+ case ARCInstKind::IntrinsicUser:
2595
+ case ARCInstKind::User:
2596
+ case ARCInstKind::None:
2597
+ // These are not relevant for autorelease analysis
2598
+ break ;
2599
+ }
2600
+ InstIndex++;
2601
+ }
2602
+ }
2603
+ return false ;
2604
+ }
2605
+
2606
+ return true ;
2607
+ }
2608
+
2493
2609
// / Optimize autorelease pools by eliminating empty push/pop pairs.
2494
2610
void ObjCARCOpt::OptimizeAutoreleasePools (Function &F) {
2495
2611
LLVM_DEBUG (dbgs () << " \n == ObjCARCOpt::OptimizeAutoreleasePools ==\n " );
@@ -2558,6 +2674,9 @@ void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
2558
2674
}
2559
2675
case ARCInstKind::CallOrUser:
2560
2676
case ARCInstKind::Call:
2677
+ if (!MayAutorelease (cast<CallBase>(Inst)))
2678
+ break ;
2679
+ [[fallthrough]];
2561
2680
case ARCInstKind::Autorelease:
2562
2681
case ARCInstKind::AutoreleaseRV:
2563
2682
case ARCInstKind::FusedRetainAutorelease:
0 commit comments