diff options
| author | Eric Christopher <echristo@gmail.com> | 2019-04-17 04:52:47 +0000 |
|---|---|---|
| committer | Eric Christopher <echristo@gmail.com> | 2019-04-17 04:52:47 +0000 |
| commit | cee313d288a4faf0355d76fb6e0e927e211d08a5 (patch) | |
| tree | d386075318d761197779a96e5d8fc0dc7b06342b /llvm/test/Transforms/FunctionAttrs | |
| parent | c3d6a929fdd92fd06d4304675ade8d7210ee711a (diff) | |
| download | bcm5719-llvm-cee313d288a4faf0355d76fb6e0e927e211d08a5.tar.gz bcm5719-llvm-cee313d288a4faf0355d76fb6e0e927e211d08a5.zip | |
Revert "Temporarily Revert "Add basic loop fusion pass.""
The reversion apparently deleted the test/Transforms directory.
Will be re-reverting again.
llvm-svn: 358552
Diffstat (limited to 'llvm/test/Transforms/FunctionAttrs')
25 files changed, 1335 insertions, 0 deletions
diff --git a/llvm/test/Transforms/FunctionAttrs/2008-09-03-Mutual.ll b/llvm/test/Transforms/FunctionAttrs/2008-09-03-Mutual.ll new file mode 100644 index 00000000000..6bbd99951ad --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2008-09-03-Mutual.ll @@ -0,0 +1,18 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: define i32 @a +define i32 @a() { + %tmp = call i32 @b( ) ; <i32> [#uses=1] + ret i32 %tmp +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: define i32 @b +define i32 @b() { + %tmp = call i32 @a( ) ; <i32> [#uses=1] + ret i32 %tmp +} diff --git a/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadNone.ll b/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadNone.ll new file mode 100644 index 00000000000..d747fe727fa --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadNone.ll @@ -0,0 +1,32 @@ +; RUN: opt < %s -basicaa -functionattrs -S | FileCheck %s +; RUN: opt < %s -aa-pipeline=basic-aa -passes=function-attrs -S | FileCheck %s + +@x = global i32 0 + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: declare i32 @e +declare i32 @e() readnone + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: define i32 @f +define i32 @f() { + %tmp = call i32 @e( ) ; <i32> [#uses=1] + ret i32 %tmp +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: define i32 @g +define i32 @g() readonly { + ret i32 0 +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NEXT: define i32 @h +define i32 @h() readnone { + %tmp = load i32, i32* @x ; <i32> [#uses=1] + ret i32 %tmp +} diff --git a/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadOnly.ll b/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadOnly.ll new file mode 100644 index 00000000000..35cb5342d8d --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2008-09-03-ReadOnly.ll @@ -0,0 +1,14 @@ +; RUN: opt < %s -basicaa -functionattrs -S | FileCheck %s +; RUN: opt < %s -aa-pipeline=basic-aa -passes=function-attrs -S | FileCheck %s + +; CHECK: define i32 @f() #0 +define i32 @f() { +entry: + %tmp = call i32 @e( ) + ret i32 %tmp +} + +; CHECK: declare i32 @e() #0 +declare i32 @e() readonly + +; CHECK: attributes #0 = { readonly } diff --git a/llvm/test/Transforms/FunctionAttrs/2008-09-13-VolatileRead.ll b/llvm/test/Transforms/FunctionAttrs/2008-09-13-VolatileRead.ll new file mode 100644 index 00000000000..8212e8945ec --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2008-09-13-VolatileRead.ll @@ -0,0 +1,12 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; PR2792 + +@g = global i32 0 ; <i32*> [#uses=1] + +define i32 @f() { + %t = load volatile i32, i32* @g ; <i32> [#uses=1] + ret i32 %t +} + +; CHECK-NOT: attributes #{{.*}} read diff --git a/llvm/test/Transforms/FunctionAttrs/2008-12-29-Constant.ll b/llvm/test/Transforms/FunctionAttrs/2008-12-29-Constant.ll new file mode 100644 index 00000000000..ee1a8caa20e --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2008-12-29-Constant.ll @@ -0,0 +1,12 @@ +; RUN: opt < %s -basicaa -functionattrs -S | FileCheck %s +; RUN: opt < %s -aa-pipeline=basic-aa -passes=function-attrs -S | FileCheck %s + +@s = external constant i8 ; <i8*> [#uses=1] + +; CHECK: define i8 @f() #0 +define i8 @f() { + %tmp = load i8, i8* @s ; <i8> [#uses=1] + ret i8 %tmp +} + +; CHECK: attributes #0 = { {{.*}} readnone diff --git a/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll b/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll new file mode 100644 index 00000000000..ce72c416563 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll @@ -0,0 +1,24 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +; CHECK: define i32* @a(i32** nocapture readonly %p) +define i32* @a(i32** %p) { + %tmp = load i32*, i32** %p + ret i32* %tmp +} + +; CHECK: define i32* @b(i32* %q) +define i32* @b(i32 *%q) { + %mem = alloca i32* + store i32* %q, i32** %mem + %tmp = call i32* @a(i32** %mem) + ret i32* %tmp +} + +; CHECK: define i32* @c(i32* readnone returned %r) +@g = global i32 0 +define i32* @c(i32 *%r) { + %a = icmp eq i32* %r, null + store i32 1, i32* @g + ret i32* %r +} diff --git a/llvm/test/Transforms/FunctionAttrs/2010-10-30-volatile.ll b/llvm/test/Transforms/FunctionAttrs/2010-10-30-volatile.ll new file mode 100644 index 00000000000..b9536dce8a4 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/2010-10-30-volatile.ll @@ -0,0 +1,14 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; PR8279 + +@g = constant i32 1 + +; CHECK: Function Attrs +; CHECK-SAME: norecurse +; CHECK-NOT: readonly +; CHECK-NEXT: void @foo() +define void @foo() { + %tmp = load volatile i32, i32* @g + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/assume.ll b/llvm/test/Transforms/FunctionAttrs/assume.ll new file mode 100644 index 00000000000..d6296624a2d --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/assume.ll @@ -0,0 +1,5 @@ +; RUN: opt -S -o - -functionattrs %s | FileCheck %s +; RUN: opt -S -o - -passes=function-attrs %s | FileCheck %s + +; CHECK-NOT: readnone +declare void @llvm.assume(i1) diff --git a/llvm/test/Transforms/FunctionAttrs/atomic.ll b/llvm/test/Transforms/FunctionAttrs/atomic.ll new file mode 100644 index 00000000000..af87a28770e --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/atomic.ll @@ -0,0 +1,24 @@ +; RUN: opt -basicaa -functionattrs -S < %s | FileCheck %s +; RUN: opt -aa-pipeline=basic-aa -passes=function-attrs -S < %s | FileCheck %s + +; Atomic load/store to local doesn't affect whether a function is +; readnone/readonly. +define i32 @test1(i32 %x) uwtable ssp { +; CHECK: define i32 @test1(i32 %x) #0 { +entry: + %x.addr = alloca i32, align 4 + store atomic i32 %x, i32* %x.addr seq_cst, align 4 + %r = load atomic i32, i32* %x.addr seq_cst, align 4 + ret i32 %r +} + +; A function with an Acquire load is not readonly. +define i32 @test2(i32* %x) uwtable ssp { +; CHECK: define i32 @test2(i32* nocapture readonly %x) #1 { +entry: + %r = load atomic i32, i32* %x seq_cst, align 4 + ret i32 %r +} + +; CHECK: attributes #0 = { norecurse nounwind readnone ssp uwtable } +; CHECK: attributes #1 = { norecurse nounwind ssp uwtable } diff --git a/llvm/test/Transforms/FunctionAttrs/comdat-ipo.ll b/llvm/test/Transforms/FunctionAttrs/comdat-ipo.ll new file mode 100644 index 00000000000..2a149e436b6 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/comdat-ipo.ll @@ -0,0 +1,17 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +; See PR26774 + +; CHECK-LABEL: define void @bar(i8* readonly) { +define void @bar(i8* readonly) { + call void @foo(i8* %0) + ret void +} + + +; CHECK-LABEL: define linkonce_odr void @foo(i8* readonly) { +define linkonce_odr void @foo(i8* readonly) { + call void @bar(i8* %0) + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/convergent.ll b/llvm/test/Transforms/FunctionAttrs/convergent.ll new file mode 100644 index 00000000000..0e4b7515d01 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/convergent.ll @@ -0,0 +1,110 @@ +; FIXME: convert CHECK-INDIRECT into CHECK (and remove -check-prefixes) as soon +; FIXME: as new-pass-manager's handling of indirect_non_convergent_call is fixed +; +; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefixes=CHECK,CHECK-INDIRECT +; RUN: opt -passes=function-attrs -S < %s | FileCheck %s + +; CHECK: Function Attrs +; CHECK-NOT: convergent +; CHECK-NEXT: define i32 @nonleaf() +define i32 @nonleaf() convergent { + %a = call i32 @leaf() + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-NOT: convergent +; CHECK-NEXT: define i32 @leaf() +define i32 @leaf() convergent { + ret i32 0 +} + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: declare i32 @k() +declare i32 @k() convergent + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @extern() +define i32 @extern() convergent { + %a = call i32 @k() convergent + ret i32 %a +} + +; Convergent should not be removed on the function here. Although the call is +; not explicitly convergent, it picks up the convergent attr from the callee. +; +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @extern_non_convergent_call() +define i32 @extern_non_convergent_call() convergent { + %a = call i32 @k() + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @indirect_convergent_call( +define i32 @indirect_convergent_call(i32 ()* %f) convergent { + %a = call i32 %f() convergent + ret i32 %a +} +; Give indirect_non_convergent_call the norecurse attribute so we get a +; "Function Attrs" comment in the output. +; +; CHECK: Function Attrs +; CHECK-INDIRECT-NOT: convergent +; CHECK-INDIRECT-NEXT: define i32 @indirect_non_convergent_call( +define i32 @indirect_non_convergent_call(i32 ()* %f) convergent norecurse { + %a = call i32 %f() + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: declare void @llvm.nvvm.barrier0() +declare void @llvm.nvvm.barrier0() convergent + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @intrinsic() +define i32 @intrinsic() convergent { + ; Implicitly convergent, because the intrinsic is convergent. + call void @llvm.nvvm.barrier0() + ret i32 0 +} + +; CHECK: Function Attrs +; CHECK-NOT: convergent +; CHECK-NEXT: define i32 @recursive1() +define i32 @recursive1() convergent { + %a = call i32 @recursive2() convergent + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-NOT: convergent +; CHECK-NEXT: define i32 @recursive2() +define i32 @recursive2() convergent { + %a = call i32 @recursive1() convergent + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @noopt() +define i32 @noopt() convergent optnone noinline { + %a = call i32 @noopt_friend() convergent + ret i32 0 +} + +; A function which is mutually-recursive with a convergent, optnone function +; shouldn't have its convergent attribute stripped. +; CHECK: Function Attrs +; CHECK-SAME: convergent +; CHECK-NEXT: define i32 @noopt_friend() +define i32 @noopt_friend() convergent { + %a = call i32 @noopt() + ret i32 0 +} diff --git a/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll b/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll new file mode 100644 index 00000000000..79af817ff03 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll @@ -0,0 +1,32 @@ +; RUN: opt -S -o - -functionattrs %s | FileCheck %s +; RUN: opt -S -o - -passes=function-attrs %s | FileCheck %s + +; Verify we remove argmemonly/inaccessiblememonly/inaccessiblemem_or_argmemonly +; function attributes when we derive readnone. + +; Function Attrs: argmemonly +define i32* @given_argmem_infer_readnone(i32* %p) #0 { +; CHECK: define i32* @given_argmem_infer_readnone(i32* readnone returned %p) #0 { +entry: + ret i32* %p +} + +; Function Attrs: inaccessiblememonly +define i32* @given_inaccessible_infer_readnone(i32* %p) #1 { +; CHECK: define i32* @given_inaccessible_infer_readnone(i32* readnone returned %p) #0 { +entry: + ret i32* %p +} + +; Function Attrs: inaccessiblemem_or_argmemonly +define i32* @given_inaccessible_or_argmem_infer_readnone(i32* %p) #2 { +; CHECK: define i32* @given_inaccessible_or_argmem_infer_readnone(i32* readnone returned %p) #0 { +entry: + ret i32* %p +} + +attributes #0 = { argmemonly } +attributes #1 = { inaccessiblememonly } +attributes #2 = { inaccessiblemem_or_argmemonly } +; CHECK: attributes #0 = { norecurse nounwind readnone } +; CHECK-NOT: attributes diff --git a/llvm/test/Transforms/FunctionAttrs/int_sideeffect.ll b/llvm/test/Transforms/FunctionAttrs/int_sideeffect.ll new file mode 100644 index 00000000000..24a145908e0 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/int_sideeffect.ll @@ -0,0 +1,29 @@ +; RUN: opt -S < %s -functionattrs | FileCheck %s +; RUN: opt -S < %s -passes=function-attrs | FileCheck %s + +; CHECK: Function Attrs +; CHECK-SAME: inaccessiblememonly +; CHECK-NEXT: declare void @llvm.sideeffect() +declare void @llvm.sideeffect() + +; Don't add readnone or similar attributes when an @llvm.sideeffect() intrinsic +; is present. + +; CHECK: Function Attrs +; CHECK-NOT: readnone +; CHECK: define void @test() +define void @test() { + call void @llvm.sideeffect() + ret void +} + +; CHECK: Function Attrs +; CHECK-NOT: readnone +; CHECK: define void @loop() +define void @loop() { + br label %loop + +loop: + call void @llvm.sideeffect() + br label %loop +} diff --git a/llvm/test/Transforms/FunctionAttrs/naked_functions.ll b/llvm/test/Transforms/FunctionAttrs/naked_functions.ll new file mode 100644 index 00000000000..d34dc0c20d9 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/naked_functions.ll @@ -0,0 +1,25 @@ +; RUN: opt -S -functionattrs %s | FileCheck %s +; RUN: opt -S -passes='function-attrs' %s | FileCheck %s + +; Don't change the attributes of parameters of naked functions, in particular +; don't mark them as readnone + +@g = common global i32 0, align 4 + +define i32 @bar() { +entry: + %call = call i32 @foo(i32* @g) +; CHECK: %call = call i32 @foo(i32* @g) + ret i32 %call +} + +define internal i32 @foo(i32*) #0 { +entry: + %retval = alloca i32, align 4 + call void asm sideeffect "ldr r0, [r0] \0Abx lr \0A", ""() + unreachable +} + +; CHECK: define internal i32 @foo(i32*) + +attributes #0 = { naked } diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll new file mode 100644 index 00000000000..0c3fc0af725 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll @@ -0,0 +1,257 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +@g = global i32* null ; <i32**> [#uses=1] + +; CHECK: define i32* @c1(i32* readnone returned %q) +define i32* @c1(i32* %q) { + ret i32* %q +} + +; CHECK: define void @c2(i32* %q) +; It would also be acceptable to mark %q as readnone. Update @c3 too. +define void @c2(i32* %q) { + store i32* %q, i32** @g + ret void +} + +; CHECK: define void @c3(i32* %q) +define void @c3(i32* %q) { + call void @c2(i32* %q) + ret void +} + +; CHECK: define i1 @c4(i32* %q, i32 %bitno) +define i1 @c4(i32* %q, i32 %bitno) { + %tmp = ptrtoint i32* %q to i32 + %tmp2 = lshr i32 %tmp, %bitno + %bit = trunc i32 %tmp2 to i1 + br i1 %bit, label %l1, label %l0 +l0: + ret i1 0 ; escaping value not caught by def-use chaining. +l1: + ret i1 1 ; escaping value not caught by def-use chaining. +} + +@lookup_table = global [2 x i1] [ i1 0, i1 1 ] + +; CHECK: define i1 @c5(i32* %q, i32 %bitno) +define i1 @c5(i32* %q, i32 %bitno) { + %tmp = ptrtoint i32* %q to i32 + %tmp2 = lshr i32 %tmp, %bitno + %bit = and i32 %tmp2, 1 + ; subtle escape mechanism follows + %lookup = getelementptr [2 x i1], [2 x i1]* @lookup_table, i32 0, i32 %bit + %val = load i1, i1* %lookup + ret i1 %val +} + +declare void @throw_if_bit_set(i8*, i8) readonly + +; CHECK: define i1 @c6(i8* readonly %q, i8 %bit) +define i1 @c6(i8* %q, i8 %bit) personality i32 (...)* @__gxx_personality_v0 { + invoke void @throw_if_bit_set(i8* %q, i8 %bit) + to label %ret0 unwind label %ret1 +ret0: + ret i1 0 +ret1: + %exn = landingpad {i8*, i32} + cleanup + ret i1 1 +} + +declare i32 @__gxx_personality_v0(...) + +define i1* @lookup_bit(i32* %q, i32 %bitno) readnone nounwind { + %tmp = ptrtoint i32* %q to i32 + %tmp2 = lshr i32 %tmp, %bitno + %bit = and i32 %tmp2, 1 + %lookup = getelementptr [2 x i1], [2 x i1]* @lookup_table, i32 0, i32 %bit + ret i1* %lookup +} + +; CHECK: define i1 @c7(i32* readonly %q, i32 %bitno) +define i1 @c7(i32* %q, i32 %bitno) { + %ptr = call i1* @lookup_bit(i32* %q, i32 %bitno) + %val = load i1, i1* %ptr + ret i1 %val +} + + +; CHECK: define i32 @nc1(i32* %q, i32* nocapture %p, i1 %b) +define i32 @nc1(i32* %q, i32* %p, i1 %b) { +e: + br label %l +l: + %x = phi i32* [ %p, %e ] + %y = phi i32* [ %q, %e ] + %tmp = bitcast i32* %x to i32* ; <i32*> [#uses=2] + %tmp2 = select i1 %b, i32* %tmp, i32* %y + %val = load i32, i32* %tmp2 ; <i32> [#uses=1] + store i32 0, i32* %tmp + store i32* %y, i32** @g + ret i32 %val +} + +; CHECK: define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* nocapture %p, i1 %b) +define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* %p, i1 %b) { +e: + br label %l +l: + %x = phi i32 addrspace(1)* [ %p, %e ] + %y = phi i32* [ %q, %e ] + %tmp = addrspacecast i32 addrspace(1)* %x to i32* ; <i32*> [#uses=2] + %tmp2 = select i1 %b, i32* %tmp, i32* %y + %val = load i32, i32* %tmp2 ; <i32> [#uses=1] + store i32 0, i32* %tmp + store i32* %y, i32** @g + ret i32 %val +} + +; CHECK: define void @nc2(i32* nocapture %p, i32* %q) +define void @nc2(i32* %p, i32* %q) { + %1 = call i32 @nc1(i32* %q, i32* %p, i1 0) ; <i32> [#uses=0] + ret void +} + +; CHECK: define void @nc3(void ()* nocapture %p) +define void @nc3(void ()* %p) { + call void %p() + ret void +} + +declare void @external(i8*) readonly nounwind +; CHECK: define void @nc4(i8* nocapture readonly %p) +define void @nc4(i8* %p) { + call void @external(i8* %p) + ret void +} + +; CHECK: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p) +define void @nc5(void (i8*)* %f, i8* %p) { + call void %f(i8* %p) readonly nounwind + call void %f(i8* nocapture %p) + ret void +} + +; CHECK: define void @test1_1(i8* nocapture readnone %x1_1, i8* %y1_1) +; It would be acceptable to add readnone to %y1_1 and %y1_2. +define void @test1_1(i8* %x1_1, i8* %y1_1) { + call i8* @test1_2(i8* %x1_1, i8* %y1_1) + store i32* null, i32** @g + ret void +} + +; CHECK: define i8* @test1_2(i8* nocapture readnone %x1_2, i8* returned %y1_2) +define i8* @test1_2(i8* %x1_2, i8* %y1_2) { + call void @test1_1(i8* %x1_2, i8* %y1_2) + store i32* null, i32** @g + ret i8* %y1_2 +} + +; CHECK: define void @test2(i8* nocapture readnone %x2) +define void @test2(i8* %x2) { + call void @test2(i8* %x2) + store i32* null, i32** @g + ret void +} + +; CHECK: define void @test3(i8* nocapture readnone %x3, i8* nocapture readnone %y3, i8* nocapture readnone %z3) +define void @test3(i8* %x3, i8* %y3, i8* %z3) { + call void @test3(i8* %z3, i8* %y3, i8* %x3) + store i32* null, i32** @g + ret void +} + +; CHECK: define void @test4_1(i8* %x4_1) +define void @test4_1(i8* %x4_1) { + call i8* @test4_2(i8* %x4_1, i8* %x4_1, i8* %x4_1) + store i32* null, i32** @g + ret void +} + +; CHECK: define i8* @test4_2(i8* nocapture readnone %x4_2, i8* readnone returned %y4_2, i8* nocapture readnone %z4_2) +define i8* @test4_2(i8* %x4_2, i8* %y4_2, i8* %z4_2) { + call void @test4_1(i8* null) + store i32* null, i32** @g + ret i8* %y4_2 +} + +declare i8* @test5_1(i8* %x5_1) + +; CHECK: define void @test5_2(i8* %x5_2) +define void @test5_2(i8* %x5_2) { + call i8* @test5_1(i8* %x5_2) + store i32* null, i32** @g + ret void +} + +declare void @test6_1(i8* %x6_1, i8* nocapture %y6_1, ...) + +; CHECK: define void @test6_2(i8* %x6_2, i8* nocapture %y6_2, i8* %z6_2) +define void @test6_2(i8* %x6_2, i8* %y6_2, i8* %z6_2) { + call void (i8*, i8*, ...) @test6_1(i8* %x6_2, i8* %y6_2, i8* %z6_2) + store i32* null, i32** @g + ret void +} + +; CHECK: define void @test_cmpxchg(i32* nocapture %p) +define void @test_cmpxchg(i32* %p) { + cmpxchg i32* %p, i32 0, i32 1 acquire monotonic + ret void +} + +; CHECK: define void @test_cmpxchg_ptr(i32** nocapture %p, i32* %q) +define void @test_cmpxchg_ptr(i32** %p, i32* %q) { + cmpxchg i32** %p, i32* null, i32* %q acquire monotonic + ret void +} + +; CHECK: define void @test_atomicrmw(i32* nocapture %p) +define void @test_atomicrmw(i32* %p) { + atomicrmw add i32* %p, i32 1 seq_cst + ret void +} + +; CHECK: define void @test_volatile(i32* %x) +define void @test_volatile(i32* %x) { +entry: + %gep = getelementptr i32, i32* %x, i64 1 + store volatile i32 0, i32* %gep, align 4 + ret void +} + +; CHECK: nocaptureLaunder(i8* nocapture %p) +define void @nocaptureLaunder(i8* %p) { +entry: + %b = call i8* @llvm.launder.invariant.group.p0i8(i8* %p) + store i8 42, i8* %b + ret void +} + +@g2 = global i8* null +; CHECK: define void @captureLaunder(i8* %p) +define void @captureLaunder(i8* %p) { + %b = call i8* @llvm.launder.invariant.group.p0i8(i8* %p) + store i8* %b, i8** @g2 + ret void +} + +; CHECK: @nocaptureStrip(i8* nocapture %p) +define void @nocaptureStrip(i8* %p) { +entry: + %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) + store i8 42, i8* %b + ret void +} + +@g3 = global i8* null +; CHECK: define void @captureStrip(i8* %p) +define void @captureStrip(i8* %p) { + %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) + store i8* %b, i8** @g3 + ret void +} + +declare i8* @llvm.launder.invariant.group.p0i8(i8*) +declare i8* @llvm.strip.invariant.group.p0i8(i8*) diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull-global.ll b/llvm/test/Transforms/FunctionAttrs/nonnull-global.ll new file mode 100644 index 00000000000..d79a7ae290a --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/nonnull-global.ll @@ -0,0 +1,11 @@ +; RUN: opt -S -functionattrs %s | FileCheck %s +; RUN: opt -S -passes=function-attrs %s | FileCheck %s + +@a = external global i8, !absolute_symbol !0 + +; CHECK-NOT: define nonnull +define i8* @foo() { + ret i8* @a +} + +!0 = !{i64 0, i64 256} diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll new file mode 100644 index 00000000000..7029be9691d --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -0,0 +1,240 @@ +; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s +; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s + +declare nonnull i8* @ret_nonnull() + +; Return a pointer trivially nonnull (call return attribute) +define i8* @test1() { +; CHECK: define nonnull i8* @test1 + %ret = call i8* @ret_nonnull() + ret i8* %ret +} + +; Return a pointer trivially nonnull (argument attribute) +define i8* @test2(i8* nonnull %p) { +; CHECK: define nonnull i8* @test2 + ret i8* %p +} + +; Given an SCC where one of the functions can not be marked nonnull, +; can we still mark the other one which is trivially nonnull +define i8* @scc_binder() { +; CHECK: define i8* @scc_binder + call i8* @test3() + ret i8* null +} + +define i8* @test3() { +; CHECK: define nonnull i8* @test3 + call i8* @scc_binder() + %ret = call i8* @ret_nonnull() + ret i8* %ret +} + +; Given a mutual recursive set of functions, we can mark them +; nonnull if neither can ever return null. (In this case, they +; just never return period.) +define i8* @test4_helper() { +; CHECK: define noalias nonnull i8* @test4_helper + %ret = call i8* @test4() + ret i8* %ret +} + +define i8* @test4() { +; CHECK: define noalias nonnull i8* @test4 + %ret = call i8* @test4_helper() + ret i8* %ret +} + +; Given a mutual recursive set of functions which *can* return null +; make sure we haven't marked them as nonnull. +define i8* @test5_helper() { +; CHECK: define noalias i8* @test5_helper + %ret = call i8* @test5() + ret i8* null +} + +define i8* @test5() { +; CHECK: define noalias i8* @test5 + %ret = call i8* @test5_helper() + ret i8* %ret +} + +; Local analysis, but going through a self recursive phi +define i8* @test6() { +entry: +; CHECK: define nonnull i8* @test6 + %ret = call i8* @ret_nonnull() + br label %loop +loop: + %phi = phi i8* [%ret, %entry], [%phi, %loop] + br i1 undef, label %loop, label %exit +exit: + ret i8* %phi +} + +; Test propagation of nonnull callsite args back to caller. + +declare void @use1(i8* %x) +declare void @use2(i8* %x, i8* %y); +declare void @use3(i8* %x, i8* %y, i8* %z); + +declare void @use1nonnull(i8* nonnull %x); +declare void @use2nonnull(i8* nonnull %x, i8* nonnull %y); +declare void @use3nonnull(i8* nonnull %x, i8* nonnull %y, i8* nonnull %z); + +declare i8 @use1safecall(i8* %x) readonly nounwind ; readonly+nounwind guarantees that execution continues to successor + +; Can't extend non-null to parent for any argument because the 2nd call is not guaranteed to execute. + +define void @parent1(i8* %a, i8* %b, i8* %c) { +; CHECK-LABEL: @parent1(i8* %a, i8* %b, i8* %c) +; CHECK-NEXT: call void @use3(i8* %c, i8* %a, i8* %b) +; CHECK-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a) +; CHECK-NEXT: ret void +; + call void @use3(i8* %c, i8* %a, i8* %b) + call void @use3nonnull(i8* %b, i8* %c, i8* %a) + ret void +} + +; Extend non-null to parent for all arguments. + +define void @parent2(i8* %a, i8* %b, i8* %c) { +; CHECK-LABEL: @parent2(i8* nonnull %a, i8* nonnull %b, i8* nonnull %c) +; CHECK-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a) +; CHECK-NEXT: call void @use3(i8* %c, i8* %a, i8* %b) +; CHECK-NEXT: ret void +; + call void @use3nonnull(i8* %b, i8* %c, i8* %a) + call void @use3(i8* %c, i8* %a, i8* %b) + ret void +} + +; Extend non-null to parent for 1st argument. + +define void @parent3(i8* %a, i8* %b, i8* %c) { +; CHECK-LABEL: @parent3(i8* nonnull %a, i8* %b, i8* %c) +; CHECK-NEXT: call void @use1nonnull(i8* %a) +; CHECK-NEXT: call void @use3(i8* %c, i8* %b, i8* %a) +; CHECK-NEXT: ret void +; + call void @use1nonnull(i8* %a) + call void @use3(i8* %c, i8* %b, i8* %a) + ret void +} + +; Extend non-null to parent for last 2 arguments. + +define void @parent4(i8* %a, i8* %b, i8* %c) { +; CHECK-LABEL: @parent4(i8* %a, i8* nonnull %b, i8* nonnull %c) +; CHECK-NEXT: call void @use2nonnull(i8* %c, i8* %b) +; CHECK-NEXT: call void @use2(i8* %a, i8* %c) +; CHECK-NEXT: call void @use1(i8* %b) +; CHECK-NEXT: ret void +; + call void @use2nonnull(i8* %c, i8* %b) + call void @use2(i8* %a, i8* %c) + call void @use1(i8* %b) + ret void +} + +; The callsite must execute in order for the attribute to transfer to the parent. +; It appears benign to extend non-null to the parent in this case, but we can't do that +; because it would incorrectly propagate the wrong information to its callers. + +define void @parent5(i8* %a, i1 %a_is_notnull) { +; CHECK-LABEL: @parent5(i8* %a, i1 %a_is_notnull) +; CHECK-NEXT: br i1 %a_is_notnull, label %t, label %f +; CHECK: t: +; CHECK-NEXT: call void @use1nonnull(i8* %a) +; CHECK-NEXT: ret void +; CHECK: f: +; CHECK-NEXT: ret void +; + br i1 %a_is_notnull, label %t, label %f +t: + call void @use1nonnull(i8* %a) + ret void +f: + ret void +} + +; The callsite must execute in order for the attribute to transfer to the parent. +; The volatile load might trap, so there's no guarantee that we'll ever get to the call. + +define i8 @parent6(i8* %a, i8* %b) { +; CHECK-LABEL: @parent6(i8* %a, i8* %b) +; CHECK-NEXT: [[C:%.*]] = load volatile i8, i8* %b +; CHECK-NEXT: call void @use1nonnull(i8* %a) +; CHECK-NEXT: ret i8 [[C]] +; + %c = load volatile i8, i8* %b + call void @use1nonnull(i8* %a) + ret i8 %c +} + +; The nonnull callsite is guaranteed to execute, so the argument must be nonnull throughout the parent. + +define i8 @parent7(i8* %a) { +; CHECK-LABEL: @parent7(i8* nonnull %a) +; CHECK-NEXT: [[RET:%.*]] = call i8 @use1safecall(i8* %a) +; CHECK-NEXT: call void @use1nonnull(i8* %a) +; CHECK-NEXT: ret i8 [[RET]] +; + %ret = call i8 @use1safecall(i8* %a) + call void @use1nonnull(i8* %a) + ret i8 %ret +} + +; Make sure that an invoke works similarly to a call. + +declare i32 @esfp(...) + +define i1 @parent8(i8* %a, i8* %bogus1, i8* %b) personality i8* bitcast (i32 (...)* @esfp to i8*){ +; CHECK-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b) +; CHECK-NEXT: entry: +; CHECK-NEXT: invoke void @use2nonnull(i8* %a, i8* %b) +; CHECK-NEXT: to label %cont unwind label %exc +; CHECK: cont: +; CHECK-NEXT: [[NULL_CHECK:%.*]] = icmp eq i8* %b, null +; CHECK-NEXT: ret i1 [[NULL_CHECK]] +; CHECK: exc: +; CHECK-NEXT: [[LP:%.*]] = landingpad { i8*, i32 } +; CHECK-NEXT: filter [0 x i8*] zeroinitializer +; CHECK-NEXT: unreachable +; +entry: + invoke void @use2nonnull(i8* %a, i8* %b) + to label %cont unwind label %exc + +cont: + %null_check = icmp eq i8* %b, null + ret i1 %null_check + +exc: + %lp = landingpad { i8*, i32 } + filter [0 x i8*] zeroinitializer + unreachable +} + +; CHECK: define nonnull i32* @gep1( +define i32* @gep1(i32* %p) { + %q = getelementptr inbounds i32, i32* %p, i32 1 + ret i32* %q +} + +define i32* @gep1_no_null_opt(i32* %p) #0 { +; Should't be able to derive nonnull based on gep. +; CHECK: define i32* @gep1_no_null_opt( + %q = getelementptr inbounds i32, i32* %p, i32 1 + ret i32* %q +} + +; CHECK: define i32 addrspace(3)* @gep2( +define i32 addrspace(3)* @gep2(i32 addrspace(3)* %p) { + %q = getelementptr inbounds i32, i32 addrspace(3)* %p, i32 1 + ret i32 addrspace(3)* %q +} + +attributes #0 = { "null-pointer-is-valid"="true" } diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse.ll b/llvm/test/Transforms/FunctionAttrs/norecurse.ll new file mode 100644 index 00000000000..0293938e479 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/norecurse.ll @@ -0,0 +1,91 @@ +; RUN: opt < %s -basicaa -functionattrs -rpo-functionattrs -S | FileCheck %s +; RUN: opt < %s -aa-pipeline=basic-aa -passes='cgscc(function-attrs),rpo-functionattrs' -S | FileCheck %s + +; CHECK: Function Attrs +; CHECK-SAME: norecurse nounwind readnone +; CHECK-NEXT: define i32 @leaf() +define i32 @leaf() { + ret i32 1 +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NOT: norecurse +; CHECK-NEXT: define i32 @self_rec() +define i32 @self_rec() { + %a = call i32 @self_rec() + ret i32 4 +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NOT: norecurse +; CHECK-NEXT: define i32 @indirect_rec() +define i32 @indirect_rec() { + %a = call i32 @indirect_rec2() + ret i32 %a +} +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NOT: norecurse +; CHECK-NEXT: define i32 @indirect_rec2() +define i32 @indirect_rec2() { + %a = call i32 @indirect_rec() + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-SAME: readnone +; CHECK-NOT: norecurse +; CHECK-NEXT: define i32 @extern() +define i32 @extern() { + %a = call i32 @k() + ret i32 %a +} + +; CHECK: Function Attrs +; CHECK-NEXT: declare i32 @k() +declare i32 @k() readnone + +; CHECK: Function Attrs +; CHECK-SAME: nounwind +; CHECK-NOT: norecurse +; CHECK-NEXT: define void @intrinsic(i8* nocapture %dest, i8* nocapture readonly %src, i32 %len) +define void @intrinsic(i8* %dest, i8* %src, i32 %len) { + call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 false) + ret void +} + +; CHECK: Function Attrs +; CHECK-NEXT: declare void @llvm.memcpy.p0i8.p0i8.i32 +declare void @llvm.memcpy.p0i8.p0i8.i32(i8*, i8*, i32, i1) + +; CHECK: Function Attrs +; CHECK-SAME: norecurse readnone +; CHECK-NEXT: define internal i32 @called_by_norecurse() +define internal i32 @called_by_norecurse() { + %a = call i32 @k() + ret i32 %a +} +; CHECK: Function Attrs +; CHECK-NEXT: define void @m() +define void @m() norecurse { + %a = call i32 @called_by_norecurse() + ret void +} + +; CHECK: Function Attrs +; CHECK-SAME: norecurse readnone +; CHECK-NEXT: define internal i32 @called_by_norecurse_indirectly() +define internal i32 @called_by_norecurse_indirectly() { + %a = call i32 @k() + ret i32 %a +} +define internal void @o() { + %a = call i32 @called_by_norecurse_indirectly() + ret void +} +define void @p() norecurse { + call void @o() + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/operand-bundles-scc.ll b/llvm/test/Transforms/FunctionAttrs/operand-bundles-scc.ll new file mode 100644 index 00000000000..69808a83c6d --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/operand-bundles-scc.ll @@ -0,0 +1,17 @@ +; RUN: opt -S -functionattrs < %s | FileCheck %s +; RUN: opt -S -passes=function-attrs < %s | FileCheck %s + +define void @f() { +; CHECK-LABEL: define void @f() #0 { + call void @g() [ "unknown"() ] + ret void +} + +define void @g() { +; CHECK-LABEL: define void @g() #0 { + call void @f() + ret void +} + + +; CHECK: attributes #0 = { nounwind } diff --git a/llvm/test/Transforms/FunctionAttrs/optnone-simple.ll b/llvm/test/Transforms/FunctionAttrs/optnone-simple.ll new file mode 100644 index 00000000000..beaa588da50 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/optnone-simple.ll @@ -0,0 +1,135 @@ +; RUN: opt -O3 -S < %s | FileCheck %s +; Show 'optnone' suppresses optimizations. + +; Two attribute groups that differ only by 'optnone'. +; 'optnone' requires 'noinline' so #0 is 'noinline' by itself, +; even though it would otherwise be irrelevant to this example. +attributes #0 = { noinline } +attributes #1 = { noinline optnone } + +; int iadd(int a, int b){ return a + b; } + +define i32 @iadd_optimize(i32 %a, i32 %b) #0 { +entry: + %a.addr = alloca i32, align 4 + %b.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + store i32 %b, i32* %b.addr, align 4 + %0 = load i32, i32* %a.addr, align 4 + %1 = load i32, i32* %b.addr, align 4 + %add = add nsw i32 %0, %1 + ret i32 %add +} + +; CHECK-LABEL: @iadd_optimize +; CHECK-NOT: alloca +; CHECK-NOT: store +; CHECK-NOT: load +; CHECK: ret + +define i32 @iadd_optnone(i32 %a, i32 %b) #1 { +entry: + %a.addr = alloca i32, align 4 + %b.addr = alloca i32, align 4 + store i32 %a, i32* %a.addr, align 4 + store i32 %b, i32* %b.addr, align 4 + %0 = load i32, i32* %a.addr, align 4 + %1 = load i32, i32* %b.addr, align 4 + %add = add nsw i32 %0, %1 + ret i32 %add +} + +; CHECK-LABEL: @iadd_optnone +; CHECK: alloca i32 +; CHECK: alloca i32 +; CHECK: store i32 +; CHECK: store i32 +; CHECK: load i32 +; CHECK: load i32 +; CHECK: add nsw i32 +; CHECK: ret i32 + +; float fsub(float a, float b){ return a - b; } + +define float @fsub_optimize(float %a, float %b) #0 { +entry: + %a.addr = alloca float, align 4 + %b.addr = alloca float, align 4 + store float %a, float* %a.addr, align 4 + store float %b, float* %b.addr, align 4 + %0 = load float, float* %a.addr, align 4 + %1 = load float, float* %b.addr, align 4 + %sub = fsub float %0, %1 + ret float %sub +} + +; CHECK-LABEL: @fsub_optimize +; CHECK-NOT: alloca +; CHECK-NOT: store +; CHECK-NOT: load +; CHECK: ret + +define float @fsub_optnone(float %a, float %b) #1 { +entry: + %a.addr = alloca float, align 4 + %b.addr = alloca float, align 4 + store float %a, float* %a.addr, align 4 + store float %b, float* %b.addr, align 4 + %0 = load float, float* %a.addr, align 4 + %1 = load float, float* %b.addr, align 4 + %sub = fsub float %0, %1 + ret float %sub +} + +; CHECK-LABEL: @fsub_optnone +; CHECK: alloca float +; CHECK: alloca float +; CHECK: store float +; CHECK: store float +; CHECK: load float +; CHECK: load float +; CHECK: fsub float +; CHECK: ret float + +; typedef float __attribute__((ext_vector_type(4))) float4; +; float4 vmul(float4 a, float4 b){ return a * b; } + +define <4 x float> @vmul_optimize(<4 x float> %a, <4 x float> %b) #0 { +entry: + %a.addr = alloca <4 x float>, align 16 + %b.addr = alloca <4 x float>, align 16 + store <4 x float> %a, <4 x float>* %a.addr, align 16 + store <4 x float> %b, <4 x float>* %b.addr, align 16 + %0 = load <4 x float>, <4 x float>* %a.addr, align 16 + %1 = load <4 x float>, <4 x float>* %b.addr, align 16 + %mul = fmul <4 x float> %0, %1 + ret <4 x float> %mul +} + +; CHECK-LABEL: @vmul_optimize +; CHECK-NOT: alloca +; CHECK-NOT: store +; CHECK-NOT: load +; CHECK: ret + +define <4 x float> @vmul_optnone(<4 x float> %a, <4 x float> %b) #1 { +entry: + %a.addr = alloca <4 x float>, align 16 + %b.addr = alloca <4 x float>, align 16 + store <4 x float> %a, <4 x float>* %a.addr, align 16 + store <4 x float> %b, <4 x float>* %b.addr, align 16 + %0 = load <4 x float>, <4 x float>* %a.addr, align 16 + %1 = load <4 x float>, <4 x float>* %b.addr, align 16 + %mul = fmul <4 x float> %0, %1 + ret <4 x float> %mul +} + +; CHECK-LABEL: @vmul_optnone +; CHECK: alloca <4 x float> +; CHECK: alloca <4 x float> +; CHECK: store <4 x float> +; CHECK: store <4 x float> +; CHECK: load <4 x float> +; CHECK: load <4 x float> +; CHECK: fmul <4 x float> +; CHECK: ret diff --git a/llvm/test/Transforms/FunctionAttrs/optnone.ll b/llvm/test/Transforms/FunctionAttrs/optnone.ll new file mode 100644 index 00000000000..586a6d4a081 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/optnone.ll @@ -0,0 +1,25 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +@x = global i32 0 + +define void @test_opt(i8* %p) { +; CHECK-LABEL: @test_opt +; CHECK: (i8* nocapture readnone %p) #0 { + ret void +} + +define void @test_optnone(i8* %p) noinline optnone { +; CHECK-LABEL: @test_optnone +; CHECK: (i8* %p) #1 { + ret void +} + +declare i8 @strlen(i8*) noinline optnone +; CHECK-LABEL: @strlen +; CHECK: (i8*) #1 + +; CHECK-LABEL: attributes #0 +; CHECK: = { norecurse nounwind readnone } +; CHECK-LABEL: attributes #1 +; CHECK: = { noinline optnone } diff --git a/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll new file mode 100644 index 00000000000..f2294fe22ef --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll @@ -0,0 +1,31 @@ +; RUN: opt -functionattrs -S < %s | FileCheck %s +; RUN: opt -passes=function-attrs -S < %s | FileCheck %s + +; This checks for an iterator wraparound bug in FunctionAttrs. The previous +; "incorrect" behavior was inferring readonly for the %x argument in @caller. +; Inferring readonly for %x *is* actually correct, since @va_func is marked +; readonly, but FunctionAttrs was inferring readonly for the wrong reasons (and +; we _need_ the readonly on @va_func to trigger the problematic code path). It +; is possible that in the future FunctionAttrs becomes smart enough to infer +; readonly for %x for the right reasons, and at that point this test will have +; to be marked invalid. + +declare void @llvm.va_start(i8*) +declare void @llvm.va_end(i8*) + +define void @va_func(i32* readonly %b, ...) readonly nounwind { +; CHECK-LABEL: define void @va_func(i32* nocapture readonly %b, ...) + entry: + %valist = alloca i8 + call void @llvm.va_start(i8* %valist) + call void @llvm.va_end(i8* %valist) + %x = call i32 @caller(i32* %b) + ret void +} + +define i32 @caller(i32* %x) { +; CHECK-LABEL: define i32 @caller(i32* nocapture %x) + entry: + call void(i32*,...) @va_func(i32* null, i32 0, i32 0, i32 0, i32* %x) + ret i32 42 +} diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll new file mode 100644 index 00000000000..3728a717972 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll @@ -0,0 +1,115 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -aa-pipeline=basic-aa -passes='cgscc(function-attrs)' -S | FileCheck %s +@x = global i32 0 + +declare void @test1_1(i8* %x1_1, i8* readonly %y1_1, ...) + +; CHECK: define void @test1_2(i8* %x1_2, i8* readonly %y1_2, i8* %z1_2) +define void @test1_2(i8* %x1_2, i8* %y1_2, i8* %z1_2) { + call void (i8*, i8*, ...) @test1_1(i8* %x1_2, i8* %y1_2, i8* %z1_2) + store i32 0, i32* @x + ret void +} + +; CHECK: define i8* @test2(i8* readnone returned %p) +define i8* @test2(i8* %p) { + store i32 0, i32* @x + ret i8* %p +} + +; CHECK: define i1 @test3(i8* readnone %p, i8* readnone %q) +define i1 @test3(i8* %p, i8* %q) { + %A = icmp ult i8* %p, %q + ret i1 %A +} + +declare void @test4_1(i8* nocapture) readonly + +; CHECK: define void @test4_2(i8* nocapture readonly %p) +define void @test4_2(i8* %p) { + call void @test4_1(i8* %p) + ret void +} + +; CHECK: define void @test5(i8** nocapture %p, i8* %q) +; Missed optz'n: we could make %q readnone, but don't break test6! +define void @test5(i8** %p, i8* %q) { + store i8* %q, i8** %p + ret void +} + +declare void @test6_1() +; CHECK: define void @test6_2(i8** nocapture %p, i8* %q) +; This is not a missed optz'n. +define void @test6_2(i8** %p, i8* %q) { + store i8* %q, i8** %p + call void @test6_1() + ret void +} + +; CHECK: define void @test7_1(i32* inalloca nocapture %a) +; inalloca parameters are always considered written +define void @test7_1(i32* inalloca %a) { + ret void +} + +; CHECK: define i32* @test8_1(i32* readnone returned %p) +define i32* @test8_1(i32* %p) { +entry: + ret i32* %p +} + +; CHECK: define void @test8_2(i32* %p) +define void @test8_2(i32* %p) { +entry: + %call = call i32* @test8_1(i32* %p) + store i32 10, i32* %call, align 4 + ret void +} + +; CHECK: declare void @llvm.masked.scatter +declare void @llvm.masked.scatter.v4i32.v4p0i32(<4 x i32>%val, <4 x i32*>, i32, <4 x i1>) + +; CHECK-NOT: readnone +; CHECK-NOT: readonly +; CHECK: define void @test9 +define void @test9(<4 x i32*> %ptrs, <4 x i32>%val) { + call void @llvm.masked.scatter.v4i32.v4p0i32(<4 x i32>%val, <4 x i32*> %ptrs, i32 4, <4 x i1><i1 true, i1 false, i1 true, i1 false>) + ret void +} + +; CHECK: declare <4 x i32> @llvm.masked.gather +declare <4 x i32> @llvm.masked.gather.v4i32.v4p0i32(<4 x i32*>, i32, <4 x i1>, <4 x i32>) +; CHECK: readonly +; CHECK: define <4 x i32> @test10 +define <4 x i32> @test10(<4 x i32*> %ptrs) { + %res = call <4 x i32> @llvm.masked.gather.v4i32.v4p0i32(<4 x i32*> %ptrs, i32 4, <4 x i1><i1 true, i1 false, i1 true, i1 false>, <4 x i32>undef) + ret <4 x i32> %res +} + +; CHECK: declare <4 x i32> @test11_1 +declare <4 x i32> @test11_1(<4 x i32*>) argmemonly nounwind readonly +; CHECK: readonly +; CHECK-NOT: readnone +; CHECK: define <4 x i32> @test11_2 +define <4 x i32> @test11_2(<4 x i32*> %ptrs) { + %res = call <4 x i32> @test11_1(<4 x i32*> %ptrs) + ret <4 x i32> %res +} + +declare <4 x i32> @test12_1(<4 x i32*>) argmemonly nounwind +; CHECK-NOT: readnone +; CHECK: define <4 x i32> @test12_2 +define <4 x i32> @test12_2(<4 x i32*> %ptrs) { + %res = call <4 x i32> @test12_1(<4 x i32*> %ptrs) + ret <4 x i32> %res +} + +; CHECK: define i32 @volatile_load( +; CHECK-NOT: readonly +; CHECK: ret +define i32 @volatile_load(i32* %p) { + %load = load volatile i32, i32* %p + ret i32 %load +} diff --git a/llvm/test/Transforms/FunctionAttrs/readnone.ll b/llvm/test/Transforms/FunctionAttrs/readnone.ll new file mode 100644 index 00000000000..b5a5b30ae21 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/readnone.ll @@ -0,0 +1,14 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +; CHECK: define void @bar(i8* nocapture readnone) +define void @bar(i8* readonly) { + call void @foo(i8* %0) + ret void +} + +; CHECK: define void @foo(i8* nocapture readnone) +define void @foo(i8* readonly) { + call void @bar(i8* %0) + ret void +} diff --git a/llvm/test/Transforms/FunctionAttrs/returned.ll b/llvm/test/Transforms/FunctionAttrs/returned.ll new file mode 100644 index 00000000000..04ddb7b5ac0 --- /dev/null +++ b/llvm/test/Transforms/FunctionAttrs/returned.ll @@ -0,0 +1,31 @@ +; RUN: opt < %s -functionattrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck %s + +; CHECK: define i32 @test1(i32 %p, i32 %q) +define i32 @test1(i32 %p, i32 %q) { +entry: + %cmp = icmp sgt i32 %p, %q + br i1 %cmp, label %cond.end, label %lor.lhs.false + +lor.lhs.false: ; preds = %entry + %tobool = icmp ne i32 %p, 0 + %tobool1 = icmp ne i32 %q, 0 + %or.cond = and i1 %tobool, %tobool1 + %p.q = select i1 %or.cond, i32 %p, i32 %q + ret i32 %p.q + +cond.end: ; preds = %entry + ret i32 %p +} + +; CHECK: define i32 @test2(i32 %p1, i32 returned %p2) +define i32 @test2(i32 %p1, i32 returned %p2) { + %_tmp4 = icmp eq i32 %p1, %p2 + br i1 %_tmp4, label %bb2, label %bb1 + +bb2: ; preds = %0 + ret i32 %p1 + +bb1: ; preds = %bb1, %0 + br label %bb1 +} |

