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