Skip to content

Commit 28fe9af

Browse files
committed
[ObjC][ARC] Prevent moving objc_retain calls past objc_release calls
that release the retained object This patch fixes what looks like a longstanding bug in ARC optimizer where it reverses the order of objc_retain calls and objc_release calls that retain and release the same object. The code in ARC optimizer that is responsible for code motion takes the following steps: 1. Traverse the CFG bottom-up and determine how far up objc_release calls can be moved. Determine the insertion points for the objc_release calls, but don't actually move them. 2. Traverse the CFG top-down and determine how far down objc_retain calls can be moved. Determine the insertion points for the objc_retain calls, but don't actually move them. 3. Try to move the objc_retain and objc_release calls if they can't be removed. The problem is that the insertion points for the objc_retain calls are determined in step 2 without taking into consideration the insertion points for objc_release calls determined in step 1, so the order of an objc_retain call and an objc_release call can be reversed, which is incorrect, even though each step is correct in isolation. To fix this bug, this patch teaches the top-down traversal step to take into consideration the insertion points for objc_release calls determined in the bottom-up traversal step. Code motion for an objc_retain call is disabled if there is a possibility that it can be moved past an objc_release call that releases the retained object. rdar://79292791 Differential Revision: https://reviews.llvm.org/D104953
1 parent 0f31f68 commit 28fe9af

File tree

2 files changed

+95
-37
lines changed

2 files changed

+95
-37
lines changed

llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,12 +532,15 @@ class ObjCARCOpt {
532532
bool VisitBottomUp(BasicBlock *BB,
533533
DenseMap<const BasicBlock *, BBState> &BBStates,
534534
BlotMapVector<Value *, RRInfo> &Retains);
535-
bool VisitInstructionTopDown(Instruction *Inst,
536-
DenseMap<Value *, RRInfo> &Releases,
537-
BBState &MyStates);
538-
bool VisitTopDown(BasicBlock *BB,
539-
DenseMap<const BasicBlock *, BBState> &BBStates,
540-
DenseMap<Value *, RRInfo> &Releases);
535+
bool VisitInstructionTopDown(
536+
Instruction *Inst, DenseMap<Value *, RRInfo> &Releases, BBState &MyStates,
537+
const DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
538+
&ReleaseInsertPtToRCIdentityRoots);
539+
bool VisitTopDown(
540+
BasicBlock *BB, DenseMap<const BasicBlock *, BBState> &BBStates,
541+
DenseMap<Value *, RRInfo> &Releases,
542+
const DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
543+
&ReleaseInsertPtToRCIdentityRoots);
541544
bool Visit(Function &F, DenseMap<const BasicBlock *, BBState> &BBStates,
542545
BlotMapVector<Value *, RRInfo> &Retains,
543546
DenseMap<Value *, RRInfo> &Releases);
@@ -1495,14 +1498,62 @@ bool ObjCARCOpt::VisitBottomUp(BasicBlock *BB,
14951498
return NestingDetected;
14961499
}
14971500

1498-
bool
1499-
ObjCARCOpt::VisitInstructionTopDown(Instruction *Inst,
1500-
DenseMap<Value *, RRInfo> &Releases,
1501-
BBState &MyStates) {
1501+
// Fill ReleaseInsertPtToRCIdentityRoots, which is a map from insertion points
1502+
// to the set of RC identity roots that would be released by the release calls
1503+
// moved to the insertion points.
1504+
static void collectReleaseInsertPts(
1505+
const BlotMapVector<Value *, RRInfo> &Retains,
1506+
DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
1507+
&ReleaseInsertPtToRCIdentityRoots) {
1508+
for (auto &P : Retains) {
1509+
// Retains is a map from an objc_retain call to a RRInfo of the RC identity
1510+
// root of the call. Get the RC identity root of the objc_retain call.
1511+
Instruction *Retain = cast<Instruction>(P.first);
1512+
Value *Root = GetRCIdentityRoot(Retain->getOperand(0));
1513+
// Collect all the insertion points of the objc_release calls that release
1514+
// the RC identity root of the objc_retain call.
1515+
for (const Instruction *InsertPt : P.second.ReverseInsertPts)
1516+
ReleaseInsertPtToRCIdentityRoots[InsertPt].insert(Root);
1517+
}
1518+
}
1519+
1520+
// Get the RC identity roots from an insertion point of an objc_release call.
1521+
// Return nullptr if the passed instruction isn't an insertion point.
1522+
static const SmallPtrSet<const Value *, 2> *
1523+
getRCIdentityRootsFromReleaseInsertPt(
1524+
const Instruction *InsertPt,
1525+
const DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
1526+
&ReleaseInsertPtToRCIdentityRoots) {
1527+
auto I = ReleaseInsertPtToRCIdentityRoots.find(InsertPt);
1528+
if (I == ReleaseInsertPtToRCIdentityRoots.end())
1529+
return nullptr;
1530+
return &I->second;
1531+
}
1532+
1533+
bool ObjCARCOpt::VisitInstructionTopDown(
1534+
Instruction *Inst, DenseMap<Value *, RRInfo> &Releases, BBState &MyStates,
1535+
const DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
1536+
&ReleaseInsertPtToRCIdentityRoots) {
15021537
bool NestingDetected = false;
15031538
ARCInstKind Class = GetARCInstKind(Inst);
15041539
const Value *Arg = nullptr;
15051540

1541+
// Make sure a call to objc_retain isn't moved past insertion points of calls
1542+
// to objc_release.
1543+
if (const SmallPtrSet<const Value *, 2> *Roots =
1544+
getRCIdentityRootsFromReleaseInsertPt(
1545+
Inst, ReleaseInsertPtToRCIdentityRoots))
1546+
for (auto *Root : *Roots) {
1547+
TopDownPtrState &S = MyStates.getPtrTopDownState(Root);
1548+
// Disable code motion if the current position is S_Retain to prevent
1549+
// moving the objc_retain call past objc_release calls. If it's
1550+
// S_CanRelease or larger, it's not necessary to disable code motion as
1551+
// the insertion points that prevent the objc_retain call from moving down
1552+
// should have been set already.
1553+
if (S.GetSeq() == S_Retain)
1554+
S.SetCFGHazardAfflicted(true);
1555+
}
1556+
15061557
LLVM_DEBUG(dbgs() << " Class: " << Class << "\n");
15071558

15081559
switch (Class) {
@@ -1565,10 +1616,11 @@ ObjCARCOpt::VisitInstructionTopDown(Instruction *Inst,
15651616
return NestingDetected;
15661617
}
15671618

1568-
bool
1569-
ObjCARCOpt::VisitTopDown(BasicBlock *BB,
1570-
DenseMap<const BasicBlock *, BBState> &BBStates,
1571-
DenseMap<Value *, RRInfo> &Releases) {
1619+
bool ObjCARCOpt::VisitTopDown(
1620+
BasicBlock *BB, DenseMap<const BasicBlock *, BBState> &BBStates,
1621+
DenseMap<Value *, RRInfo> &Releases,
1622+
const DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
1623+
&ReleaseInsertPtToRCIdentityRoots) {
15721624
LLVM_DEBUG(dbgs() << "\n== ObjCARCOpt::VisitTopDown ==\n");
15731625
bool NestingDetected = false;
15741626
BBState &MyStates = BBStates[BB];
@@ -1608,7 +1660,8 @@ ObjCARCOpt::VisitTopDown(BasicBlock *BB,
16081660
for (Instruction &Inst : *BB) {
16091661
LLVM_DEBUG(dbgs() << " Visiting " << Inst << "\n");
16101662

1611-
NestingDetected |= VisitInstructionTopDown(&Inst, Releases, MyStates);
1663+
NestingDetected |= VisitInstructionTopDown(
1664+
&Inst, Releases, MyStates, ReleaseInsertPtToRCIdentityRoots);
16121665

16131666
// Bail out if the number of pointers being tracked becomes too large so
16141667
// that this pass can complete in a reasonable amount of time.
@@ -1728,10 +1781,15 @@ bool ObjCARCOpt::Visit(Function &F,
17281781
return false;
17291782
}
17301783

1784+
DenseMap<const Instruction *, SmallPtrSet<const Value *, 2>>
1785+
ReleaseInsertPtToRCIdentityRoots;
1786+
collectReleaseInsertPts(Retains, ReleaseInsertPtToRCIdentityRoots);
1787+
17311788
// Use reverse-postorder for top-down.
17321789
bool TopDownNestingDetected = false;
17331790
for (BasicBlock *BB : llvm::reverse(PostOrder)) {
1734-
TopDownNestingDetected |= VisitTopDown(BB, BBStates, Releases);
1791+
TopDownNestingDetected |=
1792+
VisitTopDown(BB, BBStates, Releases, ReleaseInsertPtToRCIdentityRoots);
17351793
if (DisableRetainReleasePairing)
17361794
return false;
17371795
}

llvm/test/Transforms/ObjCARC/code-motion.ll

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,32 @@ define void @test2() {
3939
ret void
4040
}
4141

42-
; ARC optimizer shouldn't reverse the order of retains and releases in
43-
; if.then in @test3 and @test4.
42+
; Check that code motion is disabled in @test3 and @test4.
43+
; Previously, ARC optimizer would move the release past the retain.
44+
45+
; if.then:
46+
; call void @readOnlyFunc(i8* %obj, i8* null)
47+
; call void @llvm.objc.release(i8* %obj) #1, !clang.imprecise_release !2
48+
; %1 = add i32 1, 2
49+
; %2 = tail call i8* @llvm.objc.retain(i8* %obj)
50+
;
51+
; Ideally, the retain/release pairs in BB if.then should be removed.
4452

4553
define void @test3(i8* %obj, i1 %cond) {
4654
; CHECK-LABEL: @test3(
55+
; CHECK-NEXT: [[TMP2:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ:%.*]])
4756
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
4857
; CHECK: if.then:
49-
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ:%.*]], i8* null)
50-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]]) {{.*}}, !clang.imprecise_release !2
58+
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ]], i8* null)
5159
; CHECK-NEXT: [[TMP1:%.*]] = add i32 1, 2
52-
; CHECK-NEXT: [[TMP2:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ]])
5360
; CHECK-NEXT: call void @alterRefCount()
5461
; CHECK-NEXT: br label [[JOIN:%.*]]
5562
; CHECK: if.else:
56-
; CHECK-NEXT: [[TMP3:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ]])
5763
; CHECK-NEXT: call void @alterRefCount()
5864
; CHECK-NEXT: call void @use(i8* [[OBJ]])
59-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]]) {{.*}}, !clang.imprecise_release !2
6065
; CHECK-NEXT: br label [[JOIN]]
6166
; CHECK: join:
67+
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]]) {{.*}}, !clang.imprecise_release !2
6268
; CHECK-NEXT: ret void
6369
;
6470
%v0 = call i8* @llvm.objc.retain(i8* %obj)
@@ -82,26 +88,22 @@ join:
8288

