[llvm] [ObjCARC] Delete empty autoreleasepools with no autoreleases in them (PR #144788)
Jon Roelofs via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 18 18:50:17 PDT 2025
================
@@ -2485,6 +2490,197 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
return Changed;
}
+/// Helper function to recursively check if a value eventually leads to the
+/// target instruction through pointer casts and uses, up to a specified depth.
+static bool checkLeadsToTarget(Value *Val, User *Target, unsigned MaxDepth,
+ SmallPtrSet<Value *, 8> &Visited) {
+ if (MaxDepth == 0)
+ return false;
+
+ // Avoid infinite recursion by tracking visited values
+ if (!Visited.insert(Val).second)
+ return false;
+
+ for (User *U : Val->users()) {
+ if (U == Target)
+ return true;
+
+ // For pointer casts, recursively check their users
+ if (isa<CastInst>(U)) {
+ if (checkLeadsToTarget(cast<Value>(U), Target, MaxDepth - 1, Visited)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/// Optimize autorelease pools by eliminating empty push/pop pairs.
+void ObjCARCOpt::OptimizeAutoreleasePools(Function &F) {
+ LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::OptimizeAutoreleasePools ==\n");
+
+ OptimizationRemarkEmitter ORE(&F);
+
+ // Track empty autorelease pool push/pop pairs
+ SmallVector<std::pair<CallInst *, CallInst *>, 4> EmptyPoolPairs;
+
+ // Process each basic block independently.
+ // TODO: Can we optimize inter-block autorelease pool pairs?
+ // This would involve tracking autorelease pool state across blocks.
+ for (BasicBlock &BB : F) {
+ // Use a stack to track nested autorelease pools
+ SmallVector<std::pair<CallInst *, bool>, 4>
+ PoolStack; // {push_inst, has_autorelease_in_scope}
+
+ for (Instruction &Inst : BB) {
+ ARCInstKind Class = GetBasicARCInstKind(&Inst);
+
+ switch (Class) {
+ case ARCInstKind::AutoreleasepoolPush: {
+ // Start tracking a new autorelease pool scope
+ auto *Push = cast<CallInst>(&Inst);
+ PoolStack.push_back(
+ {Push, false}); // {push_inst, has_autorelease_in_scope}
+ LLVM_DEBUG(dbgs() << "Found autorelease pool push: " << *Push << "\n");
+ break;
+ }
+
+ case ARCInstKind::AutoreleasepoolPop: {
+ auto *Pop = cast<CallInst>(&Inst);
+
+ if (PoolStack.empty())
+ break;
+
+ auto &TopPool = PoolStack.back();
+ CallInst *PendingPush = TopPool.first;
+ bool HasAutoreleaseInScope = TopPool.second;
+
+ // Pop the stack - remove this pool scope
+ PoolStack.pop_back();
+
+ // Check if this pop matches the pending push by comparing the token
+ Value *PopArg = Pop->getArgOperand(0);
+ bool IsMatchingPop = (PopArg == PendingPush);
+
+ // Also handle pointer casts by stripping them
+ if (!IsMatchingPop) {
+ Value *StrippedPopArg = PopArg->stripPointerCasts();
+ IsMatchingPop = (StrippedPopArg == PendingPush);
+ }
+
+ if (!IsMatchingPop)
+ break;
+
+ // Before adding to EmptyPoolPairs, verify ALL uses of the push
+ // eventually lead to this specific pop instruction
+ bool SafeToOptimize = true;
+ for (User *U : PendingPush->users()) {
+ // Check if this use eventually leads to our pop instruction
+ bool LeadsToExpectedPop = false;
+
+ if (U == Pop)
+ LeadsToExpectedPop = true;
+ else if (isa<CastInst>(U)) {
+ // For pointer casts, check if they eventually lead to the pop
+ // (up to 5 levels deep)
+ SmallPtrSet<Value *, 8> Visited;
+ if (checkLeadsToTarget(cast<Value>(U), Pop, 5, Visited)) {
+ LeadsToExpectedPop = true;
+ }
+ }
+
+ if (!LeadsToExpectedPop) {
+ SafeToOptimize = false;
+ LLVM_DEBUG(dbgs() << "Unsafe to optimize: push has unexpected use: "
+ << *U << "\n");
+ break;
+ }
+ }
+
+ if (!HasAutoreleaseInScope && SafeToOptimize) {
+ LLVM_DEBUG(dbgs() << "Eliminating empty autorelease pool pair: "
+ << *PendingPush << " and " << *Pop << "\n");
+
+ // Store the pair for careful deletion later
+ EmptyPoolPairs.push_back({PendingPush, Pop});
+
+ ++NumNoops;
+ }
+ break;
+ }
+ case ARCInstKind::CallOrUser:
+ case ARCInstKind::Call:
+ case ARCInstKind::Autorelease:
+ case ARCInstKind::AutoreleaseRV: {
+ // Track that we have autorelease calls in the current pool scope
+ if (!PoolStack.empty()) {
+ PoolStack.back().second = true; // Set has_autorelease_in_scope = true
+ LLVM_DEBUG(
+ dbgs()
+ << "Found autorelease or potiential autorelease in pool scope: "
+ << Inst << "\n");
+ }
+ break;
+ }
+
+ // Enumerate all remaining ARCInstKind cases explicitly
+ case ARCInstKind::Retain:
+ case ARCInstKind::RetainRV:
+ case ARCInstKind::UnsafeClaimRV:
+ case ARCInstKind::RetainBlock:
+ case ARCInstKind::Release:
+ case ARCInstKind::NoopCast:
+ case ARCInstKind::FusedRetainAutorelease:
+ case ARCInstKind::FusedRetainAutoreleaseRV:
+ case ARCInstKind::LoadWeakRetained:
+ case ARCInstKind::StoreWeak:
+ case ARCInstKind::InitWeak:
+ case ARCInstKind::LoadWeak:
+ case ARCInstKind::MoveWeak:
+ case ARCInstKind::CopyWeak:
+ case ARCInstKind::DestroyWeak:
+ case ARCInstKind::StoreStrong:
+ case ARCInstKind::IntrinsicUser:
+ case ARCInstKind::User:
+ case ARCInstKind::None:
+ // These instruction kinds don't affect autorelease pool optimization
+ break;
+ }
+ }
+ }
+
+ // Handle empty pool pairs carefully to avoid use-after-delete
+ SmallVector<CallInst *, 8> DeadInsts;
+ for (auto &Pair : EmptyPoolPairs) {
+ CallInst *Push = Pair.first;
+ CallInst *Pop = Pair.second;
+
+ // Replace all uses of push with poison before deletion
+ Push->replaceAllUsesWith(PoisonValue::get(Push->getType()));
+
+ LLVM_DEBUG(dbgs() << "Erasing empty pool pair: " << *Push << " and " << *Pop
+ << "\n");
+ DeadInsts.push_back(Pop);
+ DeadInsts.push_back(Push);
+ }
+
+ // Remove the pairs
+ if (!DeadInsts.empty()) {
+ Changed = true;
+ for (CallInst *DeadInst : DeadInsts) {
+ LLVM_DEBUG(dbgs() << "Erasing dead instruction: " << *DeadInst << "\n");
+ DeadInst->eraseFromParent();
+ }
+
+ ORE.emit([&]() {
+ return OptimizationRemark(DEBUG_TYPE, "AutoreleasePoolElimination", &F)
----------------
jroelofs wrote:
If you do one remark per `EmptyPoolPair`, the debuginfo tying it to a particular source location will be much more useful in the opt-viewer (and related tools) to help users visualize where they're coming from. Plus, `llvm-remarkutil`'s `count` tool can do the per-function aggregation after the fact.
https://github.com/llvm/llvm-project/pull/144788
More information about the llvm-commits
mailing list