Index: llvm/include/llvm/Analysis/ValueTracking.h =================================================================== --- llvm/include/llvm/Analysis/ValueTracking.h +++ llvm/include/llvm/Analysis/ValueTracking.h @@ -242,21 +242,21 @@ /// This is a wrapper around Value::stripAndAccumulateConstantOffsets that /// creates and later unpacks the required APInt. inline Value *GetPointerBaseWithConstantOffset(Value *Ptr, int64_t &Offset, - const DataLayout &DL) { + const DataLayout &DL, + bool AllowNonInbounds = true) { APInt OffsetAPInt(DL.getIndexTypeSizeInBits(Ptr->getType()), 0); Value *Base = - Ptr->stripAndAccumulateConstantOffsets(DL, OffsetAPInt, - /* AllowNonInbounds */ true); + Ptr->stripAndAccumulateConstantOffsets(DL, OffsetAPInt, AllowNonInbounds); Offset = OffsetAPInt.getSExtValue(); return Base; } - inline const Value *GetPointerBaseWithConstantOffset(const Value *Ptr, - int64_t &Offset, - const DataLayout &DL) { - return GetPointerBaseWithConstantOffset(const_cast(Ptr), Offset, - DL); + inline const Value * + GetPointerBaseWithConstantOffset(const Value *Ptr, int64_t &Offset, + const DataLayout &DL, + bool AllowNonInbounds = true) { + return GetPointerBaseWithConstantOffset(const_cast(Ptr), Offset, DL, + AllowNonInbounds); } - /// Returns true if the GEP is based on a pointer to a string (array of // \p CharSize integers) and is indexing into this string. bool isGEPBasedOnPointerToString(const GEPOperator *GEP, Index: llvm/lib/Transforms/IPO/Attributor.cpp =================================================================== --- llvm/lib/Transforms/IPO/Attributor.cpp +++ llvm/lib/Transforms/IPO/Attributor.cpp @@ -24,6 +24,7 @@ #include "llvm/Analysis/CaptureTracking.h" #include "llvm/Analysis/GlobalsModRef.h" #include "llvm/Analysis/Loads.h" +#include "llvm/Analysis/MustExecute.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" @@ -435,6 +436,36 @@ return -1; } +// Helper function that returns pointer operand of memory instruction. +const Value *getPointerOperand(const Instruction *I) { + if (auto *LI = dyn_cast(I)) + return LI->getPointerOperand(); + + if (auto *SI = dyn_cast(I)) + return SI->getPointerOperand(); + + if (auto *CXI = dyn_cast(I)) + return CXI->getPointerOperand(); + + if (auto *RMWI = dyn_cast(I)) + return RMWI->getPointerOperand(); + + return nullptr; +} + +// Helper function that returns base pointer of memory instruction operand. +// Only inbounds GEPs are traced. +const Value *getBasePointerOfPointerOperand(const Instruction *I, + int64_t &BytesOffset, + const DataLayout &DL) { + const Value *Ptr = getPointerOperand(I); + if (!Ptr) + return nullptr; + + return GetPointerBaseWithConstantOffset(Ptr, BytesOffset, DL, + /*AllowNonInbounds*/ false); +} + /// -----------------------NoUnwind Function Attribute-------------------------- struct AANoUnwindFunction : AANoUnwind, BooleanState { @@ -1221,6 +1252,35 @@ unsigned ArgNo = Arg.getArgNo(); + MustBeExecutedContextExplorer Explorer(/* ExploreInterBlock */ true); + + // Information gave from instructions which must be executed from entry. + for (const Instruction *I : + Explorer.range(&getAnchorScope().getEntryBlock().front())) { + + // See callsite abstract attribute. + if (ImmutableCallSite ICS = ImmutableCallSite(I)) + for (unsigned int i = 0; i < ICS.getNumArgOperands(); i++) + if (ICS.getArgOperand(i) == &getAnchoredValue()) + if (auto *NonNullAA = A.getAAFor(*this, *I, i)) + // FIXME: Use assumption. + if (NonNullAA->isKnownNonNull()) { + indicateOptimisticFixpoint(); + return ChangeStatus::CHANGED; + } + + // See memory instruction with constant offset. + // Currently only inbounds GEPs are tracked. + // TODO: Trace non-inbounds GEPs. + int64_t Offset = 0; + if (const Value *Base = getBasePointerOfPointerOperand( + I, Offset, getAnchorScope().getParent()->getDataLayout())) + if (Base == &getAnchoredValue()) { + indicateOptimisticFixpoint(); + return ChangeStatus::CHANGED; + } + } + // Callback function std::function CallSiteCheck = [&](CallSite CS) { assert(CS && "Sanity check: Call site was not initialized properly!"); @@ -1689,6 +1749,11 @@ NonNullGlobalState.removeAssumedBits(DEREF_GLOBAL); } + /// Make nonnull knonwn. + void addKnownNonnull() { NonNullGlobalState.addKnownBits(DEREF_NONNULL); } + /// Make global knonwn. + void addKnownGlobal() { NonNullGlobalState.addKnownBits(DEREF_GLOBAL); } + /// Equality for DerefState. bool operator==(const DerefState &R) { return this->DerefBytesState == R.DerefBytesState && @@ -1891,6 +1956,8 @@ ChangeStatus AADereferenceableArgument::updateImpl(Attributor &A) { Function &F = getAnchorScope(); + const DataLayout &DL = F.getParent()->getDataLayout(); + Argument &Arg = cast(getAnchoredValue()); auto BeforeState = static_cast(*this); @@ -1902,6 +1969,33 @@ bool IsNonNull = isAssumedNonNull(); bool IsGlobal = isAssumedGlobal(); + MustBeExecutedContextExplorer Explorer(/* ExploreInterBlock */ true); + + // Information gave from instructions which must be executed from entry. + for (const Instruction *I : + Explorer.range(&getAnchorScope().getEntryBlock().front())) { + + // See callsite abstract attribute. + if (ImmutableCallSite ICS = ImmutableCallSite(I)) + for (unsigned int i = 0; i < ICS.getNumArgOperands(); i++) + if (ICS.getArgOperand(i) == &getAnchoredValue()) + if (auto *DerefAA = A.getAAFor(*this, *I, i)) + // FIXME: Use assumption. + takeKnownDerefBytesMaximum(DerefAA->getKnownDereferenceableBytes()); + + // See memory instruction with constant offset. + // Currently only inbounds GEPs are tracked. + // TODO: Trace non-inbounds GEPs. + int64_t Offset = 0; + if (const Value *Base = getBasePointerOfPointerOperand(I, Offset, DL)) + if (Base == &getAnchoredValue()) { + addKnownNonnull(); + takeKnownDerefBytesMaximum( + Offset + + DL.getTypeStoreSize(Base->getType()->getPointerElementType())); + } + } + // Callback function std::function CallSiteCheck = [&](CallSite CS) -> bool { assert(CS && "Sanity check: Call site was not initialized properly!"); Index: llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll +++ llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll @@ -246,7 +246,8 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define i64* @not_captured_but_returned_0(i64* returned %a) +; CHECK: define nonnull dereferenceable(8) i64* @not_captured_but_returned_0(i64* nonnull returned dereferenceable(8) %a) + define i64* @not_captured_but_returned_0(i64* %a) #0 { entry: store i64 0, i64* %a, align 8 @@ -261,7 +262,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define nonnull i64* @not_captured_but_returned_1(i64* %a) +; CHECK: define nonnull dereferenceable(8) i64* @not_captured_but_returned_1(i64* nonnull dereferenceable(16) %a) define i64* @not_captured_but_returned_1(i64* %a) #0 { entry: %add.ptr = getelementptr inbounds i64, i64* %a, i64 1 @@ -322,7 +323,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) +; CHECK: define nonnull dereferenceable(8) 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) Index: llvm/test/Transforms/FunctionAttrs/arg_returned.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/arg_returned.ll +++ llvm/test/Transforms/FunctionAttrs/arg_returned.ll @@ -262,7 +262,7 @@ ; FIXME: no-return missing ; FNATTR: define i32* @rt0(i32* readonly %a) ; BOTH: Function Attrs: nofree noinline nosync nounwind readonly uwtable -; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a) +; BOTH-NEXT: define nonnull dereferenceable(4) i32* @rt0(i32* nonnull readonly returned dereferenceable(4) %a) define i32* @rt0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -281,7 +281,7 @@ ; FIXME: no-return missing ; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a) ; BOTH: Function Attrs: nofree noinline nosync nounwind readonly uwtable -; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture readonly %a) +; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture nonnull readonly dereferenceable(4) %a) define i32* @rt1(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 Index: llvm/test/Transforms/FunctionAttrs/nonnull.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/nonnull.ll +++ llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -260,14 +260,9 @@ ; FNATTR-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a) ; FNATTR-NEXT: call void @use3(i8* %c, i8* %a, i8* %b) -; FIXME: missing "nonnull", it should be -; @parent2(i8* nonnull %a, i8* nonnull %b, i8* nonnull %c) -; call void @use3nonnull(i8* nonnull %b, i8* nonnull %c, i8* nonnull %a) -; call void @use3(i8* nonnull %c, i8* nonnull %a, i8* nonnull %b) - -; ATTRIBUTOR-LABEL: @parent2(i8* %a, i8* %b, i8* %c) +; ATTRIBUTOR-LABEL: @parent2(i8* nonnull %a, i8* nonnull %b, i8* nonnull %c) ; ATTRIBUTOR-NEXT: call void @use3nonnull(i8* nonnull %b, i8* nonnull %c, i8* nonnull %a) -; ATTRIBUTOR-NEXT: call void @use3(i8* %c, i8* %a, i8* %b) +; ATTRIBUTOR-NEXT: call void @use3(i8* nonnull %c, i8* nonnull %a, i8* nonnull %b) ; BOTH-NEXT: ret void call void @use3nonnull(i8* %b, i8* %c, i8* %a) @@ -282,13 +277,9 @@ ; FNATTR-NEXT: call void @use1nonnull(i8* %a) ; FNATTR-NEXT: call void @use3(i8* %c, i8* %b, i8* %a) -; FIXME: missing "nonnull", it should be, -; @parent3(i8* nonnull %a, i8* %b, i8* %c) -; call void @use1nonnull(i8* nonnull %a) -; call void @use3(i8* %c, i8* %b, i8* nonnull %a) -; ATTRIBUTOR-LABEL: @parent3(i8* %a, i8* %b, i8* %c) +; ATTRIBUTOR-LABEL: @parent3(i8* nonnull %a, i8* %b, i8* %c) ; ATTRIBUTOR-NEXT: call void @use1nonnull(i8* nonnull %a) -; ATTRIBUTOR-NEXT: call void @use3(i8* %c, i8* %b, i8* %a) +; ATTRIBUTOR-NEXT: call void @use3(i8* %c, i8* %b, i8* nonnull %a) ; BOTH-NEXT: ret void @@ -305,16 +296,10 @@ ; CHECK-NEXT: call void @use2(i8* %a, i8* %c) ; CHECK-NEXT: call void @use1(i8* %b) -; FIXME : missing "nonnull", it should be -; @parent4(i8* %a, i8* nonnull %b, i8* nonnull %c) -; call void @use2nonnull(i8* nonnull %c, i8* nonull %b) -; call void @use2(i8* %a, i8* nonnull %c) -; call void @use1(i8* nonnull %b) - -; ATTRIBUTOR-LABEL: @parent4(i8* %a, i8* %b, i8* %c) +; ATTRIBUTOR-LABEL: @parent4(i8* %a, i8* nonnull %b, i8* nonnull %c) ; ATTRIBUTOR-NEXT: call void @use2nonnull(i8* nonnull %c, i8* nonnull %b) -; ATTRIBUTOR-NEXT: call void @use2(i8* %a, i8* %c) -; ATTRIBUTOR-NEXT: call void @use1(i8* %b) +; ATTRIBUTOR-NEXT: call void @use2(i8* %a, i8* nonnull %c) +; ATTRIBUTOR-NEXT: call void @use1(i8* nonnull %b) ; BOTH: ret void @@ -351,8 +336,7 @@ define i8 @parent6(i8* %a, i8* %b) { ; FNATTR-LABEL: @parent6(i8* nonnull %a, i8* %b) -; FIXME: missing "nonnull" -; ATTRIBUTOR-LABEL: @parent6(i8* %a, i8* %b) +; ATTRIBUTOR-LABEL: @parent6(i8* nonnull %a, i8* nonnull dereferenceable(1) %b) ; BOTH-NEXT: [[C:%.*]] = load volatile i8, i8* %b ; FNATTR-NEXT: call void @use1nonnull(i8* %a) ; ATTRIBUTOR-NEXT: call void @use1nonnull(i8* nonnull %a) @@ -370,14 +354,9 @@ ; FNATTR-NEXT: [[RET:%.*]] = call i8 @use1safecall(i8* %a) ; FNATTR-NEXT: call void @use1nonnull(i8* %a) -; FIXME : missing "nonnull", it should be -; @parent7(i8* nonnull %a) -; [[RET:%.*]] = call i8 @use1safecall(i8* nonnull %a) -; call void @use1nonnull(i8* nonnull %a) -; ret i8 [[RET]] -; ATTRIBUTOR-LABEL: @parent7(i8* %a) -; ATTRIBUTOR-NEXT: [[RET:%.*]] = call i8 @use1safecall(i8* %a) +; ATTRIBUTOR-LABEL: @parent7(i8* nonnull %a) +; ATTRIBUTOR-NEXT: [[RET:%.*]] = call i8 @use1safecall(i8* nonnull %a) ; ATTRIBUTOR-NEXT: call void @use1nonnull(i8* nonnull %a) ; BOTH-NEXT: ret i8 [[RET]] @@ -393,8 +372,7 @@ define i1 @parent8(i8* %a, i8* %bogus1, i8* %b) personality i8* bitcast (i32 (...)* @esfp to i8*){ ; FNATTR-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b) -; FIXME : missing "nonnull", it should be @parent8(i8* nonnull %a, i8* %bogus1, i8* nonnull %b) -; ATTRIBUTOR-LABEL: @parent8(i8* %a, i8* %bogus1, i8* %b) +; ATTRIBUTOR-LABEL: @parent8(i8* nonnull %a, i8* %bogus1, i8* nonnull %b) ; BOTH-NEXT: entry: ; FNATTR-NEXT: invoke void @use2nonnull(i8* %a, i8* %b) ; ATTRIBUTOR-NEXT: invoke void @use2nonnull(i8* nonnull %a, i8* nonnull %b) Index: llvm/test/Transforms/FunctionAttrs/nosync.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/nosync.ll +++ llvm/test/Transforms/FunctionAttrs/nosync.ll @@ -45,7 +45,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind uwtable ; FNATTR-NEXT: define i32 @load_monotonic(i32* nocapture readonly) ; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define i32 @load_monotonic(i32* nocapture readonly) +; ATTRIBUTOR-NEXT: define i32 @load_monotonic(i32* nocapture nonnull readonly dereferenceable(4)) define i32 @load_monotonic(i32* nocapture readonly) norecurse nounwind uwtable { %2 = load atomic i32, i32* %0 monotonic, align 4 ret i32 %2 @@ -61,7 +61,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind uwtable ; FNATTR-NEXT: define void @store_monotonic(i32* nocapture) ; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture) +; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture nonnull dereferenceable(4)) define void @store_monotonic(i32* nocapture) norecurse nounwind uwtable { store atomic i32 10, i32* %0 monotonic, align 4 ret void @@ -78,7 +78,7 @@ ; FNATTR-NEXT: define i32 @load_acquire(i32* nocapture readonly) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define i32 @load_acquire(i32* nocapture readonly) +; ATTRIBUTOR-NEXT: define i32 @load_acquire(i32* nocapture nonnull readonly dereferenceable(4)) define i32 @load_acquire(i32* nocapture readonly) norecurse nounwind uwtable { %2 = load atomic i32, i32* %0 acquire, align 4 ret i32 %2 @@ -94,7 +94,7 @@ ; FNATTR-NEXT: define void @load_release(i32* nocapture) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define void @load_release(i32* nocapture) +; ATTRIBUTOR-NEXT: define void @load_release(i32* nocapture nonnull dereferenceable(4)) define void @load_release(i32* nocapture) norecurse nounwind uwtable { store atomic volatile i32 10, i32* %0 release, align 4 ret void @@ -106,7 +106,7 @@ ; FNATTR-NEXT: define void @load_volatile_release(i32* nocapture) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define void @load_volatile_release(i32* nocapture) +; ATTRIBUTOR-NEXT: define void @load_volatile_release(i32* nocapture nonnull dereferenceable(4)) define void @load_volatile_release(i32* nocapture) norecurse nounwind uwtable { store atomic volatile i32 10, i32* %0 release, align 4 ret void @@ -122,7 +122,7 @@ ; FNATTR-NEXT: define void @volatile_store(i32*) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define void @volatile_store(i32*) +; ATTRIBUTOR-NEXT: define void @volatile_store(i32* nonnull dereferenceable(4)) define void @volatile_store(i32*) norecurse nounwind uwtable { store volatile i32 14, i32* %0, align 4 ret void @@ -139,7 +139,7 @@ ; FNATTR-NEXT: define i32 @volatile_load(i32*) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define i32 @volatile_load(i32*) +; ATTRIBUTOR-NEXT: define i32 @volatile_load(i32* nonnull dereferenceable(4)) define i32 @volatile_load(i32*) norecurse nounwind uwtable { %2 = load volatile i32, i32* %0, align 4 ret i32 %2 @@ -226,8 +226,8 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1(i32* nocapture, %"struct.std::atomic"* nocapture) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @foo1(i32*, %"struct.std::atomic"*) -define void @foo1(i32*, %"struct.std::atomic"*) { +; ATTRIBUTOR: define void @foo1(i32* nonnull dereferenceable(4), %"struct.std::atomic"* nonnull dereferenceable(1)) +define void @foo1(i32* , %"struct.std::atomic"*) { store i32 100, i32* %0, align 4 fence release %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 @@ -238,7 +238,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar(i32* nocapture readnone, %"struct.std::atomic"* nocapture readonly) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @bar(i32*, %"struct.std::atomic"*) +; ATTRIBUTOR: define void @bar(i32*, %"struct.std::atomic"* nonnull dereferenceable(1)) define void @bar(i32 *, %"struct.std::atomic"*) { %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 br label %4 @@ -258,7 +258,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1_singlethread(i32* nocapture, %"struct.std::atomic"* nocapture) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @foo1_singlethread(i32*, %"struct.std::atomic"*) +; ATTRIBUTOR: define void @foo1_singlethread(i32* nonnull dereferenceable(4), %"struct.std::atomic"* nonnull dereferenceable(1)) define void @foo1_singlethread(i32*, %"struct.std::atomic"*) { store i32 100, i32* %0, align 4 fence syncscope("singlethread") release @@ -270,7 +270,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar_singlethread(i32* nocapture readnone, %"struct.std::atomic"* nocapture readonly) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @bar_singlethread(i32*, %"struct.std::atomic"*) +; ATTRIBUTOR: define void @bar_singlethread(i32*, %"struct.std::atomic"* nonnull dereferenceable(1)) define void @bar_singlethread(i32 *, %"struct.std::atomic"*) { %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 br label %4 Index: llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll +++ llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll @@ -71,7 +71,7 @@ } ; CHECK: Function Attrs: nofree nosync nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) +; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* nonnull dereferenceable(4) %r0, i32* returned %r1, i32* %w0) define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -122,7 +122,7 @@ } ; CHECK: Function Attrs: nofree nosync nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) +; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* nonnull dereferenceable(4) %r0, i32* returned %w0) define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 Index: llvm/test/Transforms/InferFunctionAttrs/dereferenceable.ll =================================================================== --- llvm/test/Transforms/InferFunctionAttrs/dereferenceable.ll +++ llvm/test/Transforms/InferFunctionAttrs/dereferenceable.ll @@ -1,10 +1,14 @@ ; RUN: opt < %s -inferattrs -S | FileCheck %s +; RUN: opt < %s -attributor --attributor-disable=false -S | FileCheck %s --check-prefix=ATTRIBUTOR + + ; Determine dereference-ability before unused loads get deleted: ; https://bugs.llvm.org/show_bug.cgi?id=21780 define <4 x double> @PR21780(double* %ptr) { ; CHECK-LABEL: @PR21780(double* %ptr) +; ATTRIBUTOR-LABEL: @PR21780(double* nonnull dereferenceable(32) %ptr) ; GEP of index 0 is simplified away. %arrayidx1 = getelementptr inbounds double, double* %ptr, i64 1 %arrayidx2 = getelementptr inbounds double, double* %ptr, i64 2