8389
define void @test4(i8* %obj0, i8* %obj1, i1 %cond) {
8490
; CHECK-LABEL: @test4(
91+
; CHECK-NEXT: [[TMP3:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ0:%.*]])
92+
; CHECK-NEXT: [[TMP2:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ1:%.*]])
8593
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
8694
; CHECK: if.then:
87-
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ0:%.*]], i8* [[OBJ1:%.*]])
88-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ1]]) {{.*}}, !clang.imprecise_release !2
89-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ0]]) {{.*}}, !clang.imprecise_release !2
95+
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ0]], i8* [[OBJ1]])
9096
; CHECK-NEXT: [[TMP1:%.*]] = add i32 1, 2
91-
; CHECK-NEXT: [[TMP2:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ1]])
92-
; CHECK-NEXT: [[TMP3:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ0]])
9397
; CHECK-NEXT: call void @alterRefCount()
9498
; CHECK-NEXT: br label [[JOIN:%.*]]
9599
; CHECK: if.else:
96-
; CHECK-NEXT: [[TMP4:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ1]])
97-
; CHECK-NEXT: [[TMP5:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ0]])
98100
; CHECK-NEXT: call void @alterRefCount()
99101
; CHECK-NEXT: call void @use(i8* [[OBJ0]])
100-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ0]]) {{.*}}, !clang.imprecise_release !2
101102
; CHECK-NEXT: call void @use(i8* [[OBJ1]])
102-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ1]]) {{.*}}, !clang.imprecise_release !2
103103
; CHECK-NEXT: br label [[JOIN]]
104104
; CHECK: join:
105+
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ0]]) {{.*}}, !clang.imprecise_release !2
106+
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ1]]) {{.*}}, !clang.imprecise_release !2
105107
; CHECK-NEXT: ret void
106108
;
107109
%v0 = call i8* @llvm.objc.retain(i8* %obj0)
@@ -131,27 +133,25 @@ join:
131133

132134
define void @test5(i8* %obj, i1 %cond0, i1 %cond1) {
133135
; CHECK-LABEL: @test5(
136+
; CHECK-NEXT: [[V0:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ:%.*]])
134137
; CHECK-NEXT: br i1 [[COND0:%.*]], label [[IF_THEN:%.*]], label [[IF_ELSE:%.*]]
135138
; CHECK: if.then:
136-
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ:%.*]], i8* null)
137-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]])
139+
; CHECK-NEXT: call void @readOnlyFunc(i8* [[OBJ]], i8* null)
138140
; CHECK-NEXT: br i1 [[COND1:%.*]], label [[IF_THEN2:%.*]], label [[IF_ELSE2:%.*]]
139141
; CHECK: if.then2:
140142
; CHECK-NEXT: br label [[BB1:%.*]]
141143
; CHECK: if.else2:
142144
; CHECK-NEXT: br label [[BB1]]
143145
; CHECK: bb1:
144146
; CHECK-NEXT: [[TMP1:%.*]] = add i32 1, 2
145-
; CHECK-NEXT: [[TMP2:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ]])
146147
; CHECK-NEXT: call void @alterRefCount()
147148
; CHECK-NEXT: br label [[JOIN:%.*]]
148149
; CHECK: if.else:
149-
; CHECK-NEXT: [[TMP3:%.*]] = tail call i8* @llvm.objc.retain(i8* [[OBJ]])
150150
; CHECK-NEXT: call void @alterRefCount()
151151
; CHECK-NEXT: call void @use(i8* [[OBJ]])
152-
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]])
153152
; CHECK-NEXT: br label [[JOIN]]
154153
; CHECK: join:
154+
; CHECK-NEXT: call void @llvm.objc.release(i8* [[OBJ]])
155155
; CHECK-NEXT: ret void
156156
;
157157
%v0 = call i8* @llvm.objc.retain(i8* %obj)

0 commit comments

Comments
 (0)