From: Michael Gottesman Date: Fri, 5 Apr 2013 22:54:28 +0000 (+0000) Subject: Properly model precise lifetime when given an incomplete dataflow sequence. X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=e7ce2b3f75da1cdb657753592b73cda4252817ef;p=oota-llvm.git 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. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@178921 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Transforms/ObjCARC/ObjCARCOpts.cpp b/lib/Transforms/ObjCARC/ObjCARCOpts.cpp index 3b1d7a863f3..9f59b68d68a 100644 --- a/lib/Transforms/ObjCARC/ObjCARCOpts.cpp +++ b/lib/Transforms/ObjCARC/ObjCARCOpts.cpp @@ -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(Inst)->isTailCall(); Releases[Inst] = S.RRI; ANNOTATE_TOPDOWN(Inst, Arg, S.GetSeq(), S_None); diff --git a/test/Transforms/ObjCARC/basic.ll b/test/Transforms/ObjCARC/basic.ll index 828a8a70112..021aebb126f 100644 --- a/test/Transforms/ObjCARC/basic.ll +++ b/test/Transforms/ObjCARC/basic.ll @@ -20,6 +20,7 @@ declare void @callee() declare void @callee_fnptr(void ()*) declare void @invokee() declare i8* @returner() +declare void @bar(i32 ()*) declare void @llvm.dbg.value(metadata, i64, metadata) @@ -28,10 +29,11 @@ declare i8* @objc_msgSend(i8*, i8*, ...) ; Simple retain+release pair deletion, with some intervening control ; flow and harmless instructions. -; CHECK: define void @test0( -; CHECK-NOT: @objc_ +; CHECK: define void @test0_precise( +; CHECK: @objc_retain +; CHECK: @objc_release ; CHECK: } -define void @test0(i32* %x, i1 %p) nounwind { +define void @test0_precise(i32* %x, i1 %p) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -53,16 +55,41 @@ return: ret void } +; CHECK: define void @test0_imprecise( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test0_imprecise(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void +} + ; Like test0 but the release isn't always executed when the retain is, ; so the optimization is not safe. ; TODO: Make the objc_release's argument be %0. -; CHECK: define void @test1( +; CHECK: define void @test1_precise( ; CHECK: @objc_retain(i8* %a) ; CHECK: @objc_release ; CHECK: } -define void @test1(i32* %x, i1 %p, i1 %q) nounwind { +define void @test1_precise(i32* %x, i1 %p, i1 %q) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -88,9 +115,69 @@ alt_return: ret void } +; CHECK: define void @test1_imprecise( +; CHECK: @objc_retain(i8* %a) +; CHECK: @objc_release +; CHECK: } +define void @test1_imprecise(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + call void @callee() + br i1 %q, label %return, label %alt_return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void + +alt_return: + ret void +} + + ; Don't do partial elimination into two different CFG diamonds. -; CHECK: define void @test1b( +; CHECK: define void @test1b_precise( +; CHECK: entry: +; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW:#[0-9]+]] +; CHECK-NOT: @objc_ +; CHECK: if.end5: +; CHECK: tail call void @objc_release(i8* %x) [[NUW]] +; CHECK-NOT: @objc_ +; CHECK: } +define void @test1b_precise(i8* %x, i1 %p, i1 %q) { +entry: + tail call i8* @objc_retain(i8* %x) nounwind + br i1 %p, label %if.then, label %if.end + +if.then: ; preds = %entry + tail call void @callee() + br label %if.end + +if.end: ; preds = %if.then, %entry + br i1 %q, label %if.then3, label %if.end5 + +if.then3: ; preds = %if.end + tail call void @use_pointer(i8* %x) + br label %if.end5 + +if.end5: ; preds = %if.then3, %if.end + tail call void @objc_release(i8* %x) nounwind + ret void +} + +; CHECK: define void @test1b_imprecise( ; CHECK: entry: ; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW:#[0-9]+]] ; CHECK-NOT: @objc_ @@ -98,7 +185,7 @@ alt_return: ; CHECK: tail call void @objc_release(i8* %x) [[NUW]], !clang.imprecise_release !0 ; CHECK-NOT: @objc_ ; CHECK: } -define void @test1b(i8* %x, i1 %p, i1 %q) { +define void @test1b_imprecise(i8* %x, i1 %p, i1 %q) { entry: tail call i8* @objc_retain(i8* %x) nounwind br i1 %p, label %if.then, label %if.end @@ -119,14 +206,15 @@ if.end5: ; preds = %if.then3, %if.end ret void } + ; Like test0 but the pointer is passed to an intervening call, ; so the optimization is not safe. -; CHECK: define void @test2( +; CHECK: define void @test2_precise( ; CHECK: @objc_retain(i8* %a) ; CHECK: @objc_release ; CHECK: } -define void @test2(i32* %x, i1 %p) nounwind { +define void @test2_precise(i32* %x, i1 %p) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -151,16 +239,45 @@ return: ret void } +; CHECK: define void @test2_imprecise( +; CHECK: @objc_retain(i8* %a) +; CHECK: @objc_release +; CHECK: } +define void @test2_imprecise(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + store i32 7, i32* %x + call void @use_pointer(i8* %0) + %d = bitcast i32* %x to float* + store float 3.0, float* %d + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void +} + ; Like test0 but the release is in a loop, ; so the optimization is not safe. ; TODO: For now, assume this can't happen. -; CHECK: define void @test3( +; CHECK: define void @test3_precise( ; TODO: @objc_retain(i8* %a) ; TODO: @objc_release ; CHECK: } -define void @test3(i32* %x, i1* %q) nounwind { +define void @test3_precise(i32* %x, i1* %q) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -176,16 +293,37 @@ return: ret void } +; CHECK: define void @test3_imprecise( +; TODO: @objc_retain(i8* %a) +; TODO: @objc_release +; CHECK: } +define void @test3_imprecise(i32* %x, i1* %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br label %loop + +loop: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + %j = load volatile i1* %q + br i1 %j, label %loop, label %return + +return: + ret void +} + + ; TODO: For now, assume this can't happen. ; Like test0 but the retain is in a loop, ; so the optimization is not safe. -; CHECK: define void @test4( +; CHECK: define void @test4_precise( ; TODO: @objc_retain(i8* %a) ; TODO: @objc_release ; CHECK: } -define void @test4(i32* %x, i1* %q) nounwind { +define void @test4_precise(i32* %x, i1* %q) nounwind { entry: br label %loop @@ -201,14 +339,35 @@ return: ret void } +; CHECK: define void @test4_imprecise( +; TODO: @objc_retain(i8* %a) +; TODO: @objc_release +; CHECK: } +define void @test4_imprecise(i32* %x, i1* %q) nounwind { +entry: + br label %loop + +loop: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + %j = load volatile i1* %q + br i1 %j, label %loop, label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void +} + + ; Like test0 but the pointer is conditionally passed to an intervening call, ; so the optimization is not safe. -; CHECK: define void @test5( +; CHECK: define void @test5a( ; CHECK: @objc_retain(i8* ; CHECK: @objc_release ; CHECK: } -define void @test5(i32* %x, i1 %q, i8* %y) nounwind { +define void @test5a(i32* %x, i1 %q, i8* %y) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -220,13 +379,98 @@ entry: ret void } +; CHECK: define void @test5b( +; CHECK: @objc_retain(i8* +; CHECK: @objc_release +; CHECK: } +define void @test5b(i32* %x, i1 %q, i8* %y) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + %s = select i1 %q, i8* %y, i8* %0 + call void @use_pointer(i8* %s) + store i32 7, i32* %x + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void +} + + ; retain+release pair deletion, where the release happens on two different ; flow paths. -; CHECK: define void @test6( +; CHECK: define void @test6a( +; CHECK: entry: +; CHECK: tail call i8* @objc_retain( +; CHECK: t: +; CHECK: call void @objc_release( +; CHECK: f: +; CHECK: call void @objc_release( +; CHECK: return: +; CHECK: } +define void @test6a(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + %ct = bitcast i32* %x to i8* + call void @objc_release(i8* %ct) nounwind + br label %return + +f: + store i32 7, i32* %x + call void @callee() + %cf = bitcast i32* %x to i8* + call void @objc_release(i8* %cf) nounwind + br label %return + +return: + ret void +} + +; CHECK: define void @test6b( ; CHECK-NOT: @objc_ ; CHECK: } -define void @test6(i32* %x, i1 %p) nounwind { +define void @test6b(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + %ct = bitcast i32* %x to i8* + call void @objc_release(i8* %ct) nounwind, !clang.imprecise_release !0 + br label %return + +f: + store i32 7, i32* %x + call void @callee() + %cf = bitcast i32* %x to i8* + call void @objc_release(i8* %cf) nounwind, !clang.imprecise_release !0 + br label %return + +return: + ret void +} + +; CHECK: define void @test6c( +; CHECK: entry: +; CHECK: tail call i8* @objc_retain( +; CHECK: t: +; CHECK: call void @objc_release( +; CHECK: f: +; CHECK: call void @objc_release( +; CHECK: return: +; CHECK: } +define void @test6c(i32* %x, i1 %p) nounwind { entry: %a = bitcast i32* %x to i8* %0 = call i8* @objc_retain(i8* %a) nounwind @@ -240,6 +484,40 @@ t: call void @objc_release(i8* %ct) nounwind br label %return +f: + store i32 7, i32* %x + call void @callee() + %cf = bitcast i32* %x to i8* + call void @objc_release(i8* %cf) nounwind, !clang.imprecise_release !0 + br label %return + +return: + ret void +} + +; CHECK: define void @test6d( +; CHECK: entry: +; CHECK: tail call i8* @objc_retain( +; CHECK: t: +; CHECK: call void @objc_release( +; CHECK: f: +; CHECK: call void @objc_release( +; CHECK: return: +; CHECK: } +define void @test6d(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + %0 = call i8* @objc_retain(i8* %a) nounwind + br i1 %p, label %t, label %f + +t: + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + %ct = bitcast i32* %x to i8* + call void @objc_release(i8* %ct) nounwind, !clang.imprecise_release !0 + br label %return + f: store i32 7, i32* %x call void @callee() @@ -251,11 +529,19 @@ return: ret void } + ; retain+release pair deletion, where the retain happens on two different ; flow paths. -; CHECK: define void @test7( -; CHECK-NOT: @objc_ +; CHECK: define void @test7( +; CHECK: entry: +; CHECK-NOT: objc_ +; CHECK: t: +; CHECK: call i8* @objc_retain +; CHECK: f: +; CHECK: call i8* @objc_retain +; CHECK: return: +; CHECK: call void @objc_release ; CHECK: } define void @test7(i32* %x, i1 %p) nounwind { entry: @@ -281,17 +567,44 @@ return: ret void } +; CHECK: define void @test7b( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test7b(i32* %x, i1 %p) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %return + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + call void @callee() + br label %return + +return: + %c = bitcast i32* %x to i8* + call void @objc_release(i8* %c) nounwind, !clang.imprecise_release !0 + ret void +} + ; Like test7, but there's a retain/retainBlock mismatch. Don't delete! -; CHECK: define void @test7b +; CHECK: define void @test7c ; CHECK: t: -; CHECK: call i8* @objc_retainBlock +; CHECK: call i8* @objc_retainBlock ; CHECK: f: -; CHECK: call i8* @objc_retain +; CHECK: call i8* @objc_retain ; CHECK: return: -; CHECK: call void @objc_release +; CHECK: call void @objc_release ; CHECK: } -define void @test7b(i32* %x, i1 %p) nounwind { +define void @test7c(i32* %x, i1 %p) nounwind { entry: %a = bitcast i32* %x to i8* br i1 %p, label %t, label %f @@ -318,10 +631,154 @@ return: ; retain+release pair deletion, where the retain and release both happen on ; different flow paths. Wild! -; CHECK: define void @test8( -; CHECK-NOT: @objc_ +; CHECK: define void @test8a( +; CHECK: entry: +; CHECK: t: +; CHECK: @objc_retain +; CHECK: f: +; CHECK: @objc_retain +; CHECK: mid: +; CHECK: u: +; CHECK: @objc_release +; CHECK: g: +; CHECK: @objc_release +; CHECK: return: +; CHECK: } +define void @test8a(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %mid + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + br label %mid + +mid: + br i1 %q, label %u, label %g + +u: + call void @callee() + %cu = bitcast i32* %x to i8* + call void @objc_release(i8* %cu) nounwind + br label %return + +g: + %cg = bitcast i32* %x to i8* + call void @objc_release(i8* %cg) nounwind + br label %return + +return: + ret void +} + +; CHECK: define void @test8b( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test8b(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %mid + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + br label %mid + +mid: + br i1 %q, label %u, label %g + +u: + call void @callee() + %cu = bitcast i32* %x to i8* + call void @objc_release(i8* %cu) nounwind, !clang.imprecise_release !0 + br label %return + +g: + %cg = bitcast i32* %x to i8* + call void @objc_release(i8* %cg) nounwind, !clang.imprecise_release !0 + br label %return + +return: + ret void +} + +; CHECK: define void @test8c( +; CHECK: entry: +; CHECK: t: +; CHECK: @objc_retain +; CHECK: f: +; CHECK: @objc_retain +; CHECK: mid: +; CHECK: u: +; CHECK: @objc_release +; CHECK: g: +; CHECK: @objc_release +; CHECK: return: +; CHECK: } +define void @test8c(i32* %x, i1 %p, i1 %q) nounwind { +entry: + %a = bitcast i32* %x to i8* + br i1 %p, label %t, label %f + +t: + %0 = call i8* @objc_retain(i8* %a) nounwind + store i8 3, i8* %a + %b = bitcast i32* %x to float* + store float 2.0, float* %b + br label %mid + +f: + %1 = call i8* @objc_retain(i8* %a) nounwind + store i32 7, i32* %x + br label %mid + +mid: + br i1 %q, label %u, label %g + +u: + call void @callee() + %cu = bitcast i32* %x to i8* + call void @objc_release(i8* %cu) nounwind + br label %return + +g: + %cg = bitcast i32* %x to i8* + call void @objc_release(i8* %cg) nounwind, !clang.imprecise_release !0 + br label %return + +return: + ret void +} + +; CHECK: define void @test8d( +; CHECK: entry: +; CHECK: t: +; CHECK: @objc_retain +; CHECK: f: +; CHECK: @objc_retain +; CHECK: mid: +; CHECK: u: +; CHECK: @objc_release +; CHECK: g: +; CHECK: @objc_release +; CHECK: return: ; CHECK: } -define void @test8(i32* %x, i1 %p, i1 %q) nounwind { +define void @test8d(i32* %x, i1 %p, i1 %q) nounwind { entry: %a = bitcast i32* %x to i8* br i1 %p, label %t, label %f @@ -344,7 +801,7 @@ mid: u: call void @callee() %cu = bitcast i32* %x to i8* - call void @objc_release(i8* %cu) nounwind + call void @objc_release(i8* %cu) nounwind, !clang.imprecise_release !0 br label %return g: @@ -583,7 +1040,9 @@ entry: ; CHECK: define void @test15b ; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_retain ; CHECK-NEXT: @objc_autorelease +; CHECK-NEXT: @objc_release ; CHECK-NEXT: ret void ; CHECK-NEXT: } define void @test15b(i8* %x, i64 %n) { @@ -594,13 +1053,60 @@ entry: ret void } +; CHECK: define void @test15c +; CHECK-NEXT: entry: +; CHECK-NEXT: @objc_autorelease +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test15c(i8* %x, i64 %n) { +entry: + call i8* @objc_retain(i8* %x) nounwind + call i8* @objc_autorelease(i8* %x) nounwind + call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + ret void +} + ; Retain+release pairs in diamonds, all dominated by a retain. -; CHECK: define void @test16( +; CHECK: define void @test16a( +; CHECK: @objc_retain(i8* %x) +; CHECK-NOT: @objc +; CHECK: } +define void @test16a(i1 %a, i1 %b, i8* %x) { +entry: + call i8* @objc_retain(i8* %x) nounwind + br i1 %a, label %red, label %orange + +red: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +orange: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +yellow: + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + br i1 %b, label %green, label %blue + +green: + call void @objc_release(i8* %x) nounwind + br label %purple + +blue: + call void @objc_release(i8* %x) nounwind + br label %purple + +purple: + ret void +} + +; CHECK: define void @test16b( ; CHECK: @objc_retain(i8* %x) ; CHECK-NOT: @objc ; CHECK: } -define void @test16(i1 %a, i1 %b, i8* %x) { +define void @test16b(i1 %a, i1 %b, i8* %x) { entry: call i8* @objc_retain(i8* %x) nounwind br i1 %a, label %red, label %orange @@ -619,17 +1125,86 @@ yellow: br i1 %b, label %green, label %blue green: + call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + br label %purple + +blue: call void @objc_release(i8* %x) nounwind br label %purple +purple: + ret void +} + +; CHECK: define void @test16c( +; CHECK: @objc_retain(i8* %x) +; CHECK-NOT: @objc +; CHECK: } +define void @test16c(i1 %a, i1 %b, i8* %x) { +entry: + call i8* @objc_retain(i8* %x) nounwind + br i1 %a, label %red, label %orange + +red: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +orange: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +yellow: + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + br i1 %b, label %green, label %blue + +green: + call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + br label %purple + blue: + call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + br label %purple + +purple: + ret void +} + +; CHECK: define void @test16d( +; CHECK: @objc_retain(i8* %x) +; CHECK-NOT: @objc +; CHECK: } +define void @test16d(i1 %a, i1 %b, i8* %x) { +entry: + call i8* @objc_retain(i8* %x) nounwind + br i1 %a, label %red, label %orange + +red: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +orange: + call i8* @objc_retain(i8* %x) nounwind + br label %yellow + +yellow: + call void @use_pointer(i8* %x) + call void @use_pointer(i8* %x) + br i1 %b, label %green, label %blue + +green: call void @objc_release(i8* %x) nounwind br label %purple +blue: + call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0 + br label %purple + purple: ret void } + ; Retain+release pairs in diamonds, all post-dominated by a release. ; CHECK: define void @test17( @@ -1163,12 +1738,16 @@ done: ret void } -; Delete retain,release if there's just a possible dec. +; Delete retain,release if there's just a possible dec and we have imprecise +; releases. -; CHECK: define void @test34( -; CHECK-NOT: @objc_ +; CHECK: define void @test34a( +; CHECK: call i8* @objc_retain +; CHECK: true: +; CHECK: done: +; CHECK: call void @objc_release ; CHECK: } -define void @test34(i8* %p, i1 %x, i8* %y) { +define void @test34a(i8* %p, i1 %x, i8* %y) { entry: %f0 = call i8* @objc_retain(i8* %p) br i1 %x, label %true, label %done @@ -1184,12 +1763,38 @@ done: ret void } -; Delete retain,release if there's just a use. - -; CHECK: define void @test35( +; CHECK: define void @test34b( ; CHECK-NOT: @objc_ ; CHECK: } -define void @test35(i8* %p, i1 %x, i8* %y) { +define void @test34b(i8* %p, i1 %x, i8* %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + call void @callee() + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g), !clang.imprecise_release !0 + ret void +} + + +; Delete retain,release if there's just a use and we do not have a precise +; release. + +; Precise. +; CHECK: define void @test35a( +; CHECK: entry: +; CHECK: call i8* @objc_retain +; CHECK: true: +; CHECK: done: +; CHECK: call void @objc_release +; CHECK: } +define void @test35a(i8* %p, i1 %x, i8* %y) { entry: %f0 = call i8* @objc_retain(i8* %p) br i1 %x, label %true, label %done @@ -1205,16 +1810,36 @@ done: ret void } -; Delete a retain,release if there's no actual use. - -; CHECK: define void @test36( +; Imprecise. +; CHECK: define void @test35b( ; CHECK-NOT: @objc_ +; CHECK: } +define void @test35b(i8* %p, i1 %x, i8* %y) { +entry: + %f0 = call i8* @objc_retain(i8* %p) + br i1 %x, label %true, label %done + +true: + %v = icmp eq i8* %p, %y + br label %done + +done: + %g = bitcast i8* %p to i8* + %h = getelementptr i8* %g, i64 0 + call void @objc_release(i8* %g), !clang.imprecise_release !0 + ret void +} + +; Delete a retain,release if there's no actual use and we have precise release. + +; CHECK: define void @test36a( +; CHECK: @objc_retain ; CHECK: call void @callee() ; CHECK-NOT: @objc_ ; CHECK: call void @callee() -; CHECK-NOT: @objc_ +; CHECK: @objc_release ; CHECK: } -define void @test36(i8* %p) { +define void @test36a(i8* %p) { entry: call i8* @objc_retain(i8* %p) call void @callee() @@ -1225,10 +1850,10 @@ entry: ; Like test36, but with metadata. -; CHECK: define void @test37( +; CHECK: define void @test36b( ; CHECK-NOT: @objc_ ; CHECK: } -define void @test37(i8* %p) { +define void @test36b(i8* %p) { entry: call i8* @objc_retain(i8* %p) call void @callee() @@ -1499,27 +2124,45 @@ define i8* @test49(i8* %p) nounwind { ret i8* %x } -; Do delete retain+release with intervening stores of the -; address value. +; Do delete retain+release with intervening stores of the address value if we +; have imprecise release attached to objc_release. + +; CHECK: define void @test50a( +; CHECK-NEXT: call i8* @objc_retain +; CHECK-NEXT: call void @callee +; CHECK-NEXT: store +; CHECK-NEXT: call void @objc_release +; CHECK-NEXT: ret void +; CHECK-NEXT: } +define void @test50a(i8* %p, i8** %pp) { + call i8* @objc_retain(i8* %p) + call void @callee() + store i8* %p, i8** %pp + call void @objc_release(i8* %p) + ret void +} -; CHECK: define void @test50( +; CHECK: define void @test50b( ; CHECK-NOT: @objc_ ; CHECK: } -define void @test50(i8* %p, i8** %pp) { +define void @test50b(i8* %p, i8** %pp) { call i8* @objc_retain(i8* %p) call void @callee() store i8* %p, i8** %pp - call void @objc_release(i8* %p) + call void @objc_release(i8* %p), !clang.imprecise_release !0 ret void } + ; Don't delete retain+release with intervening stores through the ; address value. -; CHECK: define void @test51( +; CHECK: define void @test51a( ; CHECK: call i8* @objc_retain(i8* %p) ; CHECK: call void @objc_release(i8* %p) -define void @test51(i8* %p) { +; CHECK: ret void +; CHECK: } +define void @test51a(i8* %p) { call i8* @objc_retain(i8* %p) call void @callee() store i8 0, i8* %p @@ -1527,15 +2170,30 @@ define void @test51(i8* %p) { ret void } +; CHECK: define void @test51b( +; CHECK: call i8* @objc_retain(i8* %p) +; CHECK: call void @objc_release(i8* %p) +; CHECK: ret void +; CHECK: } +define void @test51b(i8* %p) { + call i8* @objc_retain(i8* %p) + call void @callee() + store i8 0, i8* %p + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + ; Don't delete retain+release with intervening use of a pointer of ; unknown provenance. -; CHECK: define void @test52( +; CHECK: define void @test52a( ; CHECK: call i8* @objc_retain ; CHECK: call void @callee() ; CHECK: call void @use_pointer(i8* %z) ; CHECK: call void @objc_release -define void @test52(i8** %zz, i8** %pp) { +; CHECK: ret void +; CHECK: } +define void @test52a(i8** %zz, i8** %pp) { %p = load i8** %pp %1 = call i8* @objc_retain(i8* %p) call void @callee() @@ -1545,6 +2203,23 @@ define void @test52(i8** %zz, i8** %pp) { ret void } +; CHECK: define void @test52b( +; CHECK: call i8* @objc_retain +; CHECK: call void @callee() +; CHECK: call void @use_pointer(i8* %z) +; CHECK: call void @objc_release +; CHECK: ret void +; CHECK: } +define void @test52b(i8** %zz, i8** %pp) { + %p = load i8** %pp + %1 = call i8* @objc_retain(i8* %p) + call void @callee() + %z = load i8** %zz + call void @use_pointer(i8* %z) + call void @objc_release(i8* %p), !clang.imprecise_release !0 + ret void +} + ; Like test52, but the pointer has function type, so it's assumed to ; be not reference counted. ; Oops. That's wrong. Clang sometimes uses function types gratuitously. @@ -1697,19 +2372,78 @@ entry: @constptr = external constant i8* @something = external global i8* -; CHECK: define void @test60( -; CHECK-NOT: @objc_ +; We have a precise lifetime retain/release here. We can not remove them since +; @something is not constant. + +; CHECK: define void @test60a( +; CHECK: call i8* @objc_retain +; CHECK: call void @objc_release +; CHECK: } +define void @test60a() { + %t = load i8** @constptr + %s = load i8** @something + call i8* @objc_retain(i8* %s) + call void @callee() + call void @use_pointer(i8* %t) + call void @objc_release(i8* %s) + ret void +} + +; CHECK: define void @test60b( +; CHECK: call i8* @objc_retain +; CHECK-NOT: call i8* @objc_retain +; CHECK-NOT: call i8* @objc_rrelease ; CHECK: } -define void @test60() { +define void @test60b() { %t = load i8** @constptr %s = load i8** @something call i8* @objc_retain(i8* %s) + call i8* @objc_retain(i8* %s) call void @callee() call void @use_pointer(i8* %t) call void @objc_release(i8* %s) ret void } +; CHECK: define void @test60c( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test60c() { + %t = load i8** @constptr + %s = load i8** @something + call i8* @objc_retain(i8* %s) + call void @callee() + call void @use_pointer(i8* %t) + call void @objc_release(i8* %s), !clang.imprecise_release !0 + ret void +} + +; CHECK: define void @test60d( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test60d() { + %t = load i8** @constptr + %s = load i8** @something + call i8* @objc_retain(i8* %t) + call void @callee() + call void @use_pointer(i8* %s) + call void @objc_release(i8* %t) + ret void +} + +; CHECK: define void @test60e( +; CHECK-NOT: @objc_ +; CHECK: } +define void @test60e() { + %t = load i8** @constptr + %s = load i8** @something + call i8* @objc_retain(i8* %t) + call void @callee() + call void @use_pointer(i8* %s) + call void @objc_release(i8* %t), !clang.imprecise_release !0 + ret void +} + ; Constant pointers to objects don't need to be considered related to other ; pointers. @@ -1873,32 +2607,6 @@ return: ; preds = %if.then, %entry ret i8* %retval } -; An objc_retain can serve as a may-use for a different pointer. -; rdar://11931823 - -; CHECK: define void @test66( -; CHECK: %tmp7 = tail call i8* @objc_retain(i8* %cond) [[NUW]] -; CHECK: tail call void @objc_release(i8* %cond) [[NUW]] -; CHECK: } -define void @test66(i8* %tmp5, i8* %bar, i1 %tobool, i1 %tobool1, i8* %call) { -entry: - br i1 %tobool, label %cond.true, label %cond.end - -cond.true: - br label %cond.end - -cond.end: ; preds = %cond.true, %entry - %cond = phi i8* [ %tmp5, %cond.true ], [ %call, %entry ] - %tmp7 = tail call i8* @objc_retain(i8* %cond) nounwind - tail call void @objc_release(i8* %call) nounwind - %tmp8 = select i1 %tobool1, i8* %cond, i8* %bar - %tmp9 = tail call i8* @objc_retain(i8* %tmp8) nounwind - tail call void @objc_release(i8* %cond) nounwind - ret void -} - -declare void @bar(i32 ()*) - ; A few real-world testcases. @.str4 = private unnamed_addr constant [33 x i8] c"-[A z] = { %f, %f, { %f, %f } }\0A\00" diff --git a/test/Transforms/ObjCARC/intrinsic-use.ll b/test/Transforms/ObjCARC/intrinsic-use.ll index 9c7b81a95d2..2f7c7c8bf35 100644 --- a/test/Transforms/ObjCARC/intrinsic-use.ll +++ b/test/Transforms/ObjCARC/intrinsic-use.ll @@ -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 !{} + diff --git a/test/Transforms/ObjCARC/retain-block-escape-analysis.ll b/test/Transforms/ObjCARC/retain-block-escape-analysis.ll index 2c1ddce3283..8df05ad2266 100644 --- a/test/Transforms/ObjCARC/retain-block-escape-analysis.ll +++ b/test/Transforms/ObjCARC/retain-block-escape-analysis.ll @@ -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 ;