Index: llvm/include/llvm/Analysis/MustExecute.h =================================================================== --- llvm/include/llvm/Analysis/MustExecute.h +++ llvm/include/llvm/Analysis/MustExecute.h @@ -393,6 +393,13 @@ getMustBeExecutedNextInstruction(MustBeExecutedIterator &It, const Instruction *PP); + /// Return true if there is a instruction that \p Pred holds in the context + /// around \p PP. + bool + checkPredicateAfterInstruction(std::function Pred, + const Instruction *PP, + bool ContinueExplorationWhenPredHolds = false); + /// Parameter that limit the performed exploration. See the constructor for /// their meaning. ///{ 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/include/llvm/Transforms/IPO/Attributor.h =================================================================== --- llvm/include/llvm/Transforms/IPO/Attributor.h +++ llvm/include/llvm/Transforms/IPO/Attributor.h @@ -97,6 +97,7 @@ #define LLVM_TRANSFORMS_IPO_ATTRIBUTOR_H #include "llvm/Analysis/LazyCallGraph.h" +#include "llvm/Analysis/MustExecute.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/PassManager.h" @@ -328,6 +329,9 @@ /// reusable, it is advised to inherit from the InformationCache and cast the /// instance down in the abstract attributes. struct InformationCache { + + InformationCache(): Explorer(/* ExploreInterBlock */ true){} + /// A map type from opcodes to instructions with this opcode. using OpcodeInstMapTy = DenseMap>; @@ -345,6 +349,10 @@ return FuncRWInstsMap[&F]; } + MustBeExecutedContextExplorer& getContextExplorer() { + return Explorer; + } + private: /// A map type from functions to opcode to instruction maps. using FuncInstOpcodeMapTy = DenseMap; @@ -359,6 +367,9 @@ /// A map from functions to their instructions that may read or write memory. FuncRWInstsMapTy FuncRWInstsMap; + /// Context Explorer + MustBeExecutedContextExplorer Explorer; + /// Give the Attributor access to the members so /// Attributor::identifyDefaultAbstractAttributes(...) can initialize them. friend struct Attributor; Index: llvm/lib/Analysis/MustExecute.cpp =================================================================== --- llvm/lib/Analysis/MustExecute.cpp +++ llvm/lib/Analysis/MustExecute.cpp @@ -452,6 +452,21 @@ return nullptr; } +bool MustBeExecutedContextExplorer::checkPredicateAfterInstruction( + std::function Pred, const Instruction *PP, + bool ContinueExplorationWhenPredHolds) { + bool PredHolds = false; + for (const Instruction *I : range(PP)) { + LLVM_DEBUG(dbgs() << "[MustBeExecuted][checkPred] PP: " << *PP + << " I:" << *I << "\n"); + + PredHolds |= Pred(I); + if (PredHolds && !ContinueExplorationWhenPredHolds) + return true; + } + return PredHolds; +} + MustBeExecutedIterator::MustBeExecutedIterator( MustBeExecutedContextExplorer &Explorer, const Instruction *I) : Explorer(Explorer), CurInst(I) { Index: llvm/lib/Transforms/IPO/Attributor.cpp =================================================================== --- llvm/lib/Transforms/IPO/Attributor.cpp +++ llvm/lib/Transforms/IPO/Attributor.cpp @@ -388,6 +388,40 @@ return HasChanged; } +// Helper function that returns pointer operand of memory instruction. +static const Value *getPointerOperand(const Instruction *I) { + if (auto *LI = dyn_cast(I)) + if (!LI->isVolatile()) + return LI->getPointerOperand(); + + if (auto *SI = dyn_cast(I)) + if (!SI->isVolatile()) + return SI->getPointerOperand(); + + if (auto *CXI = dyn_cast(I)) + if (!CXI->isVolatile()) + return CXI->getPointerOperand(); + + if (auto *RMWI = dyn_cast(I)) + if (!RMWI->isVolatile()) + return RMWI->getPointerOperand(); + + return nullptr; +} + +// Helper function that returns base pointer of memory instruction operand. +// Only inbounds GEPs are traced. +static 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 AANoUnwindImpl : AANoUnwind, BooleanState { @@ -1164,6 +1198,34 @@ unsigned ArgNo = Arg.getArgNo(); + MustBeExecutedContextExplorer &Explorer = InfoCache.getContextExplorer(); + + auto Pred = [&](const Instruction *I) -> bool { + // See callsite abstract attribute. + if (ImmutableCallSite ICS = ImmutableCallSite(I)) + for (unsigned int i = 0; i < ICS.getNumArgOperands(); i++) + if (ICS.getArgOperand(i) == &getAnchorValue()) + if (auto *NonNullAA = A.getAAFor(*this, *I, i)) + // FIXME: Use assumption. + return NonNullAA->isKnownNonNull(); + + // 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())) + return Base == &getAnchorValue(); + + return false; + }; + + if (Explorer.checkPredicateAfterInstruction( + Pred, &getAnchorScope().getEntryBlock().front())) { + indicateOptimisticFixpoint(); + return ChangeStatus::CHANGED; + } + // Callback function std::function CallSiteCheck = [&](CallSite CS) { assert(CS && "Sanity check: Call site was not initialized properly!"); @@ -1426,9 +1488,9 @@ ChangeStatus HasChanged = ChangeStatus::UNCHANGED; const Function &F = getAnchorScope(); - // Flag to determine if we can change an invoke to a call assuming the callee - // is nounwind. This is not possible if the personality of the function allows - // to catch asynchronous exceptions. + // Flag to determine if we can change an invoke to a call assuming the + // callee is nounwind. This is not possible if the personality of the + // function allows to catch asynchronous exceptions. bool Invoke2CallAllowed = !mayCatchAsynchronousExceptions(F); for (const Instruction *NRC : NoReturnCalls) { @@ -1703,6 +1765,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 && @@ -1895,6 +1962,7 @@ InformationCache &InfoCache) { Function &F = getAnchorScope(); Argument &Arg = cast(getAnchorValue()); + const DataLayout &DL = F.getParent()->getDataLayout(); auto BeforeState = static_cast(*this); @@ -1905,6 +1973,41 @@ bool IsNonNull = isAssumedNonNull(); bool IsGlobal = isAssumedGlobal(); + MustBeExecutedContextExplorer &Explorer = InfoCache.getContextExplorer(); + + auto Pred = [&](const Instruction *I) { + // See callsite abstract attribute. + if (ImmutableCallSite ICS = ImmutableCallSite(I)) + for (unsigned int i = 0; i < ICS.getNumArgOperands(); i++) + if (ICS.getArgOperand(i) == &getAnchorValue()) + if (auto *DerefAA = A.getAAFor(*this, *I, i)) { + // FIXME: Use assumption. + auto DerefBytes = DerefAA->getKnownDereferenceableBytes(); + takeKnownDerefBytesMaximum(DerefBytes); + return true; + } + + // 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 == &getAnchorValue()) { + uint32_t DerefBytes = + Offset + + DL.getTypeStoreSize( + getPointerOperand(I)->getType()->getPointerElementType()); + addKnownNonnull(); + takeKnownDerefBytesMaximum(DerefBytes); + return true; + } + return false; + }; + + Explorer.checkPredicateAfterInstruction( + Pred, &getAnchorScope().getEntryBlock().front(), + /* ContinueExplorationWhenPredHolds */ true); + // 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 @@ -248,7 +248,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 @@ -263,7 +264,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 @@ -324,7 +325,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 @@ -261,7 +261,7 @@ ; ; FNATTR: define i32* @rt0(i32* readonly %a) ; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable -; BOTH-NEXT: define i32* @rt0(i32* readonly %a) +; BOTH-NEXT: define nonnull dereferenceable(4) i32* @rt0(i32* nonnull readonly dereferenceable(4) %a) define i32* @rt0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -279,7 +279,7 @@ ; ; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a) ; BOTH: Function Attrs: nofree noinline noreturn 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/dereferenceable.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/dereferenceable.ll +++ llvm/test/Transforms/FunctionAttrs/dereferenceable.ll @@ -50,3 +50,17 @@ ret i32* %0 } +; TEST 5 +; Deduction from memory inst. + +define void @test5(i32* %a, i32* %b, i8* %c) local_unnamed_addr { +; ATTRIBUTOR: define void @test5(i32* nonnull dereferenceable(4) %a, i32* nonnull dereferenceable(1) %b, i8* nonnull dereferenceable(4) %c) + %bp = bitcast i32* %b to i8* + %cp = bitcast i8* %c to i32* + + %d = load i32, i32* %a + %e = load i8, i8* %bp + %f = load i32, i32* %cp + ret void +} + Index: llvm/test/Transforms/FunctionAttrs/liveness.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/liveness.ll +++ llvm/test/Transforms/FunctionAttrs/liveness.ll @@ -19,7 +19,7 @@ } ; CHECK: Function Attrs: nofree norecurse nosync nounwind uwtable willreturn -; CHECK-NEXT: define internal i32 @internal_load(i32* nonnull %0) +; CHECK-NEXT: define internal i32 @internal_load(i32* nonnull dereferenceable(4) %0) define internal i32 @internal_load(i32*) norecurse nounwind uwtable { %2 = load i32, i32* %0, align 4 ret i32 %2 Index: llvm/test/Transforms/FunctionAttrs/nonnull.ll =================================================================== --- llvm/test/Transforms/FunctionAttrs/nonnull.ll +++ llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -236,6 +236,104 @@ ret void } +declare void @fun0() #1 +declare void @fun1(i8*) #1 +declare void @fun2(i8*, i8*) #1 +declare void @fun3(i8*, i8*, i8*) #1 +; TEST 16 simple path test +; if(..) +; fun2(nonnull %a, nonnull %b) +; else +; fun2(nonnull %a, %b) +; We can say that %a is nonnull but %b is not. +define void @f16(i8* %a, i8 * %b, i8 %c) { +; FIXME: missing nonnull on %a +; ATTRIBUTOR: define void @f16(i8* %a, i8* %b, i8 %c) + %cmp = icmp eq i8 %c, 0 + br i1 %cmp, label %if.then, label %if.else +if.then: + tail call void @fun2(i8* nonnull %a, i8* nonnull %b) + ret void +if.else: + tail call void @fun2(i8* nonnull %a, i8* %b) + ret void +} +; TEST 17 explore child BB test +; if(..) +; ... (willreturn & nounwind) +; else +; ... (willreturn & nounwind) +; fun1(nonnull %a) +; We can say that %a is nonnull +define void @f17(i8* %a, i8 %c) { +; FIXME: missing nonnull on %a +; ATTRIBUTOR: define void @f17(i8* %a, i8 %c) + %cmp = icmp eq i8 %c, 0 + br i1 %cmp, label %if.then, label %if.else +if.then: + tail call void @fun0() + br label %cont +if.else: + tail call void @fun0() + br label %cont +cont: + tail call void @fun1(i8* nonnull %a) + ret void +} +; TEST 18 More complex test +; if(..) +; ... (willreturn & nounwind) +; else +; ... (willreturn & nounwind) +; if(..) +; ... (willreturn & nounwind) +; else +; ... (willreturn & nounwind) +; fun1(nonnull %a) + +define void @f18(i8* %a, i8* %b, i8 %c) { +; FIXME: missing nonnull on %a +; ATTRIBUTOR: define void @f18(i8* %a, i8* %b, i8 %c) + %cmp1 = icmp eq i8 %c, 0 + br i1 %cmp1, label %if.then, label %if.else +if.then: + tail call void @fun0() + br label %cont +if.else: + tail call void @fun0() + br label %cont +cont: + %cmp2 = icmp eq i8 %c, 1 + br i1 %cmp2, label %cont.then, label %cont.else +cont.then: + tail call void @fun1(i8* nonnull %b) + br label %cont2 +cont.else: + tail call void @fun0() + br label %cont2 +cont2: + tail call void @fun1(i8* nonnull %a) + ret void +} + +; TEST 19: Loop + +define void @f19(i8* %a, i8* %b, i8 %c) { +; FIXME: missing nonnull on %b +; ATTRIBUTOR: define void @f19(i8* %a, i8* %b, i8 %c) + br label %loop.header +loop.header: + %cmp2 = icmp eq i8 %c, 0 + br i1 %cmp2, label %loop.body, label %loop.exit +loop.body: + tail call void @fun1(i8* nonnull %b) + tail call void @fun1(i8* nonnull %a) + br label %loop.header +loop.exit: + tail call void @fun1(i8* nonnull %b) + ret void +} + ; Test propagation of nonnull callsite args back to caller. declare void @use1(i8* %x) @@ -268,14 +366,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) @@ -290,13 +383,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 @@ -313,16 +402,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 @@ -359,8 +442,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* %b) ; BOTH-NEXT: [[C:%.*]] = load volatile i8, i8* %b ; FNATTR-NEXT: call void @use1nonnull(i8* %a) ; ATTRIBUTOR-NEXT: call void @use1nonnull(i8* nonnull %a) @@ -378,14 +460,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]] @@ -401,8 +478,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) @@ -429,6 +505,7 @@ unreachable } + ; BOTH: define nonnull i32* @gep1( define i32* @gep1(i32* %p) { %q = getelementptr inbounds i32, i32* %p, i32 1 @@ -459,3 +536,4 @@ } attributes #0 = { "null-pointer-is-valid"="true" } +attributes #1 = { nounwind willreturn} 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 %0) ; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define i32 @load_monotonic(i32* nocapture readonly %0) +; ATTRIBUTOR-NEXT: define i32 @load_monotonic(i32* nocapture nonnull readonly dereferenceable(4) %0) define i32 @load_monotonic(i32* nocapture readonly %0) 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 %0) ; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture %0) +; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture nonnull dereferenceable(4) %0) define void @store_monotonic(i32* nocapture %0) 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 %0) ; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind uwtable ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define i32 @load_acquire(i32* nocapture readonly %0) +; ATTRIBUTOR-NEXT: define i32 @load_acquire(i32* nocapture nonnull readonly dereferenceable(4) %0) define i32 @load_acquire(i32* nocapture readonly %0) norecurse nounwind uwtable { %2 = load atomic i32, i32* %0 acquire, align 4 ret i32 %2 @@ -224,7 +224,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @foo1(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @foo1(i32* nonnull dereferenceable(4) %0, %"struct.std::atomic"* nonnull dereferenceable(1) %1) define void @foo1(i32* %0, %"struct.std::atomic"* %1) { store i32 100, i32* %0, align 4 fence release @@ -236,7 +236,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar(i32* nocapture readnone %0, %"struct.std::atomic"* nocapture readonly %1) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @bar(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @bar(i32* %0, %"struct.std::atomic"* nonnull dereferenceable(1) %1) define void @bar(i32* %0, %"struct.std::atomic"* %1) { %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 br label %4 @@ -256,7 +256,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1_singlethread(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @foo1_singlethread(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @foo1_singlethread(i32* nonnull dereferenceable(4) %0, %"struct.std::atomic"* nonnull dereferenceable(1) %1) define void @foo1_singlethread(i32* %0, %"struct.std::atomic"* %1) { store i32 100, i32* %0, align 4 fence syncscope("singlethread") release @@ -268,7 +268,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar_singlethread(i32* nocapture readnone %0, %"struct.std::atomic"* nocapture readonly %1) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @bar_singlethread(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @bar_singlethread(i32* %0, %"struct.std::atomic"* nonnull dereferenceable(1) %1) define void @bar_singlethread(i32* %0, %"struct.std::atomic"* %1) { %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