[llvm] [ObjCARC] Delete retain+release pairs (PR #207469)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 3 15:26:14 PDT 2026


https://github.com/AZero13 created https://github.com/llvm/llvm-project/pull/207469

If a release is immediately followed by a retain on the same pointer, their net effect on the reference count is zero. Removing them reduces the number of instructions and ARC runtime calls.

>From a6caa33ea2c3d643ee4a55c76149ac7cfa52a7b0 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 3 Jul 2026 17:57:38 -0400
Subject: [PATCH] [ObjCARC] Delete retain+release pairs

If a release is immediately followed by a retain on the same pointer, their net effect on the reference count is zero. Removing them reduces the number of instructions and ARC runtime calls.
---
 llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp | 38 +++++++++++++++++++--
 llvm/test/Transforms/ObjCARC/basic.ll       | 11 ++++++
 2 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
index a32f824299b09..bdaceca26542e 100644
--- a/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
+++ b/llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
@@ -154,8 +154,6 @@ static const Value *FindSingleUseIdentifiedObject(const Value *Arg) {
 
 // TODO: The pointer returned from objc_loadWeakRetained is retained.
 
-// TODO: Delete release+retain pairs (rare).
-
 STATISTIC(NumNoops,       "Number of no-op objc calls eliminated");
 STATISTIC(NumPartialNoops, "Number of partially no-op objc calls eliminated");
 STATISTIC(NumAutoreleases,"Number of autoreleases converted to releases");
@@ -2474,6 +2472,42 @@ bool ObjCARCOpt::run(Function &F, AAResults &AA) {
                             (1 << unsigned(ARCInstKind::AutoreleasepoolPop))))
     OptimizeAutoreleasePools(F);
 
+  // Late peephole: Delete release+retain pairs.
+  // We do this late so it doesn't disrupt the adjacent pair heuristic in
+  // OptimizeSequences.
+  if (UsedInThisFunction & (1 << unsigned(ARCInstKind::Retain))) {
+    if (UsedInThisFunction & (1 << unsigned(ARCInstKind::Release))) {
+      for (BasicBlock &BB : F) {
+        // Live, not-yet-erased instructions seen so far (debug/pseudo
+        // excluded), most recent on top. A stack instead of a single
+        // PrevInst pointer lets a whole cascade collapse in one pass:
+        // popping a cancelled pair re-exposes whatever came before it.
+        SmallVector<Instruction *, 8> Worklist;
+        for (Instruction &Inst : llvm::make_early_inc_range(BB)) {
+          if (Inst.isDebugOrPseudoInst())
+            continue;
+
+          ARCInstKind Class = GetBasicARCInstKind(&Inst);
+          if (Class == ARCInstKind::Retain && !Worklist.empty() &&
+              GetBasicARCInstKind(Worklist.back()) == ARCInstKind::Release) {
+            Instruction *Prev = Worklist.back();
+            if (GetArgRCIdentityRoot(&Inst) == GetArgRCIdentityRoot(Prev)) {
+              Worklist.pop_back();
+              Changed = true;
+              ++NumRRs;
+              LLVM_DEBUG(dbgs() << "Erasing release+retain pair: " << *Prev
+                                << " and " << Inst << "\n");
+              EraseInstruction(&Inst);
+              EraseInstruction(Prev);
+              continue;
+            }
+          }
+          Worklist.push_back(&Inst);
+        }
+      }
+    }
+  }
+
   // Gather statistics after optimization.
 #ifndef NDEBUG
   if (AreStatisticsEnabled()) {
diff --git a/llvm/test/Transforms/ObjCARC/basic.ll b/llvm/test/Transforms/ObjCARC/basic.ll
index 24f2862ffe5c7..2da1cfcf7bc43 100644
--- a/llvm/test/Transforms/ObjCARC/basic.ll
+++ b/llvm/test/Transforms/ObjCARC/basic.ll
@@ -2939,6 +2939,17 @@ define void @test68(ptr %a, ptr %b) {
   ret void
 }
 
+; CHECK-LABEL: define void @test69_release_retain(
+; CHECK-NOT:     call void @llvm.objc.release
+; CHECK-NOT:     call ptr @llvm.objc.retain
+; CHECK:         ret void
+
+define void @test69_release_retain(ptr %a) {
+  call void @llvm.objc.release(ptr %a), !clang.imprecise_release !0
+  call ptr @llvm.objc.retain(ptr %a)
+  ret void
+}
+
 !llvm.module.flags = !{!1}
 !llvm.dbg.cu = !{!3}
 



More information about the llvm-commits mailing list