| ; RUN: opt < %s -aa-pipeline=basic-aa -passes='cgscc(function-attrs,inline)' -S | FileCheck %s |
| ; This test runs the inliner and the function attribute deduction. It ensures |
| ; that when the inliner mutates the call graph it correctly updates the CGSCC |
| ; iteration so that we can compute refined function attributes. In this way it |
| ; is leveraging function attribute computation to observe correct call graph |
| ; updates. |
| |
| ; Boring unknown external function call. |
| ; CHECK: declare void @unknown() |
| declare void @unknown() |
| |
| ; Basic correctness check: this should get annotated as memory(none). |
| ; CHECK: Function Attrs: nounwind memory(none) |
| ; CHECK-NEXT: declare void @readnone() |
| declare void @readnone() readnone nounwind |
| |
| ; The 'test1_' prefixed functions are designed to trigger forming a new direct |
| ; call in the inlined body of the function. After that, we form a new SCC and |
| ; using that can deduce precise function attrs. |
| |
| ; This function should no longer exist. |
| ; CHECK-NOT: @test1_f() |
| define internal void @test1_f(ptr %p) { |
| entry: |
| call void %p() |
| ret void |
| } |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test1_g() |
| define void @test1_g() noinline { |
| entry: |
| call void @test1_f(ptr @test1_h) |
| ret void |
| } |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test1_h() |
| define void @test1_h() noinline { |
| entry: |
| call void @test1_g() |
| call void @readnone() |
| ret void |
| } |
| |
| |
| ; The 'test2_' prefixed functions are designed to trigger forming a new direct |
| ; call due to RAUW-ing the returned value of a called function into the caller. |
| ; This too should form a new SCC which can then be reasoned about to compute |
| ; precise function attrs. |
| |
| ; This function should no longer exist. |
| ; CHECK-NOT: @test2_f() |
| define internal ptr @test2_f() { |
| entry: |
| ret ptr @test2_h |
| } |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test2_g() |
| define void @test2_g() noinline { |
| entry: |
| %p = call ptr @test2_f() |
| call void %p() |
| ret void |
| } |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test2_h() |
| define void @test2_h() noinline { |
| entry: |
| call void @test2_g() |
| call void @readnone() |
| ret void |
| } |
| |
| |
| ; The 'test3_' prefixed functions are designed to inline in a way that causes |
| ; call sites to become trivially dead during the middle of inlining callsites of |
| ; a single function to make sure that the inliner does not get confused by this |
| ; pattern. |
| |
| ; CHECK-NOT: @test3_maybe_unknown( |
| define internal void @test3_maybe_unknown(i1 %b) { |
| entry: |
| br i1 %b, label %then, label %exit |
| |
| then: |
| call void @unknown() |
| br label %exit |
| |
| exit: |
| ret void |
| } |
| |
| ; CHECK-NOT: @test3_f( |
| define internal i1 @test3_f() { |
| entry: |
| ret i1 false |
| } |
| |
| ; CHECK-NOT: @test3_g( |
| define internal i1 @test3_g(i1 %b) { |
| entry: |
| br i1 %b, label %then1, label %if2 |
| |
| then1: |
| call void @test3_maybe_unknown(i1 true) |
| br label %if2 |
| |
| if2: |
| %f = call i1 @test3_f() |
| br i1 %f, label %then2, label %exit |
| |
| then2: |
| call void @test3_maybe_unknown(i1 true) |
| br label %exit |
| |
| exit: |
| ret i1 false |
| } |
| |
| ; FIXME: Currently the inliner doesn't successfully mark this as readnone |
| ; because while it simplifies trivially dead CFGs when inlining callees it |
| ; doesn't simplify the caller's trivially dead CFG and so we end with a dead |
| ; block calling @unknown. |
| ; CHECK-NOT: Function Attrs: readnone |
| ; CHECK: define void @test3_h() |
| define void @test3_h() { |
| entry: |
| %g = call i1 @test3_g(i1 false) |
| br i1 %g, label %then, label %exit |
| |
| then: |
| call void @test3_maybe_unknown(i1 true) |
| br label %exit |
| |
| exit: |
| call void @test3_maybe_unknown(i1 false) |
| ret void |
| } |
| |
| |
| ; The 'test4_' prefixed functions are designed to trigger forming a new direct |
| ; call in the inlined body of the function similar to 'test1_'. However, after |
| ; that we continue to inline another edge of the graph forcing us to do a more |
| ; interesting call graph update for the new call edge. Eventually, we still |
| ; form a new SCC and should use that can deduce precise function attrs. |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test4_f1() |
| define void @test4_f1() noinline { |
| entry: |
| call void @test4_h() |
| ret void |
| } |
| |
| ; CHECK-NOT: @test4_f2 |
| define internal void @test4_f2() { |
| entry: |
| call void @test4_f1() |
| ret void |
| } |
| |
| ; CHECK-NOT: @test4_g |
| define internal void @test4_g(ptr %p) { |
| entry: |
| call void %p() |
| ret void |
| } |
| |
| ; This function should have had 'memory(none)' deduced for its SCC. |
| ; CHECK: Function Attrs: nofree noinline nosync nounwind memory(none) |
| ; CHECK-NEXT: define void @test4_h() |
| define void @test4_h() noinline { |
| entry: |
| call void @test4_g(ptr @test4_f2) |
| ret void |
| } |