Properly model precise lifetime when given an incomplete dataflow sequence.

The normal dataflow sequence in the ARC optimizer consists of the following
states:

    Retain -> CanRelease -> Use -> Release

The optimizer before this patch stored the uses that determine the lifetime of
the retainable object pointer when it bottom up hits a retain or when top down
it hits a release. This is correct for an imprecise lifetime scenario since what
we are trying to do is remove retains/releases while making sure that no
``CanRelease'' (which is usually a call) deallocates the given pointer before we
get to the ``Use'' (since that would cause a segfault).

If we are considering the precise lifetime scenario though, this is not
correct. In such a situation, we *DO* care about the previous sequence, but
additionally, we wish to track the uses resulting from the following incomplete
sequences:

  Retain -> CanRelease -> Release   (TopDown)
  Retain <- Use <- Release          (BottomUp)

*NOTE* This patch looks large but the most of it consists of updating
test cases. Additionally this fix exposed an additional bug. I removed
the test case that expressed said bug and will recommit it with the fix
in a little bit.

llvm-svn: 178921
This commit is contained in:
Michael Gottesman 2013-04-05 22:54:28 +00:00
parent 3b59f5c804
commit 1d8d25777d
4 changed files with 962 additions and 101 deletions

View File

@ -408,6 +408,10 @@ namespace {
KnownSafe(false), IsTailCallRelease(false), ReleaseMetadata(0) {}
void clear();
bool IsTrackingImpreciseReleases() {
return ReleaseMetadata != 0;
}
};
}
@ -1746,7 +1750,9 @@ ObjCARCOpt::VisitInstructionBottomUp(Instruction *Inst,
bool NestingDetected = false;
InstructionClass Class = GetInstructionClass(Inst);
const Value *Arg = 0;
DEBUG(dbgs() << "Class: " << Class << "\n");
switch (Class) {
case IC_Release: {
Arg = GetObjCArg(Inst);
@ -1794,7 +1800,10 @@ ObjCARCOpt::VisitInstructionBottomUp(Instruction *Inst,
case S_Release:
case S_MovableRelease:
case S_Use:
S.RRI.ReverseInsertPts.clear();
// If OldSeq is not S_Use or OldSeq is S_Use and we are tracking an
// imprecise release, clear our reverse insertion points.
if (OldSeq != S_Use || S.RRI.IsTrackingImpreciseReleases())
S.RRI.ReverseInsertPts.clear();
// FALL THROUGH
case S_CanRelease:
// Don't do retain+release tracking for IC_RetainRV, because it's
@ -2017,14 +2026,19 @@ ObjCARCOpt::VisitInstructionTopDown(Instruction *Inst,
PtrState &S = MyStates.getPtrTopDownState(Arg);
S.ClearKnownPositiveRefCount();
switch (S.GetSeq()) {
Sequence OldSeq = S.GetSeq();
MDNode *ReleaseMetadata = Inst->getMetadata(ImpreciseReleaseMDKind);
switch (OldSeq) {
case S_Retain:
case S_CanRelease:
S.RRI.ReverseInsertPts.clear();
if (OldSeq == S_Retain || ReleaseMetadata != 0)
S.RRI.ReverseInsertPts.clear();
// FALL THROUGH
case S_Use:
S.RRI.ReleaseMetadata = Inst->getMetadata(ImpreciseReleaseMDKind);
S.RRI.ReleaseMetadata = ReleaseMetadata;
S.RRI.IsTailCallRelease = cast<CallInst>(Inst)->isTailCall();
Releases[Inst] = S.RRI;
ANNOTATE_TOPDOWN(Inst, Arg, S.GetSeq(), S_None);

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,9 @@ declare void @test0_helper(i8*, i8**)
; CHECK-NEXT: @objc_release(i8* [[VAL1]])
; CHECK-NEXT: @objc_autorelease(i8* %x)
; CHECK-NEXT: store i8* %x, i8** %out
; CHECK-NEXT: @objc_retain(i8* %x)
; CHECK-NEXT: @objc_release(i8* [[VAL2]])
; CHECK-NEXT: @objc_release(i8* %x)
; CHECK-NEXT: ret void
define void @test0(i8** %out, i8* %x, i8* %y) {
entry:
@ -61,3 +63,52 @@ entry:
call void @objc_release(i8* %x) nounwind
ret void
}
; CHECK: define void @test0a(
; CHECK: @objc_retain(i8* %x)
; CHECK-NEXT: store i8* %y, i8** %temp0
; CHECK-NEXT: @objc_retain(i8* %y)
; CHECK-NEXT: call void @test0_helper
; CHECK-NEXT: [[VAL1:%.*]] = load i8** %temp0
; CHECK-NEXT: call void (...)* @clang.arc.use(i8* %y)
; CHECK-NEXT: @objc_retain(i8* [[VAL1]])
; CHECK-NEXT: @objc_release(i8* %y)
; CHECK-NEXT: store i8* [[VAL1]], i8** %temp1
; CHECK-NEXT: call void @test0_helper
; CHECK-NEXT: [[VAL2:%.*]] = load i8** %temp1
; CHECK-NEXT: call void (...)* @clang.arc.use(i8* [[VAL1]])
; CHECK-NEXT: @objc_retain(i8* [[VAL2]])
; CHECK-NEXT: @objc_release(i8* [[VAL1]])
; CHECK-NEXT: @objc_autorelease(i8* %x)
; CHECK-NEXT: @objc_release(i8* [[VAL2]])
; CHECK-NEXT: store i8* %x, i8** %out
; CHECK-NEXT: ret void
define void @test0a(i8** %out, i8* %x, i8* %y) {
entry:
%temp0 = alloca i8*, align 8
%temp1 = alloca i8*, align 8
%0 = call i8* @objc_retain(i8* %x) nounwind
%1 = call i8* @objc_retain(i8* %y) nounwind
store i8* %y, i8** %temp0
call void @test0_helper(i8* %x, i8** %temp0)
%val1 = load i8** %temp0
%2 = call i8* @objc_retain(i8* %val1) nounwind
call void (...)* @clang.arc.use(i8* %y) nounwind
call void @objc_release(i8* %y) nounwind, !clang.imprecise_release !0
store i8* %val1, i8** %temp1
call void @test0_helper(i8* %x, i8** %temp1)
%val2 = load i8** %temp1
%3 = call i8* @objc_retain(i8* %val2) nounwind
call void (...)* @clang.arc.use(i8* %val1) nounwind
call void @objc_release(i8* %val1) nounwind, !clang.imprecise_release !0
%4 = call i8* @objc_retain(i8* %x) nounwind
%5 = call i8* @objc_autorelease(i8* %x) nounwind
store i8* %x, i8** %out
call void @objc_release(i8* %val2) nounwind, !clang.imprecise_release !0
call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0
ret void
}
!0 = metadata !{}

View File

@ -21,6 +21,23 @@ declare i8* @objc_retainBlock(i8*)
define void @bitcasttest(i8* %storage, void (...)* %block) {
; CHECK: define void @bitcasttest
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK: tail call i8* @objc_retain
%t2 = tail call i8* @objc_retain(i8* %t1)
; CHECK: tail call i8* @objc_retainBlock
%t3 = tail call i8* @objc_retainBlock(i8* %t1), !clang.arc.copy_on_escape !0
%t4 = bitcast i8* %storage to void (...)**
%t5 = bitcast i8* %t3 to void (...)*
store void (...)* %t5, void (...)** %t4, align 8
; CHECK: call void @objc_release
call void @objc_release(i8* %t1)
ret void
; CHECK: }
}
define void @bitcasttest_a(i8* %storage, void (...)* %block) {
; CHECK: define void @bitcasttest_a
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK-NOT: tail call i8* @objc_retain
@ -31,12 +48,32 @@ entry:
%t5 = bitcast i8* %t3 to void (...)*
store void (...)* %t5, void (...)** %t4, align 8
; CHECK-NOT: call void @objc_release
call void @objc_release(i8* %t1)
call void @objc_release(i8* %t1), !clang.imprecise_release !0
ret void
; CHECK: }
}
define void @geptest(void (...)** %storage_array, void (...)* %block) {
; CHECK: define void @geptest
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK: tail call i8* @objc_retain
%t2 = tail call i8* @objc_retain(i8* %t1)
; CHECK: tail call i8* @objc_retainBlock
%t3 = tail call i8* @objc_retainBlock(i8* %t1), !clang.arc.copy_on_escape !0
%t4 = bitcast i8* %t3 to void (...)*
%storage = getelementptr inbounds void (...)** %storage_array, i64 0
store void (...)* %t4, void (...)** %storage, align 8
; CHECK: call void @objc_release
call void @objc_release(i8* %t1)
ret void
; CHECK: }
}
define void @geptest_a(void (...)** %storage_array, void (...)* %block) {
; CHECK: define void @geptest_a
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK-NOT: tail call i8* @objc_retain
@ -49,13 +86,32 @@ entry:
store void (...)* %t4, void (...)** %storage, align 8
; CHECK-NOT: call void @objc_release
call void @objc_release(i8* %t1)
call void @objc_release(i8* %t1), !clang.imprecise_release !0
ret void
; CHECK: }
}
define void @selecttest(void (...)** %store1, void (...)** %store2,
void (...)* %block) {
; CHECK: define void @selecttest
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK: tail call i8* @objc_retain
%t2 = tail call i8* @objc_retain(i8* %t1)
; CHECK: tail call i8* @objc_retainBlock
%t3 = tail call i8* @objc_retainBlock(i8* %t1), !clang.arc.copy_on_escape !0
%t4 = bitcast i8* %t3 to void (...)*
%store = select i1 undef, void (...)** %store1, void (...)** %store2
store void (...)* %t4, void (...)** %store, align 8
; CHECK: call void @objc_release
call void @objc_release(i8* %t1)
ret void
; CHECK: }
}
define void @selecttest_a(void (...)** %store1, void (...)** %store2,
void (...)* %block) {
; CHECK: define void @selecttest_a
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK-NOT: tail call i8* @objc_retain
@ -66,14 +122,45 @@ entry:
%store = select i1 undef, void (...)** %store1, void (...)** %store2
store void (...)* %t4, void (...)** %store, align 8
; CHECK-NOT: call void @objc_release
call void @objc_release(i8* %t1)
call void @objc_release(i8* %t1), !clang.imprecise_release !0
ret void
; CHECK: }
}
define void @phinodetest(void (...)** %storage1,
void (...)** %storage2,
void (...)* %block) {
; CHECK: define void @phinodetest
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK: tail call i8* @objc_retain
%t2 = tail call i8* @objc_retain(i8* %t1)
; CHECK: tail call i8* @objc_retainBlock
%t3 = tail call i8* @objc_retainBlock(i8* %t1), !clang.arc.copy_on_escape !0
%t4 = bitcast i8* %t3 to void (...)*
br i1 undef, label %store1_set, label %store2_set
; CHECK: store1_set:
store1_set:
br label %end
store2_set:
br label %end
end:
; CHECK: end:
%storage = phi void (...)** [ %storage1, %store1_set ], [ %storage2, %store2_set]
store void (...)* %t4, void (...)** %storage, align 8
; CHECK: call void @objc_release
call void @objc_release(i8* %t1)
ret void
; CHECK: }
}
define void @phinodetest_a(void (...)** %storage1,
void (...)** %storage2,
void (...)* %block) {
; CHECK: define void @phinodetest_a
entry:
%t1 = bitcast void (...)* %block to i8*
; CHECK-NOT: tail call i8* @objc_retain
@ -93,10 +180,11 @@ end:
%storage = phi void (...)** [ %storage1, %store1_set ], [ %storage2, %store2_set]
store void (...)* %t4, void (...)** %storage, align 8
; CHECK-NOT: call void @objc_release
call void @objc_release(i8* %t1)
call void @objc_release(i8* %t1), !clang.imprecise_release !0
ret void
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This test makes sure that we do not hang clang when visiting a use ;
; cycle caused by phi nodes during objc-arc analysis. *NOTE* This ;