[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