#include "llvm/ADT/SetVector.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/CallGraph.h"
+#include "llvm/Analysis/MustExecute.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/IR/CallSite.h"
#include "llvm/IR/PassManager.h"
/// instance down in the abstract attributes.
struct InformationCache {
InformationCache(const Module &M, AnalysisGetter &AG)
- : DL(M.getDataLayout()), AG(AG) {
+ : DL(M.getDataLayout()), Explorer(/* ExploreInterBlock */ true), AG(AG) {
CallGraph *CG = AG.getAnalysis<CallGraphAnalysis>(M);
if (!CG)
return FuncRWInstsMap[&F];
}
+ /// Return MustBeExecutedContextExplorer
+ MustBeExecutedContextExplorer &getMustBeExecutedContextExplorer() {
+ return Explorer;
+ }
+
/// Return TargetLibraryInfo for function \p F.
TargetLibraryInfo *getTargetLibraryInfoForFunction(const Function &F) {
return AG.getAnalysis<TargetLibraryAnalysis>(F);
/// The datalayout used in the module.
const DataLayout &DL;
+ /// MustBeExecutedContextExplorer
+ MustBeExecutedContextExplorer Explorer;
+
/// Getters for analysis.
AnalysisGetter &AG;
return NonNullAA && NonNullAA->isAssumedNonNull();
}
+ /// Return true if we know that the underlying value is nonnull.
+ bool isKnownNonNull() const {
+ return NonNullAA && NonNullAA->isKnownNonNull();
+ }
+
/// Return true if we assume that underlying value is
/// dereferenceable(_or_null) globally.
bool isAssumedGlobal() const { return GlobalState.getAssumed(); }
llvm_unreachable("Expected enum or string attribute!");
}
+static const Value *getPointerOperand(const Instruction *I) {
+ if (auto *LI = dyn_cast<LoadInst>(I))
+ if (!LI->isVolatile())
+ return LI->getPointerOperand();
+
+ if (auto *SI = dyn_cast<StoreInst>(I))
+ if (!SI->isVolatile())
+ return SI->getPointerOperand();
+
+ if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(I))
+ if (!CXI->isVolatile())
+ return CXI->getPointerOperand();
+
+ if (auto *RMWI = dyn_cast<AtomicRMWInst>(I))
+ if (!RMWI->isVolatile())
+ return RMWI->getPointerOperand();
+
+ return nullptr;
+}
+static const Value *getBasePointerOfAccessPointerOperand(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);
+}
ChangeStatus AbstractAttribute::update(Attributor &A) {
ChangeStatus HasChanged = ChangeStatus::UNCHANGED;
};
/// Helper class for generic replication: function returned -> cs returned.
-template <typename AAType, typename Base>
+template <typename AAType, typename Base,
+ typename StateType = typename AAType::StateType>
struct AACallSiteReturnedFromReturned : public Base {
AACallSiteReturnedFromReturned(const IRPosition &IRP) : Base(IRP) {}
}
};
+/// Helper class for generic deduction using must-be-executed-context
+/// Base class is required to have `followUse` method.
+
+/// bool followUse(Attributor &A, const Use *U, const Instruction *I)
+/// \param U Underlying use.
+/// \param I The user of the \p U.
+/// `followUse` returns true if the value should be tracked transitively.
+
+template <typename AAType, typename Base,
+ typename StateType = typename AAType::StateType>
+struct AAFromMustBeExecutedContext : public Base {
+ AAFromMustBeExecutedContext(const IRPosition &IRP) : Base(IRP) {}
+
+ void initialize(Attributor &A) override {
+ Base::initialize(A);
+ IRPosition &IRP = this->getIRPosition();
+ Instruction *CtxI = IRP.getCtxI();
+
+ if (!CtxI)
+ return;
+
+ for (const Use &U : IRP.getAssociatedValue().uses())
+ Uses.insert(&U);
+ }
+
+ /// See AbstractAttribute::updateImpl(...).
+ ChangeStatus updateImpl(Attributor &A) override {
+ auto BeforeState = this->getState();
+ auto &S = this->getState();
+ Instruction *CtxI = this->getIRPosition().getCtxI();
+ if (!CtxI)
+ return ChangeStatus::UNCHANGED;
+
+ MustBeExecutedContextExplorer &Explorer =
+ A.getInfoCache().getMustBeExecutedContextExplorer();
+
+ SetVector<const Use *> NextUses;
+
+ for (const Use *U : Uses) {
+ if (const Instruction *UserI = dyn_cast<Instruction>(U->getUser())) {
+ auto EIt = Explorer.begin(CtxI), EEnd = Explorer.end(CtxI);
+ bool Found = EIt.count(UserI);
+ while (!Found && ++EIt != EEnd)
+ Found = EIt.getCurrentInst() == UserI;
+ if (Found && Base::followUse(A, U, UserI))
+ for (const Use &Us : UserI->uses())
+ NextUses.insert(&Us);
+ }
+ }
+ for (const Use *U : NextUses)
+ Uses.insert(U);
+
+ return BeforeState == S ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED;
+ }
+
+private:
+ /// Container for (transitive) uses of the associated value.
+ SetVector<const Use *> Uses;
+};
+
+template <typename AAType, typename Base,
+ typename StateType = typename AAType::StateType>
+using AAArgumentFromCallSiteArgumentsAndMustBeExecutedContext =
+ AAComposeTwoGenericDeduction<AAType, Base, StateType,
+ AAFromMustBeExecutedContext,
+ AAArgumentFromCallSiteArguments>;
+
+template <typename AAType, typename Base,
+ typename StateType = typename AAType::StateType>
+using AACallSiteReturnedFromReturnedAndMustBeExecutedContext =
+ AAComposeTwoGenericDeduction<AAType, Base, StateType,
+ AAFromMustBeExecutedContext,
+ AACallSiteReturnedFromReturned>;
+
/// -----------------------NoUnwind Function Attribute--------------------------
struct AANoUnwindImpl : AANoUnwind {
};
/// ------------------------ NonNull Argument Attribute ------------------------
+static int64_t getKnownNonNullAndDerefBytesForUse(
+ Attributor &A, AbstractAttribute &QueryingAA, Value &AssociatedValue,
+ const Use *U, const Instruction *I, bool &IsNonNull, bool &TrackUse) {
+ // TODO: Add GEP support
+ TrackUse = false;
+
+ const Function *F = I->getFunction();
+ bool NullPointerIsDefined = F ? F->nullPointerIsDefined() : true;
+ const DataLayout &DL = A.getInfoCache().getDL();
+ if (ImmutableCallSite ICS = ImmutableCallSite(I)) {
+ if (ICS.isBundleOperand(U))
+ return 0;
+
+ if (ICS.isCallee(U)) {
+ IsNonNull |= !NullPointerIsDefined;
+ return 0;
+ }
+
+ unsigned ArgNo = ICS.getArgumentNo(U);
+ IRPosition IRP = IRPosition::callsite_argument(ICS, ArgNo);
+ auto &DerefAA = A.getAAFor<AADereferenceable>(QueryingAA, IRP);
+ IsNonNull |= DerefAA.isKnownNonNull();
+ return DerefAA.getKnownDereferenceableBytes();
+ }
+
+ int64_t Offset;
+ if (const Value *Base = getBasePointerOfAccessPointerOperand(I, Offset, DL)) {
+ if (Base == &AssociatedValue) {
+ int64_t DerefBytes =
+ Offset +
+ (int64_t)DL.getTypeStoreSize(
+ getPointerOperand(I)->getType()->getPointerElementType());
+
+ IsNonNull |= !NullPointerIsDefined;
+ return DerefBytes;
+ }
+ }
+
+ return 0;
+}
struct AANonNullImpl : AANonNull {
AANonNullImpl(const IRPosition &IRP) : AANonNull(IRP) {}
AANonNull::initialize(A);
}
+ /// See AAFromMustBeExecutedContext
+ bool followUse(Attributor &A, const Use *U, const Instruction *I) {
+ bool IsNonNull = false;
+ bool TrackUse = false;
+ getKnownNonNullAndDerefBytesForUse(A, *this, getAssociatedValue(), U, I,
+ IsNonNull, TrackUse);
+ takeKnownMaximum(IsNonNull);
+ return TrackUse;
+ }
+
/// See AbstractAttribute::getAsStr().
const std::string getAsStr() const override {
return getAssumed() ? "nonnull" : "may-null";
};
/// NonNull attribute for a floating value.
-struct AANonNullFloating : AANonNullImpl {
- AANonNullFloating(const IRPosition &IRP) : AANonNullImpl(IRP) {}
+struct AANonNullFloating
+ : AAFromMustBeExecutedContext<AANonNull, AANonNullImpl> {
+ using Base = AAFromMustBeExecutedContext<AANonNull, AANonNullImpl>;
+ AANonNullFloating(const IRPosition &IRP) : Base(IRP) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
- AANonNullImpl::initialize(A);
+ Base::initialize(A);
if (isAtFixpoint())
return;
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
+ ChangeStatus Change = Base::updateImpl(A);
+ if (isKnownNonNull())
+ return Change;
+
const DataLayout &DL = A.getDataLayout();
auto VisitValueCB = [&](Value &V, AAAlign::StateType &T,
/// NonNull attribute for function argument.
struct AANonNullArgument final
- : AAArgumentFromCallSiteArguments<AANonNull, AANonNullImpl> {
+ : AAArgumentFromCallSiteArgumentsAndMustBeExecutedContext<AANonNull,
+ AANonNullImpl> {
AANonNullArgument(const IRPosition &IRP)
- : AAArgumentFromCallSiteArguments<AANonNull, AANonNullImpl>(IRP) {}
+ : AAArgumentFromCallSiteArgumentsAndMustBeExecutedContext<AANonNull,
+ AANonNullImpl>(
+ IRP) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(nonnull) }
/// NonNull attribute for a call site return position.
struct AANonNullCallSiteReturned final
- : AACallSiteReturnedFromReturned<AANonNull, AANonNullImpl> {
+ : AACallSiteReturnedFromReturnedAndMustBeExecutedContext<AANonNull,
+ AANonNullImpl> {
AANonNullCallSiteReturned(const IRPosition &IRP)
- : AACallSiteReturnedFromReturned<AANonNull, AANonNullImpl>(IRP) {}
+ : AACallSiteReturnedFromReturnedAndMustBeExecutedContext<AANonNull,
+ AANonNullImpl>(
+ IRP) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(nonnull) }
const StateType &getState() const override { return *this; }
/// }
+ /// See AAFromMustBeExecutedContext
+ bool followUse(Attributor &A, const Use *U, const Instruction *I) {
+ bool IsNonNull = false;
+ bool TrackUse = false;
+ int64_t DerefBytes = getKnownNonNullAndDerefBytesForUse(
+ A, *this, getAssociatedValue(), U, I, IsNonNull, TrackUse);
+ takeKnownDerefBytesMaximum(DerefBytes);
+ return TrackUse;
+ }
+
void getDeducedAttributes(LLVMContext &Ctx,
SmallVectorImpl<Attribute> &Attrs) const override {
// TODO: Add *_globally support
};
/// Dereferenceable attribute for a floating value.
-struct AADereferenceableFloating : AADereferenceableImpl {
- AADereferenceableFloating(const IRPosition &IRP)
- : AADereferenceableImpl(IRP) {}
+struct AADereferenceableFloating
+ : AAFromMustBeExecutedContext<AADereferenceable, AADereferenceableImpl> {
+ using Base =
+ AAFromMustBeExecutedContext<AADereferenceable, AADereferenceableImpl>;
+ AADereferenceableFloating(const IRPosition &IRP) : Base(IRP) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
+ ChangeStatus Change = Base::updateImpl(A);
+
const DataLayout &DL = A.getDataLayout();
auto VisitValueCB = [&](Value &V, DerefState &T, bool Stripped) -> bool {
A, getIRPosition(), *this, T, VisitValueCB))
return indicatePessimisticFixpoint();
- return clampStateAndIndicateChange(getState(), T);
+ return Change | clampStateAndIndicateChange(getState(), T);
}
/// See AbstractAttribute::trackStatistics()
/// Dereferenceable attribute for an argument
struct AADereferenceableArgument final
- : AAArgumentFromCallSiteArguments<AADereferenceable, AADereferenceableImpl,
- DerefState> {
- AADereferenceableArgument(const IRPosition &IRP)
- : AAArgumentFromCallSiteArguments<AADereferenceable,
- AADereferenceableImpl, DerefState>(
- IRP) {}
+ : AAArgumentFromCallSiteArgumentsAndMustBeExecutedContext<
+ AADereferenceable, AADereferenceableImpl, DerefState> {
+ using Base = AAArgumentFromCallSiteArgumentsAndMustBeExecutedContext<
+ AADereferenceable, AADereferenceableImpl, DerefState>;
+ AADereferenceableArgument(const IRPosition &IRP) : Base(IRP) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
};
/// Dereferenceable attribute deduction for a call site return value.
-struct AADereferenceableCallSiteReturned final : AADereferenceableImpl {
- AADereferenceableCallSiteReturned(const IRPosition &IRP)
- : AADereferenceableImpl(IRP) {}
+struct AADereferenceableCallSiteReturned final
+ : AACallSiteReturnedFromReturnedAndMustBeExecutedContext<
+ AADereferenceable, AADereferenceableImpl> {
+ using Base = AACallSiteReturnedFromReturnedAndMustBeExecutedContext<
+ AADereferenceable, AADereferenceableImpl>;
+ AADereferenceableCallSiteReturned(const IRPosition &IRP) : Base(IRP) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
- AADereferenceableImpl::initialize(A);
+ Base::initialize(A);
Function *F = getAssociatedFunction();
if (!F)
indicatePessimisticFixpoint();
// call site specific liveness information and then it makes
// sense to specialize attributes for call sites arguments instead of
// redirecting requests to the callee argument.
+
+ ChangeStatus Change = Base::updateImpl(A);
Function *F = getAssociatedFunction();
const IRPosition &FnPos = IRPosition::returned(*F);
auto &FnAA = A.getAAFor<AADereferenceable>(*this, FnPos);
- return clampStateAndIndicateChange(
- getState(), static_cast<const DerefState &>(FnAA.getState()));
+ return Change |
+ clampStateAndIndicateChange(
+ getState(), static_cast<const DerefState &>(FnAA.getState()));
}
/// See AbstractAttribute::trackStatistics()
; FIXME: This will work with an upcoming patch (D66618 or similar)
; define align 32 i32* @test10a(i32* align 32 "no-capture-maybe-returned" %p)
-; ATTRIBUTOR: define i32* @test10a(i32* align 32 "no-capture-maybe-returned" %p)
+; ATTRIBUTOR: define i32* @test10a(i32* nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" %p)
define i32* @test10a(i32* align 32 %p) {
; ATTRIBUTOR: %l = load i32, i32* %p, align 32
%l = load i32, i32* %p
; FIXME: This will work with an upcoming patch (D66618 or similar)
; define align 32 i32* @test10b(i32* align 32 "no-capture-maybe-returned" %p)
-; ATTRIBUTOR: define i32* @test10b(i32* align 32 "no-capture-maybe-returned" %p)
+; ATTRIBUTOR: define i32* @test10b(i32* nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" %p)
define i32* @test10b(i32* align 32 %p) {
; ATTRIBUTOR: %l = load i32, i32* %p, align 32
%l = load i32, i32* %p
; }
;
; There should *not* be a no-capture attribute on %a
-; CHECK: define i64* @not_captured_but_returned_0(i64* returned writeonly "no-capture-maybe-returned" %a)
+; CHECK: define nonnull dereferenceable(8) i64* @not_captured_but_returned_0(i64* nonnull returned writeonly dereferenceable(8) "no-capture-maybe-returned" %a)
+
define i64* @not_captured_but_returned_0(i64* %a) #0 {
entry:
store i64 0, i64* %a, align 8
;
; 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 {
;
; FNATTR: define i32* @rt0(i32* readonly %a)
; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
-; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture readonly %a)
+; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture nonnull readonly dereferenceable(4) %a)
define i32* @rt0(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
;
; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a)
; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
-; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt1(i32* nocapture readonly %a)
+; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt1(i32* nocapture nonnull readonly dereferenceable(4) %a)
define i32* @rt1(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s
; ModuleID = 'callback_simple.c'
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
; FIXME: The callee -> call site direction is not working yet.
define void @t0_caller(i32* %a) {
-; CHECK: @t0_caller(i32* [[A:%.*]])
+; CHECK-LABEL: @t0_caller(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[B:%.*]] = alloca i32, align 32
; CHECK-NEXT: [[C:%.*]] = alloca i32*, align 64
; Note that the first two arguments are provided by the callback_broker according to the callback in !1 below!
; The others are annotated with alignment information, amongst others, or even replaced by the constants passed to the call.
define internal void @t0_callback_callee(i32* %is_not_null, i32* %ptr, i32* %a, i64 %b, i32** %c) {
-; CHECK: @t0_callback_callee(i32* nocapture writeonly [[IS_NOT_NULL:%.*]], i32* nocapture readonly [[PTR:%.*]], i32* [[A:%.*]], i64 [[B:%.*]], i32** nocapture nonnull readonly align 64 dereferenceable(8) [[C:%.*]])
+; CHECK-LABEL: @t0_callback_callee(
; CHECK-NEXT: entry:
; CHECK-NEXT: [[PTR_VAL:%.*]] = load i32, i32* [[PTR:%.*]], align 8
; CHECK-NEXT: store i32 [[PTR_VAL]], i32* [[IS_NOT_NULL:%.*]]
-; RUN: opt -attributor -attributor-manifest-internal --attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR
+; RUN: opt -attributor -attributor-manifest-internal --attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR
declare void @deref_phi_user(i32* %a);
for.end: ; preds = %for.cond.cleanup
ret void
}
+
+; TEST 7
+; share known infomation in must-be-executed-context
+declare i32* @unkown_ptr() willreturn nounwind
+declare i32 @unkown_f(i32*) willreturn nounwind
+define i32* @f7_0(i32* %ptr) {
+; ATTRIBUTOR: define nonnull dereferenceable(8) i32* @f7_0(i32* nonnull returned dereferenceable(8) %ptr)
+ %T = tail call i32 @unkown_f(i32* dereferenceable(8) %ptr)
+ ret i32* %ptr
+}
+
+; ATTRIBUTOR: define void @f7_1(i32* nonnull dereferenceable(4) %ptr, i1 %c)
+define void @f7_1(i32* %ptr, i1 %c) {
+
+; ATTRIBUTOR: %A = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+ %A = tail call i32 @unkown_f(i32* %ptr)
+
+ %ptr.0 = load i32, i32* %ptr
+ ; deref 4 hold
+
+; FIXME: this should be %B = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+; ATTRIBUTOR: %B = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+ %B = tail call i32 @unkown_f(i32* dereferenceable(1) %ptr)
+
+ br i1%c, label %if.true, label %if.false
+if.true:
+; ATTRIBUTOR: %C = tail call i32 @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+ %C = tail call i32 @unkown_f(i32* %ptr)
+
+; ATTRIBUTOR: %D = tail call i32 @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+ %D = tail call i32 @unkown_f(i32* dereferenceable(8) %ptr)
+
+; FIXME: This should be tail call i32 @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+; Making must-be-executed-context backward exploration will fix this.
+; ATTRIBUTOR: %E = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+ %E = tail call i32 @unkown_f(i32* %ptr)
+
+ ret void
+
+if.false:
+ ret void
+}
+
+; ATTRIBUTOR: define void @f7_2(i1 %c)
+define void @f7_2(i1 %c) {
+
+ %ptr = tail call i32* @unkown_ptr()
+
+; ATTRIBUTOR: %A = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+ %A = tail call i32 @unkown_f(i32* %ptr)
+
+ %arg_a.0 = load i32, i32* %ptr
+ ; deref 4 hold
+
+; ATTRIBUTOR: %B = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+ %B = tail call i32 @unkown_f(i32* dereferenceable(1) %ptr)
+
+ br i1%c, label %if.true, label %if.false
+if.true:
+
+; ATTRIBUTOR: %C = tail call i32 @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+ %C = tail call i32 @unkown_f(i32* %ptr)
+
+; ATTRIBUTOR: %D = tail call i32 @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+ %D = tail call i32 @unkown_f(i32* dereferenceable(8) %ptr)
+
+ %E = tail call i32 @unkown_f(i32* %ptr)
+; FIXME: This should be @unkown_f(i32* nonnull dereferenceable(8) %ptr)
+; Making must-be-executed-context backward exploration will fix this.
+; ATTRIBUTOR: %E = tail call i32 @unkown_f(i32* nonnull dereferenceable(4) %ptr)
+
+ ret void
+
+if.false:
+ ret void
+}
+
+define i32* @f7_3() {
+; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @f7_3()
+ %ptr = tail call i32* @unkown_ptr()
+ store i32 10, i32* %ptr, align 16
+ ret i32* %ptr
+}
+
+define i32* @test_for_minus_index(i32* %p) {
+; FIXME: This should be define nonnull dereferenceable(8) i32* @test_for_minus_index(i32* nonnull %p)
+; ATTRIBUTOR: define nonnull dereferenceable(8) i32* @test_for_minus_index(i32* writeonly "no-capture-maybe-returned" %p)
+ %q = getelementptr inbounds i32, i32* %p, i32 -2
+ store i32 1, i32* %q
+ ret i32* %q
+}
ret i32 %add
}
-; CHECK: define private i32 @noalias_args(i32* nocapture readonly %A, i32* noalias nocapture readonly %B)
+; CHECK: define private i32 @noalias_args(i32* nocapture nonnull readonly dereferenceable(4) %A, i32* noalias nocapture nonnull readonly dereferenceable(4) %B)
define private i32 @noalias_args(i32* %A, i32* %B) #0 {
entry:
; FIXME: Should be something like this.
; define internal i32 @noalias_args_argmem(i32* noalias nocapture readonly %A, i32* noalias nocapture readonly %B)
-; CHECK: define internal i32 @noalias_args_argmem(i32* nocapture readonly %A, i32* nocapture readonly %B)
+; CHECK: define internal i32 @noalias_args_argmem(i32* nocapture nonnull readonly dereferenceable(4) %A, i32* nocapture nonnull readonly dereferenceable(4) %B)
+
;
define internal i32 @noalias_args_argmem(i32* %A, i32* %B) #1 {
entry:
}
; CHECK: Function Attrs: nofree norecurse nosync nounwind readonly uwtable willreturn
-; CHECK-NEXT: define internal i32 @internal_load(i32* nocapture nonnull readonly %0)
+; CHECK-NEXT: define internal i32 @internal_load(i32* nocapture nonnull readonly dereferenceable(4) %0)
define internal i32 @internal_load(i32*) norecurse nounwind uwtable {
%2 = load i32, i32* %0, align 4
ret i32 %2
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s
; TEST 1 - negative.
ret void
}
-; EITHER: define void @nc3(void ()* nocapture %p)
+
+; FNATTR: define void @nc3(void ()* nocapture %p)
+; ATTRIBUTOR: define void @nc3(void ()* nocapture nonnull %p)
define void @nc3(void ()* %p) {
call void %p()
ret void
ret void
}
-; EITHER: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p)
+; FNATTR: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p)
+; ATTRIBUTOR: define void @nc5(void (i8*)* nocapture nonnull %f, i8* nocapture %p)
define void @nc5(void (i8*)* %f, i8* %p) {
call void %f(i8* %p) readonly nounwind
call void %f(i8* nocapture %p)
ret void
}
-; EITHER: define void @test_cmpxchg(i32* nocapture %p)
+; FNATTR: define void @test_cmpxchg(i32* nocapture %p)
+; ATTRIBUTOR: define void @test_cmpxchg(i32* nocapture nonnull dereferenceable(4) %p)
define void @test_cmpxchg(i32* %p) {
cmpxchg i32* %p, i32 0, i32 1 acquire monotonic
ret void
}
-; EITHER: define void @test_cmpxchg_ptr(i32** nocapture %p, i32* %q)
+; FNATTR: define void @test_cmpxchg_ptr(i32** nocapture %p, i32* %q)
+; ATTRIBUTOR: define void @test_cmpxchg_ptr(i32** nocapture nonnull dereferenceable(8) %p, i32* %q)
define void @test_cmpxchg_ptr(i32** %p, i32* %q) {
cmpxchg i32** %p, i32* null, i32* %q acquire monotonic
ret void
}
-; EITHER: define void @test_atomicrmw(i32* nocapture %p)
+; FNATTR: define void @test_atomicrmw(i32* nocapture %p)
+; ATTRIBUTOR: define void @test_atomicrmw(i32* nocapture nonnull dereferenceable(4) %p)
define void @test_atomicrmw(i32* %p) {
atomicrmw add i32* %p, i32 1 seq_cst
ret void
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)
; 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)
; 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
; 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
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)
; 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]]
declare i32 @esfp(...)
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* nocapture readnone %bogus1, i8* %b)
+; BOTH-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %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)
ret void
}
+
attributes #0 = { "null-pointer-is-valid"="true" }
+attributes #1 = { nounwind willreturn}
; Call through a function pointer
; ATTRIBUTOR-NOT: Function Attrs
-; ATTRIBUTOR: define i32 @eval_func(i32 (i32)* nocapture %0, i32 %1)
-define i32 @eval_func(i32 (i32)* , i32) local_unnamed_addr {
+; ATTRIBUTOR: define i32 @eval_func1(i32 (i32)* nocapture nonnull %0, i32 %1)
+define i32 @eval_func1(i32 (i32)* , i32) local_unnamed_addr {
+ %3 = tail call i32 %0(i32 %1) #2
+ ret i32 %3
+}
+
+; ATTRIBUTOR-NOT: Function Attrs
+; ATTRIBUTOR: define i32 @eval_func2(i32 (i32)* nocapture %0, i32 %1)
+define i32 @eval_func2(i32 (i32)* , i32) local_unnamed_addr "null-pointer-is-valid"="true"{
%3 = tail call i32 %0(i32 %1) #2
ret i32 %3
}
; 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
; 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 writeonly %0)
+; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture nonnull writeonly dereferenceable(4) %0)
define void @store_monotonic(i32* nocapture %0) norecurse nounwind uwtable {
store atomic i32 10, i32* %0 monotonic, align 4
ret void
; 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
; 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* nocapture writeonly %0, %"struct.std::atomic"* nocapture writeonly %1)
+; ATTRIBUTOR: define void @foo1(i32* nocapture nonnull writeonly dereferenceable(4) %0, %"struct.std::atomic"* nocapture writeonly %1)
+
define void @foo1(i32* %0, %"struct.std::atomic"* %1) {
store i32 100, i32* %0, align 4
fence release
; TEST 13 - Fence syncscope("singlethread") seq_cst
; 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* nocapture writeonly %0, %"struct.std::atomic"* nocapture writeonly %1)
+; ATTRIBUTOR: Function Attrs: nofree nosync nounwind willreturn
+; ATTRIBUTOR: define void @foo1_singlethread(i32* nocapture nonnull writeonly dereferenceable(4) %0, %"struct.std::atomic"* nocapture writeonly %1)
+
define void @foo1_singlethread(i32* %0, %"struct.std::atomic"* %1) {
store i32 100, i32* %0, align 4
fence syncscope("singlethread") release
; 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: Function Attrs: nofree nosync nounwind
; ATTRIBUTOR: define void @bar_singlethread(i32* nocapture readnone %0, %"struct.std::atomic"* nocapture readonly %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
}
; 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
}
; 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
}
; FNATTR: define void @test5(i8** nocapture %p, i8* %q)
-; ATTRIBUTOR: define void @test5(i8** nocapture writeonly %p, i8* %q)
+; ATTRIBUTOR: define void @test5(i8** nocapture nonnull writeonly dereferenceable(8) %p, i8* %q)
; Missed optz'n: we could make %q readnone, but don't break test6!
define void @test5(i8** %p, i8* %q) {
store i8* %q, i8** %p
declare void @test6_1()
; FNATTR: define void @test6_2(i8** nocapture %p, i8* %q)
-; ATTRIBUTOR: define void @test6_2(i8** nocapture writeonly %p, i8* %q)
+; ATTRIBUTOR: define void @test6_2(i8** nocapture nonnull writeonly dereferenceable(8) %p, i8* %q)
; This is not a missed optz'n.
define void @test6_2(i8** %p, i8* %q) {
store i8* %q, i8** %p
; 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)
+; FIXME: this should be @PR21780(double* nonnull dereferenceable(32) %ptr)
+; trakcing use of GEP in Attributor would fix this problem.
+; ATTRIBUTOR-LABEL: @PR21780(double* nocapture nonnull readonly dereferenceable(8) %ptr)
+
; GEP of index 0 is simplified away.
%arrayidx1 = getelementptr inbounds double, double* %ptr, i64 1
%arrayidx2 = getelementptr inbounds double, double* %ptr, i64 2
ret <4 x double> %shuffle
}
+
+define double @PR21780_only_access3_with_inbounds(double* %ptr) {
+; CHECK-LABEL: @PR21780_only_access3_with_inbounds(double* %ptr)
+; FIXME: this should be @PR21780_only_access3_with_inbounds(double* nonnull dereferenceable(32) %ptr)
+; trakcing use of GEP in Attributor would fix this problem.
+; ATTRIBUTOR-LABEL: @PR21780_only_access3_with_inbounds(double* nocapture readonly %ptr)
+
+ %arrayidx3 = getelementptr inbounds double, double* %ptr, i64 3
+ %t3 = load double, double* %arrayidx3, align 8
+ ret double %t3
+}
+
+define double @PR21780_only_access3_without_inbounds(double* %ptr) {
+; CHECK-LABEL: @PR21780_only_access3_without_inbounds(double* %ptr)
+; ATTRIBUTOR-LABEL: @PR21780_only_access3_without_inbounds(double* nocapture readonly %ptr)
+ %arrayidx3 = getelementptr double, double* %ptr, i64 3
+ %t3 = load double, double* %arrayidx3, align 8
+ ret double %t3
+}
+
+define double @PR21780_without_inbounds(double* %ptr) {
+; CHECK-LABEL: @PR21780_without_inbounds(double* %ptr)
+; FIXME: this should be @PR21780_without_inbounds(double* nonnull dereferenceable(32) %ptr)
+; ATTRIBUTOR-LABEL: @PR21780_without_inbounds(double* nocapture nonnull readonly dereferenceable(8) %ptr)
+
+ %arrayidx1 = getelementptr double, double* %ptr, i64 1
+ %arrayidx2 = getelementptr double, double* %ptr, i64 2
+ %arrayidx3 = getelementptr double, double* %ptr, i64 3
+
+ %t0 = load double, double* %ptr, align 8
+ %t1 = load double, double* %arrayidx1, align 8
+ %t2 = load double, double* %arrayidx2, align 8
+ %t3 = load double, double* %arrayidx3, align 8
+
+ ret double %t3
+}
+
; Unsimplified, but still valid. Also, throw in some bogus arguments.
define void @gep0(i8* %unused, i8* %other, i8* %ptr) {