-; RUN: opt -objc-arc -S < %s | FileCheck %s
+; RUN: opt -basicaa -objc-arc -S < %s | FileCheck %s
target datalayout = "e-p:64:64:64"
declare i8* @objc_retain(i8*)
+declare i8* @objc_retainAutoreleasedReturnValue(i8*)
declare void @objc_release(i8*)
declare i8* @objc_autorelease(i8*)
+declare i8* @objc_autoreleaseReturnValue(i8*)
declare void @objc_autoreleasePoolPop(i8*)
-declare void @objc_autoreleasePoolPush()
+declare i8* @objc_autoreleasePoolPush()
declare i8* @objc_retainBlock(i8*)
declare i8* @objc_retainedObject(i8*)
ret void
}
+; Don't do partial elimination into two different CFG diamonds.
+
+; CHECK: define void @test1b(
+; 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]], !clang.imprecise_release !0
+; CHECK-NOT: @objc_
+; CHECK: }
+define void @test1b(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, !clang.imprecise_release !0
+ ret void
+}
+
; Like test0 but the pointer is passed to an intervening call,
; so the optimization is not safe.
loop:
%c = bitcast i32* %x to i8*
call void @objc_release(i8* %c) nounwind
- %j = volatile load i1* %q
+ %j = load volatile i1* %q
br i1 %j, label %loop, label %return
return:
loop:
%a = bitcast i32* %x to i8*
%0 = call i8* @objc_retain(i8* %a) nounwind
- %j = volatile load i1* %q
+ %j = load volatile i1* %q
br i1 %j, label %loop, label %return
return:
; CHECK: define void @test10(
; CHECK: @objc_retain(i8* %x)
+; CHECK: @callee
; CHECK: @use_pointer
; CHECK: @objc_release
; CHECK: }
define void @test10(i8* %x) nounwind {
entry:
%0 = call i8* @objc_retain(i8* %x) nounwind
- call void @use_pointer(i8* %x)
+ call void @callee()
call void @use_pointer(i8* %x)
call void @objc_release(i8* %0) nounwind
ret void
; a stack argument.
; CHECK: define void @test11(
-; CHECK: tail call i8* @objc_retain(i8* %x) nounwind
-; CHECK: tail call i8* @objc_autorelease(i8* %0) nounwind
+; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW]]
+; CHECK: call i8* @objc_autorelease(i8* %0) [[NUW]]
; CHECK: }
define void @test11(i8* %x) nounwind {
entry:
; Same as test11 but the value is returned. Do an RV optimization.
; CHECK: define i8* @test11b(
-; CHECK: tail call i8* @objc_retain(i8* %x) nounwind
-; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %0) nounwind
+; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW]]
+; CHECK: tail call i8* @objc_autoreleaseReturnValue(i8* %0) [[NUW]]
; CHECK: }
define i8* @test11b(i8* %x) nounwind {
entry:
; Trivial retain,autorelease pair. Don't delete!
; CHECK: define void @test13(
-; CHECK: tail call i8* @objc_retain(i8* %x) nounwind
-; CHECK: tail call i8* @objc_retain(i8* %x) nounwind
+; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW]]
+; CHECK: tail call i8* @objc_retain(i8* %x) [[NUW]]
; CHECK: @use_pointer(i8* %x)
-; CHECK: tail call i8* @objc_autorelease(i8* %x) nounwind
+; CHECK: call i8* @objc_autorelease(i8* %x) [[NUW]]
; CHECK: }
define void @test13(i8* %x, i64 %n) {
entry:
define void @test13d(i8* %x, i64 %n) {
entry:
call i8* @objc_retain(i8* %x) nounwind
- call void @objc_autoreleasePoolPush()
+ call i8* @objc_autoreleasePoolPush()
call i8* @objc_retain(i8* %x) nounwind
call void @use_pointer(i8* %x)
call void @use_pointer(i8* %x)
; Bitcast insertion
; CHECK: define void @test20(
-; CHECK: %tmp1 = tail call i8* @objc_retain(i8* %tmp) nounwind
+; CHECK: %tmp1 = tail call i8* @objc_retain(i8* %tmp) [[NUW]]
; CHECK-NEXT: invoke
define void @test20(double* %self) {
if.then12:
lpad20: ; preds = %invoke.cont23, %if.then12
%tmp502 = phi double* [ undef, %invoke.cont23 ], [ %self, %if.then12 ]
+ %exn = landingpad {i8*, i32} personality i32 (...)* @__gxx_personality_v0
+ cleanup
unreachable
if.end: ; preds = %invoke.cont23
@__block_holder_tmp_1 = external constant %block1
define void @test23() {
entry:
- %0 = call i8* @objc_retainBlock(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind
+ %0 = call i8* @objc_retainBlock(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind, !clang.arc.copy_on_escape !0
call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*))
call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*))
call void @objc_release(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind
; CHECK: }
define void @test23b(i8* %p) {
entry:
- %0 = call i8* @objc_retainBlock(i8* %p) nounwind
- call void @use_pointer(i8* %p)
+ %0 = call i8* @objc_retainBlock(i8* %p) nounwind, !clang.arc.copy_on_escape !0
+ call void @callee()
call void @use_pointer(i8* %p)
call void @objc_release(i8* %p) nounwind
ret void
}
+; Don't optimize objc_retainBlock, because there's no copy_on_escape metadata.
+
+; CHECK: define void @test23c(
+; CHECK: @objc_retainBlock
+; CHECK: @objc_release
+; CHECK: }
+define void @test23c() {
+entry:
+ %0 = call i8* @objc_retainBlock(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind
+ call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*))
+ call void @bar(i32 ()* bitcast (%block1* @__block_holder_tmp_1 to i32 ()*))
+ call void @objc_release(i8* bitcast (%block1* @__block_holder_tmp_1 to i8*)) nounwind
+ ret void
+}
+
; Any call can decrement a retain count.
; CHECK: define void @test24(
; CHECK: call i8* @objc_retain(
; CHECK: call void @callee()
; CHECK: store
-; CHECK: call void @objc_release(i8* %p) nounwind, !clang.imprecise_release
+; CHECK: call void @objc_release(i8* %p) [[NUW]], !clang.imprecise_release
; CHECK: done:
; CHECK-NOT: @objc_
; CHECK: }
; Delete retain,release pairs around loops.
; CHECK: define void @test39(
-; CHECK_NOT: @objc_
+; CHECK-NOT: @objc_
; CHECK: }
define void @test39(i8* %p) {
entry:
; Delete retain,release pairs around loops containing uses.
; CHECK: define void @test39b(
-; CHECK_NOT: @objc_
+; CHECK-NOT: @objc_
; CHECK: }
define void @test39b(i8* %p) {
entry:
; Delete retain,release pairs around loops containing potential decrements.
; CHECK: define void @test39c(
-; CHECK_NOT: @objc_
+; CHECK-NOT: @objc_
; CHECK: }
define void @test39c(i8* %p) {
entry:
; the successors are in a different order.
; CHECK: define void @test40(
-; CHECK_NOT: @objc_
+; CHECK-NOT: @objc_
; CHECK: }
define void @test40(i8* %p) {
entry:
; CHECK-NEXT: call i8* @objc_autorelease(i8* %p)
; CHECK-NEXT: call void @use_pointer(i8* %p)
; CHECK-NEXT: call void @use_pointer(i8* %p)
-; CHECK-NEXT: call void @objc_autoreleasePoolPush()
+; CHECK-NEXT: call i8* @objc_autoreleasePoolPush()
; CHECK-NEXT: ret void
; CHECK-NEXT: }
define void @test43b(i8* %p) {
call i8* @objc_retain(i8* %p)
call void @use_pointer(i8* %p)
call void @use_pointer(i8* %p)
- call void @objc_autoreleasePoolPush()
+ call i8* @objc_autoreleasePoolPush()
call void @objc_release(i8* %p)
ret void
}
; Don't delete retain and autorelease here.
; CHECK: define void @test46(
-; CHECK: tail call i8* @objc_retain(i8* %p) nounwind
+; CHECK: tail call i8* @objc_retain(i8* %p) [[NUW]]
; CHECK: true:
-; CHECK: tail call i8* @objc_autorelease(i8* %p) nounwind
+; CHECK: call i8* @objc_autorelease(i8* %p) [[NUW]]
define void @test46(i8* %p, i1 %a) {
entry:
call i8* @objc_retain(i8* %p)
}
; Do delete retain+release with intervening stores of the
-; address value;
+; address value.
; CHECK: define void @test50(
; CHECK-NOT: @objc_
; 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.
+; See rdar://10551239.
; CHECK: define void @test53(
-; CHECK-NOT: @objc_
+; CHECK: @objc_
; CHECK: }
define void @test53(void ()** %zz, i8** %pp) {
%p = load i8** %pp
; CHECK: define void @test54(
; CHECK: call i8* @returner()
-; CHECK-NEXT: call void @objc_release(i8* %t) nounwind, !clang.imprecise_release !0
+; CHECK-NEXT: call void @objc_release(i8* %t) [[NUW]], !clang.imprecise_release !0
; CHECK-NEXT: ret void
define void @test54() {
%t = call i8* @returner()
; CHECK: define void @test56(
; CHECK-NOT: @objc
; CHECK: if.then:
-; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) nounwind
+; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) [[NUW]]
; CHECK-NEXT: tail call void @use_pointer(i8* %x)
; CHECK-NEXT: tail call void @use_pointer(i8* %x)
-; CHECK-NEXT: tail call void @objc_release(i8* %x) nounwind, !clang.imprecise_release !0
+; CHECK-NEXT: tail call void @objc_release(i8* %x) [[NUW]], !clang.imprecise_release !0
; CHECK-NEXT: br label %if.end
; CHECK-NOT: @objc
; CHECK: }
; CHECK-NEXT: entry:
; CHECK-NEXT: call void @use_pointer(i8* %x)
; CHECK-NEXT: call void @use_pointer(i8* %x)
-; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) nounwind
+; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) [[NUW]]
; CHECK-NEXT: call void @use_pointer(i8* %x)
; CHECK-NEXT: call void @use_pointer(i8* %x)
-; CHECK-NEXT: call void @objc_release(i8* %x) nounwind
+; CHECK-NEXT: call void @objc_release(i8* %x) [[NUW]]
; CHECK-NEXT: ret void
; CHECK-NEXT: }
define void @test57(i8* %x) nounwind {
; CHECK: define void @test59(
; CHECK-NEXT: entry:
-; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) nounwind
+; CHECK-NEXT: %0 = tail call i8* @objc_retain(i8* %x) [[NUW]]
; CHECK-NEXT: call void @use_pointer(i8* %x)
; CHECK-NEXT: call void @use_pointer(i8* %x)
-; CHECK-NEXT: call void @objc_release(i8* %x) nounwind
+; CHECK-NEXT: call void @objc_release(i8* %x) [[NUW]]
; CHECK-NEXT: ret void
; CHECK-NEXT: }
define void @test59(i8* %x) nounwind {
ret void
}
+; Constant pointers to objects don't need reference counting.
+
+@constptr = external constant i8*
+@something = external global i8*
+
+; CHECK: define void @test60(
+; CHECK-NOT: @objc_
+; CHECK: }
+define void @test60() {
+ %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
+}
+
+; Constant pointers to objects don't need to be considered related to other
+; pointers.
+
+; CHECK: define void @test61(
+; CHECK-NOT: @objc_
+; CHECK: }
+define void @test61() {
+ %t = load i8** @constptr
+ call i8* @objc_retain(i8* %t)
+ call void @callee()
+ call void @use_pointer(i8* %t)
+ call void @objc_release(i8* %t)
+ ret void
+}
+
+; Delete a retain matched by releases when one is inside the loop and the
+; other is outside the loop.
+
+; CHECK: define void @test62(
+; CHECK-NOT: @objc_
+; CHECK: }
+define void @test62(i8* %x, i1* %p) nounwind {
+entry:
+ br label %loop
+
+loop:
+ call i8* @objc_retain(i8* %x)
+ %q = load i1* %p
+ br i1 %q, label %loop.more, label %exit
+
+loop.more:
+ call void @objc_release(i8* %x)
+ br label %loop
+
+exit:
+ call void @objc_release(i8* %x)
+ ret void
+}
+
+; Like test62 but with no release in exit.
+; Don't delete anything!
+
+; CHECK: define void @test63(
+; CHECK: loop:
+; CHECK: tail call i8* @objc_retain(i8* %x)
+; CHECK: loop.more:
+; CHECK: call void @objc_release(i8* %x)
+; CHECK: }
+define void @test63(i8* %x, i1* %p) nounwind {
+entry:
+ br label %loop
+
+loop:
+ call i8* @objc_retain(i8* %x)
+ %q = load i1* %p
+ br i1 %q, label %loop.more, label %exit
+
+loop.more:
+ call void @objc_release(i8* %x)
+ br label %loop
+
+exit:
+ ret void
+}
+
+; Like test62 but with no release in loop.more.
+; Don't delete anything!
+
+; CHECK: define void @test64(
+; CHECK: loop:
+; CHECK: tail call i8* @objc_retain(i8* %x)
+; CHECK: exit:
+; CHECK: call void @objc_release(i8* %x)
+; CHECK: }
+define void @test64(i8* %x, i1* %p) nounwind {
+entry:
+ br label %loop
+
+loop:
+ call i8* @objc_retain(i8* %x)
+ %q = load i1* %p
+ br i1 %q, label %loop.more, label %exit
+
+loop.more:
+ br label %loop
+
+exit:
+ call void @objc_release(i8* %x)
+ ret void
+}
+
+; Move an autorelease past a phi with a null.
+
+; CHECK: define i8* @test65(
+; CHECK: if.then:
+; CHECK: call i8* @objc_autorelease(
+; CHECK: return:
+; CHECK-NOT: @objc_autorelease
+; CHECK: }
+define i8* @test65(i1 %x) {
+entry:
+ br i1 %x, label %return, label %if.then
+
+if.then: ; preds = %entry
+ %c = call i8* @returner()
+ %s = call i8* @objc_retainAutoreleasedReturnValue(i8* %c) nounwind
+ br label %return
+
+return: ; preds = %if.then, %entry
+ %retval = phi i8* [ %s, %if.then ], [ null, %entry ]
+ %q = call i8* @objc_autorelease(i8* %retval) nounwind
+ ret i8* %retval
+}
+
+; Don't move an autorelease past an autorelease pool boundary.
+
+; CHECK: define i8* @test65b(
+; CHECK: if.then:
+; CHECK-NOT: @objc_autorelease
+; CHECK: return:
+; CHECK: call i8* @objc_autorelease(
+; CHECK: }
+define i8* @test65b(i1 %x) {
+entry:
+ %t = call i8* @objc_autoreleasePoolPush()
+ br i1 %x, label %return, label %if.then
+
+if.then: ; preds = %entry
+ %c = call i8* @returner()
+ %s = call i8* @objc_retainAutoreleasedReturnValue(i8* %c) nounwind
+ br label %return
+
+return: ; preds = %if.then, %entry
+ %retval = phi i8* [ %s, %if.then ], [ null, %entry ]
+ call void @objc_autoreleasePoolPop(i8* %t)
+ %q = call i8* @objc_autorelease(i8* %retval) nounwind
+ ret i8* %retval
+}
+
+; Don't move an autoreleaseReuturnValue, which would break
+; the RV optimization.
+
+; CHECK: define i8* @test65c(
+; CHECK: if.then:
+; CHECK-NOT: @objc_autorelease
+; CHECK: return:
+; CHECK: call i8* @objc_autoreleaseReturnValue(
+; CHECK: }
+define i8* @test65c(i1 %x) {
+entry:
+ br i1 %x, label %return, label %if.then
+
+if.then: ; preds = %entry
+ %c = call i8* @returner()
+ %s = call i8* @objc_retainAutoreleasedReturnValue(i8* %c) nounwind
+ br label %return
+
+return: ; preds = %if.then, %entry
+ %retval = phi i8* [ %s, %if.then ], [ null, %entry ]
+ %q = call i8* @objc_autoreleaseReturnValue(i8* %retval) nounwind
+ 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.
}
!0 = metadata !{}
+
+declare i32 @__gxx_personality_v0(...)
+
+; CHECK: attributes #0 = { nounwind readnone }
+; CHECK: attributes [[NUW]] = { nounwind }