[NFC][FnAttrs] Stress tests for attribute deduction
This commit is a preparation of upcoming patches on attribute deduction. It will shorten the diffs and make it clear what we inferred before. Reviewers: chandlerc, homerdin, hfinkel, fedor.sergeev, sanjoy, spatel, nlopes Subscribers: bollu, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D59903 llvm-svn: 362577
This commit is contained in:
445
llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
Normal file
445
llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
Normal file
@@ -0,0 +1,445 @@
|
||||
; RUN: opt -functionattrs -S < %s | FileCheck %s
|
||||
;
|
||||
; Test cases specifically designed for the "no-capture" argument attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
;
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
; TEST comparison against NULL
|
||||
;
|
||||
; int is_null_return(int *p) {
|
||||
; return p == 0;
|
||||
; }
|
||||
;
|
||||
; FIXME: no-capture missing for %p
|
||||
; CHECK: define i32 @is_null_return(i32* readnone %p)
|
||||
define i32 @is_null_return(i32* %p) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32* %p, null
|
||||
%conv = zext i1 %cmp to i32
|
||||
ret i32 %conv
|
||||
}
|
||||
|
||||
; TEST comparison against NULL in control flow
|
||||
;
|
||||
; int is_null_control(int *p) {
|
||||
; if (p == 0)
|
||||
; return 1;
|
||||
; if (0 == p)
|
||||
; return 1;
|
||||
; return 0;
|
||||
; }
|
||||
;
|
||||
; FIXME: no-capture missing for %p
|
||||
; CHECK: define i32 @is_null_control(i32* readnone %p)
|
||||
define i32 @is_null_control(i32* %p) #0 {
|
||||
entry:
|
||||
%retval = alloca i32, align 4
|
||||
%cmp = icmp eq i32* %p, null
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%cmp1 = icmp eq i32* null, %p
|
||||
br i1 %cmp1, label %if.then2, label %if.end3
|
||||
|
||||
if.then2: ; preds = %if.end
|
||||
store i32 1, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
if.end3: ; preds = %if.end
|
||||
store i32 0, i32* %retval, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %if.end3, %if.then2, %if.then
|
||||
%0 = load i32, i32* %retval, align 4
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; TEST singleton SCC
|
||||
;
|
||||
; double *srec0(double *a) {
|
||||
; srec0(a);
|
||||
; return 0;
|
||||
; }
|
||||
;
|
||||
; CHECK: define noalias double* @srec0(double* nocapture readnone %a)
|
||||
define double* @srec0(double* %a) #0 {
|
||||
entry:
|
||||
%call = call double* @srec0(double* %a)
|
||||
ret double* null
|
||||
}
|
||||
|
||||
; TEST singleton SCC with lots of nested recursive calls
|
||||
;
|
||||
; int* srec16(int* a) {
|
||||
; return srec16(srec16(srec16(srec16(
|
||||
; srec16(srec16(srec16(srec16(
|
||||
; srec16(srec16(srec16(srec16(
|
||||
; srec16(srec16(srec16(srec16(
|
||||
; a
|
||||
; ))))))))))))))));
|
||||
; }
|
||||
;
|
||||
; Other arguments are possible here due to the no-return behavior.
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: define noalias nonnull i32* @srec16(i32* nocapture readnone %a)
|
||||
define i32* @srec16(i32* %a) #0 {
|
||||
entry:
|
||||
%call = call i32* @srec16(i32* %a)
|
||||
%call1 = call i32* @srec16(i32* %call)
|
||||
%call2 = call i32* @srec16(i32* %call1)
|
||||
%call3 = call i32* @srec16(i32* %call2)
|
||||
%call4 = call i32* @srec16(i32* %call3)
|
||||
%call5 = call i32* @srec16(i32* %call4)
|
||||
%call6 = call i32* @srec16(i32* %call5)
|
||||
%call7 = call i32* @srec16(i32* %call6)
|
||||
%call8 = call i32* @srec16(i32* %call7)
|
||||
%call9 = call i32* @srec16(i32* %call8)
|
||||
%call10 = call i32* @srec16(i32* %call9)
|
||||
%call11 = call i32* @srec16(i32* %call10)
|
||||
%call12 = call i32* @srec16(i32* %call11)
|
||||
%call13 = call i32* @srec16(i32* %call12)
|
||||
%call14 = call i32* @srec16(i32* %call13)
|
||||
%call15 = call i32* @srec16(i32* %call14)
|
||||
ret i32* %call15
|
||||
}
|
||||
|
||||
; TEST SCC with various calls, casts, and comparisons agains NULL
|
||||
;
|
||||
; FIXME: returned missing for %a
|
||||
; FIXME: no-capture missing for %a
|
||||
; CHECK: define float* @scc_A(i32* readnone %a)
|
||||
;
|
||||
; FIXME: returned missing for %a
|
||||
; FIXME: no-capture missing for %a
|
||||
; CHECK: define i64* @scc_B(double* readnone %a)
|
||||
;
|
||||
; FIXME: returned missing for %a
|
||||
; FIXME: readnone missing for %s
|
||||
; FIXME: no-capture missing for %a
|
||||
; CHECK: define i8* @scc_C(i16* %a)
|
||||
;
|
||||
; float *scc_A(int *a) {
|
||||
; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a);
|
||||
; }
|
||||
;
|
||||
; long *scc_B(double *a) {
|
||||
; return (long*)(a ? scc_C((short*)scc_B((double*)scc_A((int*)a))) : a);
|
||||
; }
|
||||
;
|
||||
; void *scc_C(short *a) {
|
||||
; return scc_A((int*)(scc_C(a) ? scc_B((double*)a) : scc_C(a)));
|
||||
; }
|
||||
define float* @scc_A(i32* %a) {
|
||||
entry:
|
||||
%tobool = icmp ne i32* %a, null
|
||||
br i1 %tobool, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %entry
|
||||
%0 = bitcast i32* %a to i16*
|
||||
%call = call i8* @scc_C(i16* %0)
|
||||
%1 = bitcast i8* %call to double*
|
||||
%call1 = call i64* @scc_B(double* %1)
|
||||
%2 = bitcast i64* %call1 to i32*
|
||||
%call2 = call float* @scc_A(i32* %2)
|
||||
%3 = bitcast float* %call2 to i32*
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %entry
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i32* [ %3, %cond.true ], [ %a, %cond.false ]
|
||||
%4 = bitcast i32* %cond to float*
|
||||
ret float* %4
|
||||
}
|
||||
|
||||
define i64* @scc_B(double* %a) {
|
||||
entry:
|
||||
%tobool = icmp ne double* %a, null
|
||||
br i1 %tobool, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %entry
|
||||
%0 = bitcast double* %a to i32*
|
||||
%call = call float* @scc_A(i32* %0)
|
||||
%1 = bitcast float* %call to double*
|
||||
%call1 = call i64* @scc_B(double* %1)
|
||||
%2 = bitcast i64* %call1 to i16*
|
||||
%call2 = call i8* @scc_C(i16* %2)
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %entry
|
||||
%3 = bitcast double* %a to i8*
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i8* [ %call2, %cond.true ], [ %3, %cond.false ]
|
||||
%4 = bitcast i8* %cond to i64*
|
||||
ret i64* %4
|
||||
}
|
||||
|
||||
define i8* @scc_C(i16* %a) {
|
||||
entry:
|
||||
%call = call i8* @scc_C(i16* %a)
|
||||
%tobool = icmp ne i8* %call, null
|
||||
br i1 %tobool, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %entry
|
||||
%0 = bitcast i16* %a to double*
|
||||
%call1 = call i64* @scc_B(double* %0)
|
||||
%1 = bitcast i64* %call1 to i8*
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %entry
|
||||
%call2 = call i8* @scc_C(i16* %a)
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i8* [ %1, %cond.true ], [ %call2, %cond.false ]
|
||||
%2 = bitcast i8* %cond to i32*
|
||||
%call3 = call float* @scc_A(i32* %2)
|
||||
%3 = bitcast float* %call3 to i8*
|
||||
ret i8* %3
|
||||
}
|
||||
|
||||
|
||||
; TEST call to external function, marked no-capture
|
||||
;
|
||||
; void external_no_capture(int /* no-capture */ *p);
|
||||
; void test_external_no_capture(int *p) {
|
||||
; external_no_capture(p);
|
||||
; }
|
||||
;
|
||||
; CHECK: define void @test_external_no_capture(i32* nocapture %p)
|
||||
declare void @external_no_capture(i32* nocapture)
|
||||
|
||||
define void @test_external_no_capture(i32* %p) #0 {
|
||||
entry:
|
||||
call void @external_no_capture(i32* %p)
|
||||
ret void
|
||||
}
|
||||
|
||||
; TEST call to external var-args function, marked no-capture
|
||||
;
|
||||
; void test_var_arg_call(char *p, int a) {
|
||||
; printf(p, a);
|
||||
; }
|
||||
;
|
||||
; CHECK: define void @test_var_arg_call(i8* nocapture %p, i32 %a)
|
||||
define void @test_var_arg_call(i8* %p, i32 %a) #0 {
|
||||
entry:
|
||||
%call = call i32 (i8*, ...) @printf(i8* %p, i32 %a)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @printf(i8* nocapture, ...)
|
||||
|
||||
|
||||
; TEST "captured" only through return
|
||||
;
|
||||
; long *not_captured_but_returned_0(long *a) {
|
||||
; *a1 = 0;
|
||||
; return a;
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define i64* @not_captured_but_returned_0(i64* returned %a)
|
||||
define i64* @not_captured_but_returned_0(i64* %a) #0 {
|
||||
entry:
|
||||
store i64 0, i64* %a, align 8
|
||||
ret i64* %a
|
||||
}
|
||||
|
||||
; TEST "captured" only through return
|
||||
;
|
||||
; long *not_captured_but_returned_1(long *a) {
|
||||
; *(a+1) = 1;
|
||||
; return a + 1;
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define nonnull i64* @not_captured_but_returned_1(i64* %a)
|
||||
define i64* @not_captured_but_returned_1(i64* %a) #0 {
|
||||
entry:
|
||||
%add.ptr = getelementptr inbounds i64, i64* %a, i64 1
|
||||
store i64 1, i64* %add.ptr, align 8
|
||||
ret i64* %add.ptr
|
||||
}
|
||||
|
||||
; TEST calls to "captured" only through return functions
|
||||
;
|
||||
; void test_not_captured_but_returned_calls(long *a) {
|
||||
; not_captured_but_returned_0(a);
|
||||
; not_captured_but_returned_1(a);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-capture missing for %a
|
||||
; CHECK: define void @test_not_captured_but_returned_calls(i64* %a)
|
||||
define void @test_not_captured_but_returned_calls(i64* %a) #0 {
|
||||
entry:
|
||||
%call = call i64* @not_captured_but_returned_0(i64* %a)
|
||||
%call1 = call i64* @not_captured_but_returned_1(i64* %a)
|
||||
ret void
|
||||
}
|
||||
|
||||
; TEST "captured" only through transitive return
|
||||
;
|
||||
; long* negative_test_not_captured_but_returned_call_0a(long *a) {
|
||||
; return not_captured_but_returned_0(a);
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define i64* @negative_test_not_captured_but_returned_call_0a(i64* returned %a)
|
||||
define i64* @negative_test_not_captured_but_returned_call_0a(i64* %a) #0 {
|
||||
entry:
|
||||
%call = call i64* @not_captured_but_returned_0(i64* %a)
|
||||
ret i64* %call
|
||||
}
|
||||
|
||||
; TEST captured through write
|
||||
;
|
||||
; void negative_test_not_captured_but_returned_call_0b(long *a) {
|
||||
; *a = (long)not_captured_but_returned_0(a);
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define void @negative_test_not_captured_but_returned_call_0b(i64* %a)
|
||||
define void @negative_test_not_captured_but_returned_call_0b(i64* %a) #0 {
|
||||
entry:
|
||||
%call = call i64* @not_captured_but_returned_0(i64* %a)
|
||||
%0 = ptrtoint i64* %call to i64
|
||||
store i64 %0, i64* %a, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
; TEST "captured" only through transitive return
|
||||
;
|
||||
; long* negative_test_not_captured_but_returned_call_1a(long *a) {
|
||||
; return not_captured_but_returned_1(a);
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* %a)
|
||||
define i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) #0 {
|
||||
entry:
|
||||
%call = call i64* @not_captured_but_returned_1(i64* %a)
|
||||
ret i64* %call
|
||||
}
|
||||
|
||||
; TEST captured through write
|
||||
;
|
||||
; void negative_test_not_captured_but_returned_call_1b(long *a) {
|
||||
; *a = (long)not_captured_but_returned_1(a);
|
||||
; }
|
||||
;
|
||||
; There should *not* be a no-capture attribute on %a
|
||||
; CHECK: define void @negative_test_not_captured_but_returned_call_1b(i64* %a)
|
||||
define void @negative_test_not_captured_but_returned_call_1b(i64* %a) #0 {
|
||||
entry:
|
||||
%call = call i64* @not_captured_but_returned_1(i64* %a)
|
||||
%0 = ptrtoint i64* %call to i64
|
||||
store i64 %0, i64* %call, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
; TEST return argument or unknown call result
|
||||
;
|
||||
; int* ret_arg_or_unknown(int* b) {
|
||||
; if (b == 0)
|
||||
; return b;
|
||||
; return unknown();
|
||||
; }
|
||||
;
|
||||
; Verify we do *not* assume b is returned or not captured.
|
||||
;
|
||||
; CHECK: define i32* @ret_arg_or_unknown(i32* readnone %b)
|
||||
; CHECK: define i32* @ret_arg_or_unknown_through_phi(i32* readnone %b)
|
||||
declare i32* @unknown()
|
||||
|
||||
define i32* @ret_arg_or_unknown(i32* %b) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32* %b, null
|
||||
br i1 %cmp, label %ret_arg, label %ret_unknown
|
||||
|
||||
ret_arg:
|
||||
ret i32* %b
|
||||
|
||||
ret_unknown:
|
||||
%call = call i32* @unknown()
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
define i32* @ret_arg_or_unknown_through_phi(i32* %b) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32* %b, null
|
||||
br i1 %cmp, label %ret_arg, label %ret_unknown
|
||||
|
||||
ret_arg:
|
||||
br label %r
|
||||
|
||||
ret_unknown:
|
||||
%call = call i32* @unknown()
|
||||
br label %r
|
||||
|
||||
r:
|
||||
%phi = phi i32* [ %b, %ret_arg ], [ %call, %ret_unknown ]
|
||||
ret i32* %phi
|
||||
}
|
||||
|
||||
|
||||
; TEST not captured by readonly external function
|
||||
;
|
||||
; CHECK: define void @not_captured_by_readonly_call(i32* nocapture %b)
|
||||
declare i32* @readonly_unknown(i32*, i32*) readonly
|
||||
|
||||
define void @not_captured_by_readonly_call(i32* %b) #0 {
|
||||
entry:
|
||||
%call = call i32* @readonly_unknown(i32* %b, i32* %b)
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; TEST not captured by readonly external function if return chain is known
|
||||
;
|
||||
; Make sure the returned flag on %r is strong enough to justify nocapture on %b but **not** on %r.
|
||||
;
|
||||
; FIXME: The "returned" information is not propagated to the fullest extend causing us to miss "nocapture" on %b in the following:
|
||||
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either1(i32* readonly %b, i32* readonly returned %r)
|
||||
;
|
||||
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either2(i32* readonly %b, i32* readonly returned %r)
|
||||
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either3(i32* readonly %b, i32* readonly returned %r)
|
||||
;
|
||||
; FIXME: The "nounwind" information is not derived to the fullest extend causing us to miss "nocapture" on %b in the following:
|
||||
; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either4(i32* readonly %b, i32* readonly returned %r)
|
||||
define i32* @not_captured_by_readonly_call_not_returned_either1(i32* %b, i32* returned %r) #0 {
|
||||
entry:
|
||||
%call = call i32* @readonly_unknown(i32* %b, i32* %r) nounwind
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
declare i32* @readonly_unknown_r1a(i32*, i32* returned) readonly
|
||||
define i32* @not_captured_by_readonly_call_not_returned_either2(i32* %b, i32* %r) #0 {
|
||||
entry:
|
||||
%call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r) nounwind
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
declare i32* @readonly_unknown_r1b(i32*, i32* returned) readonly nounwind
|
||||
define i32* @not_captured_by_readonly_call_not_returned_either3(i32* %b, i32* %r) #0 {
|
||||
entry:
|
||||
%call = call i32* @readonly_unknown_r1b(i32* %b, i32* %r)
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
define i32* @not_captured_by_readonly_call_not_returned_either4(i32* %b, i32* %r) #0 {
|
||||
entry:
|
||||
%call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r)
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
attributes #0 = { noinline nounwind uwtable }
|
||||
589
llvm/test/Transforms/FunctionAttrs/arg_returned.ll
Normal file
589
llvm/test/Transforms/FunctionAttrs/arg_returned.ll
Normal file
@@ -0,0 +1,589 @@
|
||||
; RUN: opt -functionattrs -S < %s | FileCheck %s
|
||||
;
|
||||
; Test cases specifically designed for the "returned" argument attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
;
|
||||
|
||||
; TEST SCC test returning an integer value argument
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define i32 @sink_r0(i32 returned %r)
|
||||
;
|
||||
; FIXME: returned on %r missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define i32 @scc_r1(i32 %a, i32 %r, i32 %b)
|
||||
;
|
||||
; FIXME: returned on %r missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define i32 @scc_r2(i32 %a, i32 %b, i32 %r)
|
||||
;
|
||||
; int scc_r1(int a, int b, int r);
|
||||
; int scc_r2(int a, int b, int r);
|
||||
;
|
||||
; __attribute__((noinline)) int sink_r0(int r) {
|
||||
; return r;
|
||||
; }
|
||||
;
|
||||
; __attribute__((noinline)) int scc_r1(int a, int r, int b) {
|
||||
; return scc_r2(r, a, sink_r0(r));
|
||||
; }
|
||||
;
|
||||
; __attribute__((noinline)) int scc_r2(int a, int b, int r) {
|
||||
; if (a > b)
|
||||
; return scc_r2(b, a, sink_r0(r));
|
||||
; if (a < b)
|
||||
; return scc_r1(sink_r0(b), scc_r2(scc_r1(a, b, r), scc_r1(a, scc_r2(r, r, r), r), scc_r2(a, b, r)), scc_r1(a, b, r));
|
||||
; return a == b ? r : scc_r2(a, b, r);
|
||||
; }
|
||||
; __attribute__((noinline)) int scc_rX(int a, int b, int r) {
|
||||
; if (a > b)
|
||||
; return scc_r2(b, a, sink_r0(r));
|
||||
; if (a < b) // V Diff to scc_r2
|
||||
; return scc_r1(sink_r0(b), scc_r2(scc_r1(a, b, r), scc_r1(a, scc_r2(r, r, r), r), scc_r1(a, b, r)), scc_r1(a, b, r));
|
||||
; return a == b ? r : scc_r2(a, b, r);
|
||||
; }
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
define i32 @sink_r0(i32 %r) #0 {
|
||||
entry:
|
||||
ret i32 %r
|
||||
}
|
||||
|
||||
define i32 @scc_r1(i32 %a, i32 %r, i32 %b) #0 {
|
||||
entry:
|
||||
%call = call i32 @sink_r0(i32 %r)
|
||||
%call1 = call i32 @scc_r2(i32 %r, i32 %a, i32 %call)
|
||||
ret i32 %call1
|
||||
}
|
||||
|
||||
define i32 @scc_r2(i32 %a, i32 %b, i32 %r) #0 {
|
||||
entry:
|
||||
%cmp = icmp sgt i32 %a, %b
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%call = call i32 @sink_r0(i32 %r)
|
||||
%call1 = call i32 @scc_r2(i32 %b, i32 %a, i32 %call)
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%cmp2 = icmp slt i32 %a, %b
|
||||
br i1 %cmp2, label %if.then3, label %if.end12
|
||||
|
||||
if.then3: ; preds = %if.end
|
||||
%call4 = call i32 @sink_r0(i32 %b)
|
||||
%call5 = call i32 @scc_r1(i32 %a, i32 %b, i32 %r)
|
||||
%call6 = call i32 @scc_r2(i32 %r, i32 %r, i32 %r)
|
||||
%call7 = call i32 @scc_r1(i32 %a, i32 %call6, i32 %r)
|
||||
%call8 = call i32 @scc_r2(i32 %a, i32 %b, i32 %r)
|
||||
%call9 = call i32 @scc_r2(i32 %call5, i32 %call7, i32 %call8)
|
||||
%call10 = call i32 @scc_r1(i32 %a, i32 %b, i32 %r)
|
||||
%call11 = call i32 @scc_r1(i32 %call4, i32 %call9, i32 %call10)
|
||||
br label %return
|
||||
|
||||
if.end12: ; preds = %if.end
|
||||
%cmp13 = icmp eq i32 %a, %b
|
||||
br i1 %cmp13, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %if.end12
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %if.end12
|
||||
%call14 = call i32 @scc_r2(i32 %a, i32 %b, i32 %r)
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i32 [ %r, %cond.true ], [ %call14, %cond.false ]
|
||||
br label %return
|
||||
|
||||
return: ; preds = %cond.end, %if.then3, %if.then
|
||||
%retval.0 = phi i32 [ %call1, %if.then ], [ %call11, %if.then3 ], [ %cond, %cond.end ]
|
||||
ret i32 %retval.0
|
||||
}
|
||||
|
||||
define i32 @scc_rX(i32 %a, i32 %b, i32 %r) #0 {
|
||||
entry:
|
||||
%cmp = icmp sgt i32 %a, %b
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%call = call i32 @sink_r0(i32 %r)
|
||||
%call1 = call i32 @scc_r2(i32 %b, i32 %a, i32 %call)
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%cmp2 = icmp slt i32 %a, %b
|
||||
br i1 %cmp2, label %if.then3, label %if.end12
|
||||
|
||||
if.then3: ; preds = %if.end
|
||||
%call4 = call i32 @sink_r0(i32 %b)
|
||||
%call5 = call i32 @scc_r1(i32 %a, i32 %b, i32 %r)
|
||||
%call6 = call i32 @scc_r2(i32 %r, i32 %r, i32 %r)
|
||||
%call7 = call i32 @scc_r1(i32 %a, i32 %call6, i32 %r)
|
||||
%call8 = call i32 @scc_r1(i32 %a, i32 %b, i32 %r)
|
||||
%call9 = call i32 @scc_r2(i32 %call5, i32 %call7, i32 %call8)
|
||||
%call10 = call i32 @scc_r1(i32 %a, i32 %b, i32 %r)
|
||||
%call11 = call i32 @scc_r1(i32 %call4, i32 %call9, i32 %call10)
|
||||
br label %return
|
||||
|
||||
if.end12: ; preds = %if.end
|
||||
%cmp13 = icmp eq i32 %a, %b
|
||||
br i1 %cmp13, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %if.end12
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %if.end12
|
||||
%call14 = call i32 @scc_r2(i32 %a, i32 %b, i32 %r)
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i32 [ %r, %cond.true ], [ %call14, %cond.false ]
|
||||
br label %return
|
||||
|
||||
return: ; preds = %cond.end, %if.then3, %if.then
|
||||
%retval.0 = phi i32 [ %call1, %if.then ], [ %call11, %if.then3 ], [ %cond, %cond.end ]
|
||||
ret i32 %retval.0
|
||||
}
|
||||
|
||||
|
||||
; TEST SCC test returning a pointer value argument
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @ptr_sink_r0(double* readnone returned %r)
|
||||
;
|
||||
; FIXME: returned on %r missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b)
|
||||
;
|
||||
; FIXME: returned on %r missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r)
|
||||
;
|
||||
; double* ptr_scc_r1(double* a, double* b, double* r);
|
||||
; double* ptr_scc_r2(double* a, double* b, double* r);
|
||||
;
|
||||
; __attribute__((noinline)) double* ptr_sink_r0(double* r) {
|
||||
; return r;
|
||||
; }
|
||||
;
|
||||
; __attribute__((noinline)) double* ptr_scc_r1(double* a, double* r, double* b) {
|
||||
; return ptr_scc_r2(r, a, ptr_sink_r0(r));
|
||||
; }
|
||||
;
|
||||
; __attribute__((noinline)) double* ptr_scc_r2(double* a, double* b, double* r) {
|
||||
; if (a > b)
|
||||
; return ptr_scc_r2(b, a, ptr_sink_r0(r));
|
||||
; if (a < b)
|
||||
; return ptr_scc_r1(ptr_sink_r0(b), ptr_scc_r2(ptr_scc_r1(a, b, r), ptr_scc_r1(a, ptr_scc_r2(r, r, r), r), ptr_scc_r2(a, b, r)), ptr_scc_r1(a, b, r));
|
||||
; return a == b ? r : ptr_scc_r2(a, b, r);
|
||||
; }
|
||||
define double* @ptr_sink_r0(double* %r) #0 {
|
||||
entry:
|
||||
ret double* %r
|
||||
}
|
||||
|
||||
define double* @ptr_scc_r1(double* %a, double* %r, double* %b) #0 {
|
||||
entry:
|
||||
%call = call double* @ptr_sink_r0(double* %r)
|
||||
%call1 = call double* @ptr_scc_r2(double* %r, double* %a, double* %call)
|
||||
ret double* %call1
|
||||
}
|
||||
|
||||
define double* @ptr_scc_r2(double* %a, double* %b, double* %r) #0 {
|
||||
entry:
|
||||
%cmp = icmp ugt double* %a, %b
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%call = call double* @ptr_sink_r0(double* %r)
|
||||
%call1 = call double* @ptr_scc_r2(double* %b, double* %a, double* %call)
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%cmp2 = icmp ult double* %a, %b
|
||||
br i1 %cmp2, label %if.then3, label %if.end12
|
||||
|
||||
if.then3: ; preds = %if.end
|
||||
%call4 = call double* @ptr_sink_r0(double* %b)
|
||||
%call5 = call double* @ptr_scc_r1(double* %a, double* %b, double* %r)
|
||||
%call6 = call double* @ptr_scc_r2(double* %r, double* %r, double* %r)
|
||||
%call7 = call double* @ptr_scc_r1(double* %a, double* %call6, double* %r)
|
||||
%call8 = call double* @ptr_scc_r2(double* %a, double* %b, double* %r)
|
||||
%call9 = call double* @ptr_scc_r2(double* %call5, double* %call7, double* %call8)
|
||||
%call10 = call double* @ptr_scc_r1(double* %a, double* %b, double* %r)
|
||||
%call11 = call double* @ptr_scc_r1(double* %call4, double* %call9, double* %call10)
|
||||
br label %return
|
||||
|
||||
if.end12: ; preds = %if.end
|
||||
%cmp13 = icmp eq double* %a, %b
|
||||
br i1 %cmp13, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %if.end12
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %if.end12
|
||||
%call14 = call double* @ptr_scc_r2(double* %a, double* %b, double* %r)
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi double* [ %r, %cond.true ], [ %call14, %cond.false ]
|
||||
br label %return
|
||||
|
||||
return: ; preds = %cond.end, %if.then3, %if.then
|
||||
%retval.0 = phi double* [ %call1, %if.then ], [ %call11, %if.then3 ], [ %cond, %cond.end ]
|
||||
ret double* %retval.0
|
||||
}
|
||||
|
||||
|
||||
; TEST a singleton SCC with a lot of recursive calls
|
||||
;
|
||||
; int* ret0(int *a) {
|
||||
; return *a ? a : ret0(ret0(ret0(...ret0(a)...)));
|
||||
; }
|
||||
;
|
||||
; FIXME: returned on %a missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readonly uwtable
|
||||
; CHECK: define i32* @ret0(i32* readonly %a)
|
||||
define i32* @ret0(i32* %a) #0 {
|
||||
entry:
|
||||
%v = load i32, i32* %a, align 4
|
||||
%tobool = icmp ne i32 %v, 0
|
||||
%call = call i32* @ret0(i32* %a)
|
||||
%call1 = call i32* @ret0(i32* %call)
|
||||
%call2 = call i32* @ret0(i32* %call1)
|
||||
%call3 = call i32* @ret0(i32* %call2)
|
||||
%call4 = call i32* @ret0(i32* %call3)
|
||||
%call5 = call i32* @ret0(i32* %call4)
|
||||
%call6 = call i32* @ret0(i32* %call5)
|
||||
%call7 = call i32* @ret0(i32* %call6)
|
||||
%call8 = call i32* @ret0(i32* %call7)
|
||||
%call9 = call i32* @ret0(i32* %call8)
|
||||
%call10 = call i32* @ret0(i32* %call9)
|
||||
%call11 = call i32* @ret0(i32* %call10)
|
||||
%call12 = call i32* @ret0(i32* %call11)
|
||||
%call13 = call i32* @ret0(i32* %call12)
|
||||
%call14 = call i32* @ret0(i32* %call13)
|
||||
%call15 = call i32* @ret0(i32* %call14)
|
||||
%call16 = call i32* @ret0(i32* %call15)
|
||||
%call17 = call i32* @ret0(i32* %call16)
|
||||
%sel = select i1 %tobool, i32* %a, i32* %call17
|
||||
ret i32* %sel
|
||||
}
|
||||
|
||||
|
||||
; TEST address taken function with call to an external functions
|
||||
;
|
||||
; void unknown_fn(void *);
|
||||
;
|
||||
; int* calls_unknown_fn(int *r) {
|
||||
; unknown_fn(&calls_unknown_fn);
|
||||
; return r;
|
||||
; }
|
||||
;
|
||||
; CHECK: Function Attrs: noinline nounwind uwtable
|
||||
; CHECK: declare void @unknown_fn(i32* (i32*)*)
|
||||
;
|
||||
; CHECK: Function Attrs: noinline nounwind uwtable
|
||||
; CHECK: define i32* @calls_unknown_fn(i32* readnone returned %r)
|
||||
declare void @unknown_fn(i32* (i32*)*) #0
|
||||
|
||||
define i32* @calls_unknown_fn(i32* %r) #0 {
|
||||
tail call void @unknown_fn(i32* (i32*)* nonnull @calls_unknown_fn)
|
||||
ret i32* %r
|
||||
}
|
||||
|
||||
|
||||
; TEST call to a function that might be redifined at link time
|
||||
;
|
||||
; int *maybe_redefined_fn(int *r) {
|
||||
; return r;
|
||||
; }
|
||||
;
|
||||
; int *calls_maybe_redefined_fn(int *r) {
|
||||
; maybe_redefined_fn(r);
|
||||
; return r;
|
||||
; }
|
||||
;
|
||||
; Verify the maybe-redefined function is not annotated:
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind uwtable
|
||||
; CHECK: define linkonce_odr i32* @maybe_redefined_fn(i32* %r)
|
||||
; FIXME: We should not derive norecurse for potentially redefined functions!
|
||||
; Function Attrs: noinline nounwind uwtable
|
||||
; define linkonce_odr i32* @maybe_redefined_fn(i32* %r)
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind uwtable
|
||||
; CHECK: define i32* @calls_maybe_redefined_fn(i32* returned %r)
|
||||
; FIXME: We should not derive norecurse for potentially redefined functions!
|
||||
; Function Attrs: noinline nounwind uwtable
|
||||
; define i32* @calls_maybe_redefined_fn(i32* returned %r)
|
||||
define linkonce_odr i32* @maybe_redefined_fn(i32* %r) #0 {
|
||||
entry:
|
||||
ret i32* %r
|
||||
}
|
||||
|
||||
define i32* @calls_maybe_redefined_fn(i32* %r) #0 {
|
||||
entry:
|
||||
%call = call i32* @maybe_redefined_fn(i32* %r)
|
||||
ret i32* %r
|
||||
}
|
||||
|
||||
|
||||
; TEST returned argument goes through select and phi
|
||||
;
|
||||
; double select_and_phi(double b) {
|
||||
; double x = b;
|
||||
; if (b > 0)
|
||||
; x = b;
|
||||
; return b == 0? b : x;
|
||||
; }
|
||||
;
|
||||
; FIXME: returned on %b missing:
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double @select_and_phi(double %b)
|
||||
define double @select_and_phi(double %b) #0 {
|
||||
entry:
|
||||
%cmp = fcmp ogt double %b, 0.000000e+00
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
br label %if.end
|
||||
|
||||
if.end: ; preds = %if.then, %entry
|
||||
%phi = phi double [ %b, %if.then ], [ %b, %entry ]
|
||||
%cmp1 = fcmp oeq double %b, 0.000000e+00
|
||||
%sel = select i1 %cmp1, double %b, double %phi
|
||||
ret double %sel
|
||||
}
|
||||
|
||||
|
||||
; TEST returned argument goes through recursion, select, and phi
|
||||
;
|
||||
; double recursion_select_and_phi(int a, double b) {
|
||||
; double x = b;
|
||||
; if (a-- > 0)
|
||||
; x = recursion_select_and_phi(a, b);
|
||||
; return b == 0? b : x;
|
||||
; }
|
||||
;
|
||||
; FIXME: returned on %b missing:
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define double @recursion_select_and_phi(i32 %a, double %b)
|
||||
define double @recursion_select_and_phi(i32 %a, double %b) #0 {
|
||||
entry:
|
||||
%dec = add nsw i32 %a, -1
|
||||
%cmp = icmp sgt i32 %a, 0
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%call = call double @recursion_select_and_phi(i32 %dec, double %b)
|
||||
br label %if.end
|
||||
|
||||
if.end: ; preds = %if.then, %entry
|
||||
%phi = phi double [ %call, %if.then ], [ %b, %entry ]
|
||||
%cmp1 = fcmp oeq double %b, 0.000000e+00
|
||||
%sel = select i1 %cmp1, double %b, double %phi
|
||||
ret double %sel
|
||||
}
|
||||
|
||||
|
||||
; TEST returned argument goes through bitcasts
|
||||
;
|
||||
; double* bitcast(int* b) {
|
||||
; return (double*)b;
|
||||
; }
|
||||
;
|
||||
; FIXME: returned on %b missing:
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @bitcast(i32* readnone %b)
|
||||
define double* @bitcast(i32* %b) #0 {
|
||||
entry:
|
||||
%bc0 = bitcast i32* %b to double*
|
||||
ret double* %bc0
|
||||
}
|
||||
|
||||
|
||||
; TEST returned argument goes through select and phi interleaved with bitcasts
|
||||
;
|
||||
; double* bitcasts_select_and_phi(int* b) {
|
||||
; double* x = b;
|
||||
; if (b == 0)
|
||||
; x = b;
|
||||
; return b != 0 ? b : x;
|
||||
; }
|
||||
;
|
||||
; FIXME: returned on %b missing:
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @bitcasts_select_and_phi(i32* readnone %b)
|
||||
define double* @bitcasts_select_and_phi(i32* %b) #0 {
|
||||
entry:
|
||||
%bc0 = bitcast i32* %b to double*
|
||||
%cmp = icmp eq double* %bc0, null
|
||||
br i1 %cmp, label %if.then, label %if.end
|
||||
|
||||
if.then: ; preds = %entry
|
||||
%bc1 = bitcast i32* %b to double*
|
||||
br label %if.end
|
||||
|
||||
if.end: ; preds = %if.then, %entry
|
||||
%phi = phi double* [ %bc1, %if.then ], [ %bc0, %entry ]
|
||||
%bc2 = bitcast double* %phi to i8*
|
||||
%bc3 = bitcast i32* %b to i8*
|
||||
%cmp2 = icmp ne double* %bc0, null
|
||||
%sel = select i1 %cmp2, i8* %bc2, i8* %bc3
|
||||
%bc4 = bitcast i8* %sel to double*
|
||||
ret double* %bc4
|
||||
}
|
||||
|
||||
|
||||
; TEST return argument or argument or undef
|
||||
;
|
||||
; double* ret_arg_arg_undef(int* b) {
|
||||
; if (b == 0)
|
||||
; return (double*)b;
|
||||
; if (b == 0)
|
||||
; return (double*)b;
|
||||
; /* return undef */
|
||||
; }
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @ret_arg_arg_undef(i32* readnone %b)
|
||||
define double* @ret_arg_arg_undef(i32* %b) #0 {
|
||||
entry:
|
||||
%bc0 = bitcast i32* %b to double*
|
||||
%cmp = icmp eq double* %bc0, null
|
||||
br i1 %cmp, label %ret_arg0, label %if.end
|
||||
|
||||
ret_arg0:
|
||||
%bc1 = bitcast i32* %b to double*
|
||||
ret double* %bc1
|
||||
|
||||
if.end:
|
||||
br i1 %cmp, label %ret_arg1, label %ret_undef
|
||||
|
||||
ret_arg1:
|
||||
ret double* %bc0
|
||||
|
||||
ret_undef:
|
||||
ret double *undef
|
||||
}
|
||||
|
||||
|
||||
; TEST return undef or argument or argument
|
||||
;
|
||||
; double* ret_undef_arg_arg(int* b) {
|
||||
; if (b == 0)
|
||||
; return (double*)b;
|
||||
; if (b == 0)
|
||||
; return (double*)b;
|
||||
; /* return undef */
|
||||
; }
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @ret_undef_arg_arg(i32* readnone %b)
|
||||
define double* @ret_undef_arg_arg(i32* %b) #0 {
|
||||
entry:
|
||||
%bc0 = bitcast i32* %b to double*
|
||||
%cmp = icmp eq double* %bc0, null
|
||||
br i1 %cmp, label %ret_undef, label %if.end
|
||||
|
||||
ret_undef:
|
||||
ret double *undef
|
||||
|
||||
if.end:
|
||||
br i1 %cmp, label %ret_arg0, label %ret_arg1
|
||||
|
||||
ret_arg0:
|
||||
ret double* %bc0
|
||||
|
||||
ret_arg1:
|
||||
%bc1 = bitcast i32* %b to double*
|
||||
ret double* %bc1
|
||||
}
|
||||
|
||||
|
||||
; TEST return undef or argument or undef
|
||||
;
|
||||
; double* ret_undef_arg_undef(int* b) {
|
||||
; if (b == 0)
|
||||
; /* return undef */
|
||||
; if (b == 0)
|
||||
; return (double*)b;
|
||||
; /* return undef */
|
||||
; }
|
||||
;
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define double* @ret_undef_arg_undef(i32* readnone %b)
|
||||
define double* @ret_undef_arg_undef(i32* %b) #0 {
|
||||
entry:
|
||||
%bc0 = bitcast i32* %b to double*
|
||||
%cmp = icmp eq double* %bc0, null
|
||||
br i1 %cmp, label %ret_undef0, label %if.end
|
||||
|
||||
ret_undef0:
|
||||
ret double *undef
|
||||
|
||||
if.end:
|
||||
br i1 %cmp, label %ret_arg, label %ret_undef1
|
||||
|
||||
ret_arg:
|
||||
ret double* %bc0
|
||||
|
||||
ret_undef1:
|
||||
ret double *undef
|
||||
}
|
||||
|
||||
; TEST return argument or unknown call result
|
||||
;
|
||||
; int* ret_arg_or_unknown(int* b) {
|
||||
; if (b == 0)
|
||||
; return b;
|
||||
; return unknown(b);
|
||||
; }
|
||||
;
|
||||
; Verify we do not assume b is returned>
|
||||
;
|
||||
; CHECK: define i32* @ret_arg_or_unknown(i32* %b)
|
||||
; CHECK: define i32* @ret_arg_or_unknown_through_phi(i32* %b)
|
||||
declare i32* @unknown(i32*)
|
||||
|
||||
define i32* @ret_arg_or_unknown(i32* %b) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32* %b, null
|
||||
br i1 %cmp, label %ret_arg, label %ret_unknown
|
||||
|
||||
ret_arg:
|
||||
ret i32* %b
|
||||
|
||||
ret_unknown:
|
||||
%call = call i32* @unknown(i32* %b)
|
||||
ret i32* %call
|
||||
}
|
||||
|
||||
define i32* @ret_arg_or_unknown_through_phi(i32* %b) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32* %b, null
|
||||
br i1 %cmp, label %ret_arg, label %ret_unknown
|
||||
|
||||
ret_arg:
|
||||
br label %r
|
||||
|
||||
ret_unknown:
|
||||
%call = call i32* @unknown(i32* %b)
|
||||
br label %r
|
||||
|
||||
r:
|
||||
%phi = phi i32* [ %b, %ret_arg ], [ %call, %ret_unknown ]
|
||||
ret i32* %phi
|
||||
}
|
||||
|
||||
attributes #0 = { noinline nounwind uwtable }
|
||||
|
||||
; CHECK-NOT: attributes #
|
||||
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind readnone uwtable }
|
||||
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readnone uwtable }
|
||||
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readonly uwtable }
|
||||
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable }
|
||||
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind uwtable }
|
||||
; CHECK-NOT: attributes #
|
||||
134
llvm/test/Transforms/FunctionAttrs/fn_noreturn.ll
Normal file
134
llvm/test/Transforms/FunctionAttrs/fn_noreturn.ll
Normal file
@@ -0,0 +1,134 @@
|
||||
; RUN: opt -functionattrs -S < %s | FileCheck %s
|
||||
;
|
||||
; Test cases specifically designed for the "no-return" function attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
;
|
||||
; TEST 1: singleton SCC void return type
|
||||
; TEST 2: singleton SCC int return type with a lot of recursive calls
|
||||
; TEST 3: endless loop, no return instruction
|
||||
; TEST 4: endless loop, dead return instruction
|
||||
; TEST 5: all paths contain a no-return function call
|
||||
;
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
|
||||
; TEST 1
|
||||
;
|
||||
; void srec0() {
|
||||
; return srec0();
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define void @srec0()
|
||||
;
|
||||
define void @srec0() #0 {
|
||||
entry:
|
||||
call void @srec0()
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
; TEST 2
|
||||
;
|
||||
; int srec16(int a) {
|
||||
; return srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(srec16(a))))))))))))))));
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define i32 @srec16(i32 %a)
|
||||
;
|
||||
define i32 @srec16(i32 %a) #0 {
|
||||
entry:
|
||||
%call = call i32 @srec16(i32 %a)
|
||||
%call1 = call i32 @srec16(i32 %call)
|
||||
%call2 = call i32 @srec16(i32 %call1)
|
||||
%call3 = call i32 @srec16(i32 %call2)
|
||||
%call4 = call i32 @srec16(i32 %call3)
|
||||
%call5 = call i32 @srec16(i32 %call4)
|
||||
%call6 = call i32 @srec16(i32 %call5)
|
||||
%call7 = call i32 @srec16(i32 %call6)
|
||||
%call8 = call i32 @srec16(i32 %call7)
|
||||
%call9 = call i32 @srec16(i32 %call8)
|
||||
%call10 = call i32 @srec16(i32 %call9)
|
||||
%call11 = call i32 @srec16(i32 %call10)
|
||||
%call12 = call i32 @srec16(i32 %call11)
|
||||
%call13 = call i32 @srec16(i32 %call12)
|
||||
%call14 = call i32 @srec16(i32 %call13)
|
||||
%call15 = call i32 @srec16(i32 %call14)
|
||||
ret i32 %call15
|
||||
}
|
||||
|
||||
|
||||
; TEST 3
|
||||
;
|
||||
; int endless_loop(int a) {
|
||||
; while (1);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define i32 @endless_loop(i32 %a)
|
||||
;
|
||||
define i32 @endless_loop(i32 %a) #0 {
|
||||
entry:
|
||||
br label %while.body
|
||||
|
||||
while.body: ; preds = %entry, %while.body
|
||||
br label %while.body
|
||||
}
|
||||
|
||||
|
||||
; TEST 4
|
||||
;
|
||||
; int endless_loop(int a) {
|
||||
; while (1);
|
||||
; return a;
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
|
||||
; CHECK: define i32 @dead_return(i32 returned %a)
|
||||
;
|
||||
define i32 @dead_return(i32 %a) #0 {
|
||||
entry:
|
||||
br label %while.body
|
||||
|
||||
while.body: ; preds = %entry, %while.body
|
||||
br label %while.body
|
||||
|
||||
return: ; No predecessors!
|
||||
ret i32 %a
|
||||
}
|
||||
|
||||
|
||||
; TEST 5
|
||||
;
|
||||
; int multiple_noreturn_calls(int a) {
|
||||
; return a == 0 ? endless_loop(a) : srec16(a);
|
||||
; }
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: Function Attrs: noinline nounwind readnone uwtable
|
||||
; CHECK: define i32 @multiple_noreturn_calls(i32 %a)
|
||||
;
|
||||
define i32 @multiple_noreturn_calls(i32 %a) #0 {
|
||||
entry:
|
||||
%cmp = icmp eq i32 %a, 0
|
||||
br i1 %cmp, label %cond.true, label %cond.false
|
||||
|
||||
cond.true: ; preds = %entry
|
||||
%call = call i32 @endless_loop(i32 %a)
|
||||
br label %cond.end
|
||||
|
||||
cond.false: ; preds = %entry
|
||||
%call1 = call i32 @srec16(i32 %a)
|
||||
br label %cond.end
|
||||
|
||||
cond.end: ; preds = %cond.false, %cond.true
|
||||
%cond = phi i32 [ %call, %cond.true ], [ %call1, %cond.false ]
|
||||
ret i32 %cond
|
||||
}
|
||||
|
||||
attributes #0 = { noinline nounwind uwtable }
|
||||
@@ -0,0 +1,164 @@
|
||||
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s
|
||||
;
|
||||
; This is an evolved example to stress test SCC parameter attribute propagation.
|
||||
; The SCC in this test is made up of the following six function, three of which
|
||||
; are internal and three externally visible:
|
||||
;
|
||||
; static int *internal_ret0_nw(int *n0, int *w0);
|
||||
; static int *internal_ret1_rw(int *r0, int *w0);
|
||||
; static int *internal_ret1_rrw(int *r0, int *r1, int *w0);
|
||||
; int *external_ret2_nrw(int *n0, int *r0, int *w0);
|
||||
; int *external_sink_ret2_nrw(int *n0, int *r0, int *w0);
|
||||
; int *external_source_ret2_nrw(int *n0, int *r0, int *w0);
|
||||
;
|
||||
; The top four functions call each other while the "sink" function will not
|
||||
; call anything and the "source" function will not be called in this module.
|
||||
; The names of the functions define the returned parameter (X for "_retX_"),
|
||||
; as well as how the parameters are (transitively) used (n = readnone,
|
||||
; r = readonly, w = writeonly).
|
||||
;
|
||||
; What we should see is something along the lines of:
|
||||
; 1 - Number of functions marked as norecurse
|
||||
; 6 - Number of functions marked argmemonly
|
||||
; 6 - Number of functions marked as nounwind
|
||||
; 16 - Number of arguments marked nocapture
|
||||
; 4 - Number of arguments marked readnone
|
||||
; 6 - Number of arguments marked writeonly
|
||||
; 6 - Number of arguments marked readonly
|
||||
; 6 - Number of arguments marked returned
|
||||
;
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
%call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0)
|
||||
%call1 = call i32* @internal_ret1_rrw(i32* %r0, i32* %r0, i32* %w0)
|
||||
%call2 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
%call3 = call i32* @internal_ret1_rw(i32* %r0, i32* %w0)
|
||||
ret i32* %call3
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0)
|
||||
define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) {
|
||||
entry:
|
||||
%r0 = alloca i32, align 4
|
||||
%r1 = alloca i32, align 4
|
||||
%tobool = icmp ne i32* %n0, null
|
||||
br i1 %tobool, label %if.end, label %if.then
|
||||
|
||||
if.then: ; preds = %entry
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
store i32 3, i32* %r0, align 4
|
||||
store i32 5, i32* %r1, align 4
|
||||
store i32 1, i32* %w0, align 4
|
||||
%call = call i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0)
|
||||
%call1 = call i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
%call2 = call i32* @external_ret2_nrw(i32* %n0, i32* %r1, i32* %w0)
|
||||
%call3 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
%call4 = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r1, i32* %w0)
|
||||
%call5 = call i32* @internal_ret0_nw(i32* %n0, i32* %w0)
|
||||
br label %return
|
||||
|
||||
return: ; preds = %if.end, %if.then
|
||||
%retval.0 = phi i32* [ %call5, %if.end ], [ %n0, %if.then ]
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0)
|
||||
define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) {
|
||||
entry:
|
||||
%0 = load i32, i32* %r0, align 4
|
||||
%tobool = icmp ne i32 %0, 0
|
||||
br i1 %tobool, label %if.end, label %if.then
|
||||
|
||||
if.then: ; preds = %entry
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%call = call i32* @internal_ret1_rw(i32* %r0, i32* %w0)
|
||||
%1 = load i32, i32* %r0, align 4
|
||||
%2 = load i32, i32* %r1, align 4
|
||||
%add = add nsw i32 %1, %2
|
||||
store i32 %add, i32* %w0, align 4
|
||||
%call1 = call i32* @internal_ret1_rw(i32* %r1, i32* %w0)
|
||||
%call2 = call i32* @internal_ret0_nw(i32* %r0, i32* %w0)
|
||||
%call3 = call i32* @internal_ret0_nw(i32* %w0, i32* %w0)
|
||||
%call4 = call i32* @external_ret2_nrw(i32* %r0, i32* %r1, i32* %w0)
|
||||
%call5 = call i32* @external_ret2_nrw(i32* %r1, i32* %r0, i32* %w0)
|
||||
%call6 = call i32* @external_sink_ret2_nrw(i32* %r0, i32* %r1, i32* %w0)
|
||||
%call7 = call i32* @external_sink_ret2_nrw(i32* %r1, i32* %r0, i32* %w0)
|
||||
%call8 = call i32* @internal_ret0_nw(i32* %r1, i32* %w0)
|
||||
br label %return
|
||||
|
||||
return: ; preds = %if.end, %if.then
|
||||
%retval.0 = phi i32* [ %call8, %if.end ], [ %r1, %if.then ]
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: norecurse nounwind
|
||||
; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0)
|
||||
define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
%tobool = icmp ne i32* %n0, null
|
||||
br i1 %tobool, label %if.end, label %if.then
|
||||
|
||||
if.then: ; preds = %entry
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%0 = load i32, i32* %r0, align 4
|
||||
store i32 %0, i32* %w0, align 4
|
||||
br label %return
|
||||
|
||||
return: ; preds = %if.end, %if.then
|
||||
ret i32* %w0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0)
|
||||
define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
%0 = load i32, i32* %r0, align 4
|
||||
%tobool = icmp ne i32 %0, 0
|
||||
br i1 %tobool, label %if.end, label %if.then
|
||||
|
||||
if.then: ; preds = %entry
|
||||
br label %return
|
||||
|
||||
if.end: ; preds = %entry
|
||||
%call = call i32* @internal_ret1_rrw(i32* %r0, i32* %r0, i32* %w0)
|
||||
%1 = load i32, i32* %r0, align 4
|
||||
store i32 %1, i32* %w0, align 4
|
||||
%call1 = call i32* @internal_ret0_nw(i32* %r0, i32* %w0)
|
||||
%call2 = call i32* @internal_ret0_nw(i32* %w0, i32* %w0)
|
||||
%call3 = call i32* @external_sink_ret2_nrw(i32* %r0, i32* %r0, i32* %w0)
|
||||
%call4 = call i32* @external_ret2_nrw(i32* %r0, i32* %r0, i32* %w0)
|
||||
br label %return
|
||||
|
||||
return: ; preds = %if.end, %if.then
|
||||
%retval.0 = phi i32* [ %call4, %if.end ], [ %w0, %if.then ]
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
%call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
%call1 = call i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
ret i32* %call1
|
||||
}
|
||||
|
||||
; Verify that we see only expected attribute sets, the above lines only check
|
||||
; for a subset relation.
|
||||
;
|
||||
; CHECK-NOT: attributes #
|
||||
; CHECK: attributes #{{.*}} = { nounwind }
|
||||
; CHECK: attributes #{{.*}} = { norecurse nounwind }
|
||||
; CHECK-NOT: attributes #
|
||||
@@ -1,10 +1,11 @@
|
||||
; 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, ...)
|
||||
|
||||
; NOTE: readonly for %y1_2 would be OK here but not for the similar situation in test13.
|
||||
;
|
||||
; 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)
|
||||
@@ -113,3 +114,31 @@ define i32 @volatile_load(i32* %p) {
|
||||
%load = load volatile i32, i32* %p
|
||||
ret i32 %load
|
||||
}
|
||||
|
||||
declare void @escape_readonly_ptr(i8** %addr, i8* readnone %ptr)
|
||||
declare void @escape_readnone_ptr(i8** %addr, i8* readonly %ptr)
|
||||
|
||||
; The argument pointer %escaped_then_written cannot be marked readnone/only even
|
||||
; though the only direct use, in @escape_readnone_ptr/@escape_readonly_ptr,
|
||||
; is marked as readnone/only. However, the functions can write the pointer into
|
||||
; %addr, causing the store to write to %escaped_then_written.
|
||||
;
|
||||
; FIXME: This test currently exposes a bug!
|
||||
;
|
||||
; BUG: define void @unsound_readnone(i8* %ignored, i8* readnone %escaped_then_written)
|
||||
; BUG: define void @unsound_readonly(i8* %ignored, i8* readonly %escaped_then_written)
|
||||
define void @unsound_readnone(i8* %ignored, i8* %escaped_then_written) {
|
||||
%addr = alloca i8*
|
||||
call void @escape_readnone_ptr(i8** %addr, i8* %escaped_then_written)
|
||||
%addr.ld = load i8*, i8** %addr
|
||||
store i8 0, i8* %addr.ld
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @unsound_readonly(i8* %ignored, i8* %escaped_then_written) {
|
||||
%addr = alloca i8*
|
||||
call void @escape_readonly_ptr(i8** %addr, i8* %escaped_then_written)
|
||||
%addr.ld = load i8*, i8** %addr
|
||||
store i8 0, i8* %addr.ld
|
||||
ret void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user