diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp index 375385aca7a39..d890ae83d1949 100644 --- a/llvm/lib/Analysis/ValueTracking.cpp +++ b/llvm/lib/Analysis/ValueTracking.cpp @@ -3098,6 +3098,13 @@ bool isKnownNonZero(const Value *V, const APInt &DemandedElts, // Check for pointer simplifications. if (PointerType *PtrTy = dyn_cast(Ty)) { + const DataLayout &DL = Q.DL; + if (DL.isSentinelValueDefined()) { + unsigned AddrSpace = PtrTy->getPointerAddressSpace(); + + return (DL.getSentinelPointerValue(AddrSpace) != 0); + } + // A byval, inalloca may not be null in a non-default addres space. A // nonnull argument is assumed never 0. if (const Argument *A = dyn_cast(V)) { diff --git a/llvm/test/Transforms/Attributor/IPConstantProp/pthreads-dl-sentinel.ll b/llvm/test/Transforms/Attributor/IPConstantProp/pthreads-dl-sentinel.ll new file mode 100755 index 0000000000000..da05e40e09f5d --- /dev/null +++ b/llvm/test/Transforms/Attributor/IPConstantProp/pthreads-dl-sentinel.ll @@ -0,0 +1,151 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals +; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC +; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -data-layout=z0:1-z2:neg1-z3:neg1-z5:neg1 -S < %s | FileCheck %s --check-prefixes=CHECK-DL,CGSCC-DL +; +; #include +; +; ptr GlobalVPtr; +; +; static ptr foo(ptr arg) { return arg; } +; static ptr bar(ptr arg) { return arg; } +; +; int main() { +; pthread_t thread; +; pthread_create(&thread, NULL, foo, NULL); +; pthread_create(&thread, NULL, bar, &GlobalVPtr); +; return 0; +; } +; +; Verify the constant values NULL and &GlobalVPtr are propagated into foo and +; bar, respectively. +; + +%union.pthread_attr_t = type { i64, [48 x i8] } + +@GlobalVPtr = common dso_local global ptr null, align 8 + +; FIXME: nocapture & noalias for @GlobalVPtr in %call1 +; FIXME: nocapture & noalias for %alloc2 in %call3 + +;. +; CHECK: @GlobalVPtr = common dso_local global ptr null, align 8 +;. +; CHECK-DL: @GlobalVPtr = common dso_local global ptr null, align 8 +;. +define dso_local i32 @main() { +; CHECK-LABEL: define {{[^@]+}}@main() { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[ALLOC1:%.*]] = alloca i8, align 8 +; CHECK-NEXT: [[ALLOC2:%.*]] = alloca i8, align 8 +; CHECK-NEXT: [[THREAD:%.*]] = alloca i64, align 8 +; CHECK-NEXT: [[CALL:%.*]] = call i32 @pthread_create(ptr noundef nonnull align 8 dereferenceable(8) [[THREAD]], ptr noundef align 4294967296 null, ptr noundef nonnull @foo, ptr nofree noundef readnone align 4294967296 null) +; CHECK-NEXT: [[CALL1:%.*]] = call i32 @pthread_create(ptr noundef nonnull align 8 dereferenceable(8) [[THREAD]], ptr noundef align 4294967296 null, ptr noundef nonnull @bar, ptr noalias nocapture nofree noundef nonnull readnone align 8 dereferenceable(8) @GlobalVPtr) +; CHECK-NEXT: [[CALL2:%.*]] = call i32 @pthread_create(ptr noundef nonnull align 8 dereferenceable(8) [[THREAD]], ptr noundef align 4294967296 null, ptr noundef nonnull @baz, ptr noalias nocapture nofree noundef nonnull readnone align 8 dereferenceable(1) [[ALLOC1]]) +; CHECK-NEXT: [[CALL3:%.*]] = call i32 @pthread_create(ptr noundef nonnull align 8 dereferenceable(8) [[THREAD]], ptr noundef align 4294967296 null, ptr noundef nonnull @buz, ptr noalias nofree noundef nonnull readnone align 8 dereferenceable(1) [[ALLOC2]]) +; CHECK-NEXT: ret i32 0 +; +; CHECK-DL-LABEL: define {{[^@]+}}@main() { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: [[ALLOC1:%.*]] = alloca i8, align 8 +; CHECK-DL-NEXT: [[ALLOC2:%.*]] = alloca i8, align 8 +; CHECK-DL-NEXT: [[THREAD:%.*]] = alloca i64, align 8 +; CHECK-DL-NEXT: unreachable +; +entry: + %alloc1 = alloca i8, align 8 + %alloc2 = alloca i8, align 8 + %thread = alloca i64, align 8 + %call = call i32 @pthread_create(ptr nonnull %thread, ptr null, ptr nonnull @foo, ptr null) + %call1 = call i32 @pthread_create(ptr nonnull %thread, ptr null, ptr nonnull @bar, ptr @GlobalVPtr) + %call2 = call i32 @pthread_create(ptr nonnull %thread, ptr null, ptr nonnull @baz, ptr nocapture %alloc1) + %call3 = call i32 @pthread_create(ptr nonnull %thread, ptr null, ptr nonnull @buz, ptr %alloc2) + ret i32 0 +} + +declare !callback !0 dso_local i32 @pthread_create(ptr, ptr, ptr, ptr) + +define internal ptr @foo(ptr %arg) { +; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@foo +; CHECK-SAME: (ptr noalias nocapture nofree readnone align 4294967296 [[ARG:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: ret ptr null +; +; CHECK-DL: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define {{[^@]+}}@foo +; CHECK-DL-SAME: (ptr noalias nocapture nofree nonnull readnone align 4294967296 [[ARG:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: unreachable +; +entry: + ret ptr %arg +} + + +define internal ptr @bar(ptr %arg) { +; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@bar +; CHECK-SAME: (ptr noalias nocapture nofree nonnull readnone align 8 dereferenceable(8) [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: ret ptr @GlobalVPtr +; +; CHECK-DL: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define {{[^@]+}}@bar +; CHECK-DL-SAME: (ptr noalias nocapture nofree nonnull readnone align 8 dereferenceable(8) [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: ret ptr @GlobalVPtr +; +entry: + ret ptr %arg +} + +define internal ptr @baz(ptr %arg) { +; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@baz +; CHECK-SAME: (ptr noalias nofree noundef nonnull readnone returned align 8 dereferenceable(1) "no-capture-maybe-returned" [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: ret ptr [[ARG]] +; +; CHECK-DL: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define {{[^@]+}}@baz +; CHECK-DL-SAME: (ptr noalias nofree noundef nonnull readnone returned align 8 dereferenceable(1) "no-capture-maybe-returned" [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: ret ptr [[ARG]] +; +entry: + ret ptr %arg +} + +define internal ptr @buz(ptr %arg) { +; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-LABEL: define {{[^@]+}}@buz +; CHECK-SAME: (ptr noalias nofree noundef nonnull readnone returned align 8 dereferenceable(1) "no-capture-maybe-returned" [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: ret ptr [[ARG]] +; +; CHECK-DL: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define {{[^@]+}}@buz +; CHECK-DL-SAME: (ptr noalias nofree noundef nonnull readnone returned align 8 dereferenceable(1) "no-capture-maybe-returned" [[ARG:%.*]]) #[[ATTR0]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: ret ptr [[ARG]] +; +entry: + ret ptr %arg +} + +!1 = !{i64 2, i64 3, i1 false} +!0 = !{!1} +;. +; CHECK: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +;. +; CHECK-DL: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) } +;. +; CHECK: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]} +; CHECK: [[META1]] = !{i64 2, i64 3, i1 false} +;. +; CHECK-DL: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]} +; CHECK-DL: [[META1]] = !{i64 2, i64 3, i1 false} +;. +;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line: +; CGSCC: {{.*}} +; CGSCC-DL: {{.*}} diff --git a/llvm/test/Transforms/Attributor/nocapture-2-dl-sentinel.ll b/llvm/test/Transforms/Attributor/nocapture-2-dl-sentinel.ll new file mode 100755 index 0000000000000..1775681ec5f48 --- /dev/null +++ b/llvm/test/Transforms/Attributor/nocapture-2-dl-sentinel.ll @@ -0,0 +1,376 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals --version 2 +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-annotate-decl-cs -data-layout=z0:1-z2:neg1-z3:neg1-z5:neg1 -S < %s | FileCheck %s --check-prefixes=CHECK-DL +; +; Test cases specifically designed for the "no-capture" argument attribute. +; We use FIXME's to indicate problems and missing attributes. +; + +declare ptr @unknown() + +; TEST comparison against NULL +; +; int is_null_return(int *p) { +; return p == 0; +; } +; +; no-capture is missing on %p because it is not dereferenceable +define i32 @is_null_return(ptr %p) #0 { +; CHECK: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable +; CHECK-LABEL: define i32 @is_null_return +; CHECK-SAME: (ptr nofree readnone [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[P]], null +; CHECK-NEXT: [[CONV:%.*]] = zext i1 [[CMP]] to i32 +; CHECK-NEXT: ret i32 [[CONV]] +; +; CHECK-DL: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable +; CHECK-DL-LABEL: define noundef i32 @is_null_return +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: ret i32 0 +; +entry: + %cmp = icmp eq ptr %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; +; } +; +; no-capture is missing on %p because it is not dereferenceable +define i32 @is_null_control(ptr %p) #0 { +; CHECK: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable +; CHECK-LABEL: define i32 @is_null_control +; CHECK-SAME: (ptr nofree [[P:%.*]]) #[[ATTR0]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[RETVAL:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[P]], null +; CHECK-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +; CHECK: if.then: +; CHECK-NEXT: store i32 1, ptr [[RETVAL]], align 4 +; CHECK-NEXT: br label [[RETURN:%.*]] +; CHECK: if.end: +; CHECK-NEXT: br label [[IF_END3:%.*]] +; CHECK: if.then2: +; CHECK-NEXT: unreachable +; CHECK: if.end3: +; CHECK-NEXT: store i32 0, ptr [[RETVAL]], align 4 +; CHECK-NEXT: br label [[RETURN]] +; CHECK: return: +; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[RETVAL]], align 4 +; CHECK-NEXT: ret i32 [[TMP0]] +; +; CHECK-DL: Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable +; CHECK-DL-LABEL: define i32 @is_null_control +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone [[P:%.*]]) #[[ATTR0]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: [[RETVAL:%.*]] = alloca i32, align 4 +; CHECK-DL-NEXT: br label [[IF_END:%.*]] +; CHECK-DL: if.then: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: if.end: +; CHECK-DL-NEXT: br label [[IF_END3:%.*]] +; CHECK-DL: if.then2: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: if.end3: +; CHECK-DL-NEXT: br label [[RETURN:%.*]] +; CHECK-DL: return: +; CHECK-DL-NEXT: ret i32 0 +; +entry: + %retval = alloca i32, align 4 + %cmp = icmp eq ptr %p, null + br i1 %cmp, label %if.then, label %if.end + +if.then: ; preds = %entry + store i32 1, ptr %retval, align 4 + br label %return + +if.end: ; preds = %entry + %cmp1 = icmp eq ptr null, %p + br i1 %cmp1, label %if.then2, label %if.end3 + +if.then2: ; preds = %if.end + store i32 1, ptr %retval, align 4 + br label %return + +if.end3: ; preds = %if.end + store i32 0, ptr %retval, align 4 + br label %return + +return: ; preds = %if.end3, %if.then2, %if.then + %0 = load i32, ptr %retval, align 4 + ret i32 %0 +} + +; TEST SCC with various calls, casts, and comparisons agains NULL +; +; 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_A(a) ? scc_B((double*)a) : scc_C(a))); +; } +define ptr @scc_A(ptr dereferenceable_or_null(4) %a) { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define noundef dereferenceable_or_null(4) ptr @scc_A +; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null +; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]] +; CHECK: cond.true: +; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_C(ptr noalias nofree noundef nonnull readnone dereferenceable(4) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: br label [[COND_END:%.*]] +; CHECK: cond.false: +; CHECK-NEXT: br label [[COND_END]] +; CHECK: cond.end: +; CHECK-NEXT: [[COND:%.*]] = phi ptr [ [[A]], [[COND_TRUE]] ], [ [[A]], [[COND_FALSE]] ] +; CHECK-NEXT: ret ptr [[A]] +; +; CHECK-DL: Function Attrs: mustprogress nofree noreturn nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define noalias nonnull align 4294967296 dereferenceable(4294967295) ptr @scc_A +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[A:%.*]]) #[[ATTR1:[0-9]+]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: br label [[COND_TRUE:%.*]] +; CHECK-DL: cond.true: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.false: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.end: +; CHECK-DL-NEXT: unreachable +; +entry: + %tobool = icmp ne ptr %a, null + br i1 %tobool, label %cond.true, label %cond.false + +cond.true: ; preds = %entry + %call = call ptr @scc_C(ptr %a) + %call1 = call ptr @scc_B(ptr %call) + %call2 = call ptr @scc_A(ptr %call1) + br label %cond.end + +cond.false: ; preds = %entry + br label %cond.end + +cond.end: ; preds = %cond.false, %cond.true + %cond = phi ptr [ %call2, %cond.true ], [ %a, %cond.false ] + ret ptr %cond +} + +; FIXME: the call1 below to scc_B should return dereferenceable_or_null(8) (as the callee does). Something prevented that deduction and needs to be investigated. +define ptr @scc_B(ptr dereferenceable_or_null(8) %a) { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define noundef dereferenceable_or_null(8) ptr @scc_B +; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(8) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null +; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]] +; CHECK: cond.true: +; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_A(ptr noalias nofree noundef nonnull readnone dereferenceable(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: br label [[COND_END:%.*]] +; CHECK: cond.false: +; CHECK-NEXT: br label [[COND_END]] +; CHECK: cond.end: +; CHECK-NEXT: [[COND:%.*]] = phi ptr [ [[A]], [[COND_TRUE]] ], [ [[A]], [[COND_FALSE]] ] +; CHECK-NEXT: ret ptr [[A]] +; +; CHECK-DL: Function Attrs: mustprogress nofree noreturn nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define noalias nonnull align 4294967296 dereferenceable(4294967295) ptr @scc_B +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(8) [[A:%.*]]) #[[ATTR1]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: br label [[COND_TRUE:%.*]] +; CHECK-DL: cond.true: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.false: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.end: +; CHECK-DL-NEXT: unreachable +; +entry: + %tobool = icmp ne ptr %a, null + br i1 %tobool, label %cond.true, label %cond.false + +cond.true: ; preds = %entry + %call = call ptr @scc_A(ptr %a) + %call1 = call ptr @scc_B(ptr %call) + %call2 = call ptr @scc_C(ptr %call1) + br label %cond.end + +cond.false: ; preds = %entry + br label %cond.end + +cond.end: ; preds = %cond.false, %cond.true + %cond = phi ptr [ %call2, %cond.true ], [ %a, %cond.false ] + ret ptr %cond +} + +define ptr @scc_C(ptr dereferenceable_or_null(2) %a) { +; CHECK: Function Attrs: nofree nosync nounwind memory(none) +; CHECK-LABEL: define noundef dereferenceable_or_null(4) ptr @scc_C +; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null +; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]] +; CHECK: cond.true: +; CHECK-NEXT: [[CALL1:%.*]] = call ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: br label [[COND_END:%.*]] +; CHECK: cond.false: +; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: br label [[COND_END]] +; CHECK: cond.end: +; CHECK-NEXT: [[COND:%.*]] = phi ptr [ [[A]], [[COND_TRUE]] ], [ [[A]], [[COND_FALSE]] ] +; CHECK-NEXT: [[CALL3:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR1]] +; CHECK-NEXT: ret ptr [[A]] +; +; CHECK-DL: Function Attrs: mustprogress nofree noreturn nosync nounwind willreturn memory(none) +; CHECK-DL-LABEL: define noalias nonnull align 4294967296 dereferenceable(4294967295) ptr @scc_C +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[A:%.*]]) #[[ATTR1]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.true: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.false: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: cond.end: +; CHECK-DL-NEXT: unreachable +; +entry: + %call = call ptr @scc_A(ptr %a) + %tobool = icmp ne ptr %call, null + br i1 %tobool, label %cond.true, label %cond.false + +cond.true: ; preds = %entry + %call1 = call ptr @scc_B(ptr %a) + br label %cond.end + +cond.false: ; preds = %entry + %call2 = call ptr @scc_C(ptr %a) + br label %cond.end + +cond.end: ; preds = %cond.false, %cond.true + %cond = phi ptr [ %call1, %cond.true ], [ %call2, %cond.false ] + %call3 = call ptr @scc_A(ptr %cond) + ret ptr %call3 +} + +; 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. +; + +define ptr @ret_arg_or_unknown(ptr %b) #0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define ptr @ret_arg_or_unknown +; CHECK-SAME: (ptr [[B:%.*]]) #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[B]], null +; CHECK-NEXT: br i1 [[CMP]], label [[RET_ARG:%.*]], label [[RET_UNKNOWN:%.*]] +; CHECK: ret_arg: +; CHECK-NEXT: ret ptr [[B]] +; CHECK: ret_unknown: +; CHECK-NEXT: [[CALL:%.*]] = call ptr @unknown() +; CHECK-NEXT: ret ptr [[CALL]] +; +; CHECK-DL: Function Attrs: noinline nounwind uwtable +; CHECK-DL-LABEL: define nonnull ptr @ret_arg_or_unknown +; CHECK-DL-SAME: (ptr nofree nonnull readnone "no-capture-maybe-returned" [[B:%.*]]) #[[ATTR2:[0-9]+]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: br label [[RET_UNKNOWN:%.*]] +; CHECK-DL: ret_arg: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: ret_unknown: +; CHECK-DL-NEXT: [[CALL:%.*]] = call nonnull ptr @unknown() +; CHECK-DL-NEXT: ret ptr [[CALL]] +; +entry: + %cmp = icmp eq ptr %b, null + br i1 %cmp, label %ret_arg, label %ret_unknown + +ret_arg: + ret ptr %b + +ret_unknown: + %call = call ptr @unknown() + ret ptr %call +} + +define ptr @ret_arg_or_unknown_through_phi(ptr %b) #0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define ptr @ret_arg_or_unknown_through_phi +; CHECK-SAME: (ptr [[B:%.*]]) #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[B]], null +; CHECK-NEXT: br i1 [[CMP]], label [[RET_ARG:%.*]], label [[RET_UNKNOWN:%.*]] +; CHECK: ret_arg: +; CHECK-NEXT: br label [[R:%.*]] +; CHECK: ret_unknown: +; CHECK-NEXT: [[CALL:%.*]] = call ptr @unknown() +; CHECK-NEXT: br label [[R]] +; CHECK: r: +; CHECK-NEXT: [[PHI:%.*]] = phi ptr [ [[B]], [[RET_ARG]] ], [ [[CALL]], [[RET_UNKNOWN]] ] +; CHECK-NEXT: ret ptr [[PHI]] +; +; CHECK-DL: Function Attrs: noinline nounwind uwtable +; CHECK-DL-LABEL: define nonnull ptr @ret_arg_or_unknown_through_phi +; CHECK-DL-SAME: (ptr nocapture nofree nonnull readnone [[B:%.*]]) #[[ATTR2]] { +; CHECK-DL-NEXT: entry: +; CHECK-DL-NEXT: br label [[RET_UNKNOWN:%.*]] +; CHECK-DL: ret_arg: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: ret_unknown: +; CHECK-DL-NEXT: [[CALL:%.*]] = call nonnull ptr @unknown() +; CHECK-DL-NEXT: br label [[R:%.*]] +; CHECK-DL: r: +; CHECK-DL-NEXT: ret ptr [[CALL]] +; +entry: + %cmp = icmp eq ptr %b, null + br i1 %cmp, label %ret_arg, label %ret_unknown + +ret_arg: + br label %r + +ret_unknown: + %call = call ptr @unknown() + br label %r + +r: + %phi = phi ptr [ %b, %ret_arg ], [ %call, %ret_unknown ] + ret ptr %phi +} + +attributes #0 = { noinline nounwind uwtable } +;. +; CHECK: attributes #[[ATTR0]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable } +; CHECK: attributes #[[ATTR1]] = { nofree nosync nounwind memory(none) } +; CHECK: attributes #[[ATTR2]] = { noinline nounwind uwtable } +;. +; CHECK-DL: attributes #[[ATTR0]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable } +; CHECK-DL: attributes #[[ATTR1]] = { mustprogress nofree noreturn nosync nounwind willreturn memory(none) } +; CHECK-DL: attributes #[[ATTR2]] = { noinline nounwind uwtable } +;. diff --git a/llvm/test/Transforms/Attributor/nofree-dl-sentinel.ll b/llvm/test/Transforms/Attributor/nofree-dl-sentinel.ll new file mode 100755 index 0000000000000..d26b64cb9cfcc --- /dev/null +++ b/llvm/test/Transforms/Attributor/nofree-dl-sentinel.ll @@ -0,0 +1,140 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-annotate-decl-cs -data-layout=z0:1-z2:neg1-z3:neg1-z5:neg1 -S < %s | FileCheck %s --check-prefixes=CHECK-DL + +; Test cases specifically designed for the "nofree" function attribute. +; We use FIXME's to indicate problems and missing attributes. + +; Free functions +declare void @free(ptr nocapture) local_unnamed_addr #1 +declare noalias ptr @realloc(ptr nocapture, i64) local_unnamed_addr #0 +declare void @_ZdaPv(ptr) local_unnamed_addr #2 + +; TEST 3 (negative case) +; Free occurs in same scc. +; void free_in_scc1(char*p){ +; free_in_scc2(p); +; } +; void free_in_scc2(char*p){ +; free_in_scc1(p); +; free(p); +; } + + +define void @free_in_scc1(ptr nocapture %0) local_unnamed_addr #0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define {{[^@]+}}@free_in_scc1 +; CHECK-SAME: (ptr nocapture [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: tail call void @free_in_scc2(ptr nocapture [[TMP0]]) #[[ATTR0:[0-9]+]] +; CHECK-NEXT: ret void +; +; CHECK-DL: Function Attrs: noinline nounwind uwtable +; CHECK-DL-LABEL: define {{[^@]+}}@free_in_scc1 +; CHECK-DL-SAME: (ptr nocapture nonnull [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { +; CHECK-DL-NEXT: tail call void @free_in_scc2(ptr nocapture nonnull [[TMP0]]) #[[ATTR0:[0-9]+]] +; CHECK-DL-NEXT: ret void +; + tail call void @free_in_scc2(ptr %0) #1 + ret void +} +define void @free_in_scc2(ptr nocapture %0) local_unnamed_addr #0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define {{[^@]+}}@free_in_scc2 +; CHECK-SAME: (ptr nocapture [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[TMP0]], null +; CHECK-NEXT: br i1 [[CMP]], label [[REC:%.*]], label [[CALL:%.*]] +; CHECK: call: +; CHECK-NEXT: tail call void @free(ptr nocapture nonnull [[TMP0]]) #[[ATTR0]] +; CHECK-NEXT: br label [[END:%.*]] +; CHECK: rec: +; CHECK-NEXT: tail call void @free_in_scc1(ptr nocapture [[TMP0]]) #[[ATTR0]] +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: ret void +; +; CHECK-DL: Function Attrs: noinline nounwind uwtable +; CHECK-DL-LABEL: define {{[^@]+}}@free_in_scc2 +; CHECK-DL-SAME: (ptr nocapture nonnull [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-DL-NEXT: br label [[CALL:%.*]] +; CHECK-DL: call: +; CHECK-DL-NEXT: tail call void @free(ptr nocapture nonnull [[TMP0]]) #[[ATTR0]] +; CHECK-DL-NEXT: br label [[END:%.*]] +; CHECK-DL: rec: +; CHECK-DL-NEXT: unreachable +; CHECK-DL: end: +; CHECK-DL-NEXT: ret void +; + %cmp = icmp eq ptr %0, null + br i1 %cmp, label %rec, label %call +call: + tail call void @free(ptr %0) #1 + br label %end +rec: + tail call void @free_in_scc1(ptr %0) + br label %end +end: + ret void +} + +; TEST 5 +; C++ delete operation (negative case) +; void delete_op (char p[]){ +; delete [] p; +; } + +define void @_Z9delete_opPc(ptr %0) local_unnamed_addr #0 { +; CHECK: Function Attrs: noinline nounwind uwtable +; CHECK-LABEL: define {{[^@]+}}@_Z9delete_opPc +; CHECK-SAME: (ptr [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-NEXT: [[TMP2:%.*]] = icmp eq ptr [[TMP0]], null +; CHECK-NEXT: br i1 [[TMP2]], label [[TMP4:%.*]], label [[TMP3:%.*]] +; CHECK: 3: +; CHECK-NEXT: tail call void @_ZdaPv(ptr nonnull [[TMP0]]) #[[ATTR2:[0-9]+]] +; CHECK-NEXT: br label [[TMP4]] +; CHECK: 4: +; CHECK-NEXT: ret void +; +; CHECK-DL: Function Attrs: noinline nounwind uwtable +; CHECK-DL-LABEL: define {{[^@]+}}@_Z9delete_opPc +; CHECK-DL-SAME: (ptr nonnull [[TMP0:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-DL-NEXT: br label [[TMP2:%.*]] +; CHECK-DL: 2: +; CHECK-DL-NEXT: tail call void @_ZdaPv(ptr nonnull [[TMP0]]) #[[ATTR2:[0-9]+]] +; CHECK-DL-NEXT: br label [[TMP3:%.*]] +; CHECK-DL: 3: +; CHECK-DL-NEXT: ret void +; + %2 = icmp eq ptr %0, null + br i1 %2, label %4, label %3 + +;