mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-31 12:41:49 +01:00
[Attributor] Deduce "nonnull" attribute
Summary: Porting nonnull attribute to attributor. Reviewers: jdoerfert, sstefan1 Reviewed By: jdoerfert Subscribers: xbolva00, hiraditya, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D63604 llvm-svn: 366043
This commit is contained in:
parent
cb15cb48f4
commit
060abd8195
@ -263,6 +263,14 @@ struct Attributor {
|
||||
Function &F, InformationCache &InfoCache,
|
||||
DenseSet</* Attribute::AttrKind */ unsigned> *Whitelist = nullptr);
|
||||
|
||||
/// Check \p Pred on all function call sites.
|
||||
///
|
||||
/// This method will evaluate \p Pred on call sites and return
|
||||
/// true if \p Pred holds in every call sites. However, this is only possible
|
||||
/// all call sites are known, hence the function has internal linkage.
|
||||
bool checkForAllCallSites(Function &F, std::function<bool(CallSite)> &Pred,
|
||||
bool RequireAllCallSites);
|
||||
|
||||
private:
|
||||
/// The set of all abstract attributes.
|
||||
///{
|
||||
@ -708,6 +716,30 @@ struct AANoSync : public AbstractAttribute {
|
||||
virtual bool isKnownNoSync() const = 0;
|
||||
};
|
||||
|
||||
/// An abstract interface for all nonnull attributes.
|
||||
struct AANonNull : public AbstractAttribute {
|
||||
|
||||
/// See AbstractAttribute::AbstractAttribute(...).
|
||||
AANonNull(Value &V, InformationCache &InfoCache)
|
||||
: AbstractAttribute(V, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::AbstractAttribute(...).
|
||||
AANonNull(Value *AssociatedVal, Value &AnchoredValue,
|
||||
InformationCache &InfoCache)
|
||||
: AbstractAttribute(AssociatedVal, AnchoredValue, InfoCache) {}
|
||||
|
||||
/// Return true if we assume that the underlying value is nonnull.
|
||||
virtual bool isAssumedNonNull() const = 0;
|
||||
|
||||
/// Return true if we know that underlying value is nonnull.
|
||||
virtual bool isKnownNonNull() const = 0;
|
||||
|
||||
/// See AbastractState::getAttrKind().
|
||||
Attribute::AttrKind getAttrKind() const override { return ID; }
|
||||
|
||||
/// The identifier used by the Attributor for this class of attributes.
|
||||
static constexpr Attribute::AttrKind ID = Attribute::NonNull;
|
||||
};
|
||||
} // end namespace llvm
|
||||
|
||||
#endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/Statistic.h"
|
||||
#include "llvm/Analysis/GlobalsModRef.h"
|
||||
#include "llvm/Analysis/ValueTracking.h"
|
||||
#include "llvm/IR/Argument.h"
|
||||
#include "llvm/IR/Attributes.h"
|
||||
#include "llvm/IR/InstIterator.h"
|
||||
@ -51,6 +52,10 @@ STATISTIC(NumFnArgumentReturned,
|
||||
"Number of function arguments marked returned");
|
||||
STATISTIC(NumFnNoSync, "Number of functions marked nosync");
|
||||
STATISTIC(NumFnNoFree, "Number of functions marked nofree");
|
||||
STATISTIC(NumFnReturnedNonNull,
|
||||
"Number of function return values marked nonnull");
|
||||
STATISTIC(NumFnArgumentNonNull, "Number of function arguments marked nonnull");
|
||||
STATISTIC(NumCSArgumentNonNull, "Number of call site arguments marked nonnull");
|
||||
|
||||
// TODO: Determine a good default value.
|
||||
//
|
||||
@ -108,6 +113,21 @@ static void bookkeeping(AbstractAttribute::ManifestPosition MP,
|
||||
case Attribute::NoFree:
|
||||
NumFnNoFree++;
|
||||
break;
|
||||
case Attribute::NonNull:
|
||||
switch (MP) {
|
||||
case AbstractAttribute::MP_RETURNED:
|
||||
NumFnReturnedNonNull++;
|
||||
break;
|
||||
case AbstractAttribute::MP_ARGUMENT:
|
||||
NumFnArgumentNonNull++;
|
||||
break;
|
||||
case AbstractAttribute::MP_CALL_SITE_ARGUMENT:
|
||||
NumCSArgumentNonNull++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
@ -970,10 +990,252 @@ ChangeStatus AANoFreeFunction::updateImpl(Attributor &A) {
|
||||
return ChangeStatus::UNCHANGED;
|
||||
}
|
||||
|
||||
/// ------------------------ NonNull Argument Attribute ------------------------
|
||||
struct AANonNullImpl : AANonNull, BooleanState {
|
||||
|
||||
AANonNullImpl(Value &V, InformationCache &InfoCache)
|
||||
: AANonNull(V, InfoCache) {}
|
||||
|
||||
AANonNullImpl(Value *AssociatedVal, Value &AnchoredValue,
|
||||
InformationCache &InfoCache)
|
||||
: AANonNull(AssociatedVal, AnchoredValue, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getState()
|
||||
/// {
|
||||
AbstractState &getState() override { return *this; }
|
||||
const AbstractState &getState() const override { return *this; }
|
||||
/// }
|
||||
|
||||
/// See AbstractAttribute::getAsStr().
|
||||
const std::string getAsStr() const override {
|
||||
return getAssumed() ? "nonnull" : "may-null";
|
||||
}
|
||||
|
||||
/// See AANonNull::isAssumedNonNull().
|
||||
bool isAssumedNonNull() const override { return getAssumed(); }
|
||||
|
||||
/// See AANonNull::isKnownNonNull().
|
||||
bool isKnownNonNull() const override { return getKnown(); }
|
||||
|
||||
/// Generate a predicate that checks if a given value is assumed nonnull.
|
||||
/// The generated function returns true if a value satisfies any of
|
||||
/// following conditions.
|
||||
/// (i) A value is known nonZero(=nonnull).
|
||||
/// (ii) A value is associated with AANonNull and its isAssumedNonNull() is
|
||||
/// true.
|
||||
std::function<bool(Value &)> generatePredicate(Attributor &);
|
||||
};
|
||||
|
||||
std::function<bool(Value &)> AANonNullImpl::generatePredicate(Attributor &A) {
|
||||
// FIXME: The `AAReturnedValues` should provide the predicate with the
|
||||
// `ReturnInst` vector as well such that we can use the control flow sensitive
|
||||
// version of `isKnownNonZero`. This should fix `test11` in
|
||||
// `test/Transforms/FunctionAttrs/nonnull.ll`
|
||||
|
||||
std::function<bool(Value &)> Pred = [&](Value &RV) -> bool {
|
||||
if (isKnownNonZero(&RV, getAnchorScope().getParent()->getDataLayout()))
|
||||
return true;
|
||||
|
||||
auto *NonNullAA = A.getAAFor<AANonNull>(*this, RV);
|
||||
|
||||
ImmutableCallSite ICS(&RV);
|
||||
|
||||
if ((!NonNullAA || !NonNullAA->isAssumedNonNull()) &&
|
||||
(!ICS || !ICS.hasRetAttr(Attribute::NonNull)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return Pred;
|
||||
}
|
||||
|
||||
/// NonNull attribute for function return value.
|
||||
struct AANonNullReturned : AANonNullImpl {
|
||||
|
||||
AANonNullReturned(Function &F, InformationCache &InfoCache)
|
||||
: AANonNullImpl(F, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
ManifestPosition getManifestPosition() const override { return MP_RETURNED; }
|
||||
|
||||
/// See AbstractAttriubute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
Function &F = getAnchorScope();
|
||||
|
||||
// Already nonnull.
|
||||
if (F.getAttributes().hasAttribute(AttributeList::ReturnIndex,
|
||||
Attribute::NonNull))
|
||||
indicateOptimisticFixpoint();
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(...).
|
||||
ChangeStatus updateImpl(Attributor &A) override;
|
||||
};
|
||||
|
||||
ChangeStatus AANonNullReturned::updateImpl(Attributor &A) {
|
||||
Function &F = getAnchorScope();
|
||||
|
||||
auto *AARetVal = A.getAAFor<AAReturnedValues>(*this, F);
|
||||
if (!AARetVal) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
std::function<bool(Value &)> Pred = this->generatePredicate(A);
|
||||
if (!AARetVal->checkForallReturnedValues(Pred)) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
return ChangeStatus::UNCHANGED;
|
||||
}
|
||||
|
||||
/// NonNull attribute for function argument.
|
||||
struct AANonNullArgument : AANonNullImpl {
|
||||
|
||||
AANonNullArgument(Argument &A, InformationCache &InfoCache)
|
||||
: AANonNullImpl(A, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
ManifestPosition getManifestPosition() const override { return MP_ARGUMENT; }
|
||||
|
||||
/// See AbstractAttriubute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
Argument *Arg = cast<Argument>(getAssociatedValue());
|
||||
if (Arg->hasNonNullAttr())
|
||||
indicateOptimisticFixpoint();
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(...).
|
||||
ChangeStatus updateImpl(Attributor &A) override;
|
||||
};
|
||||
|
||||
/// NonNull attribute for a call site argument.
|
||||
struct AANonNullCallSiteArgument : AANonNullImpl {
|
||||
|
||||
/// See AANonNullImpl::AANonNullImpl(...).
|
||||
AANonNullCallSiteArgument(CallSite CS, unsigned ArgNo,
|
||||
InformationCache &InfoCache)
|
||||
: AANonNullImpl(CS.getArgOperand(ArgNo), *CS.getInstruction(), InfoCache),
|
||||
ArgNo(ArgNo) {}
|
||||
|
||||
/// See AbstractAttribute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
CallSite CS(&getAnchoredValue());
|
||||
if (isKnownNonZero(getAssociatedValue(),
|
||||
getAnchorScope().getParent()->getDataLayout()) ||
|
||||
CS.paramHasAttr(ArgNo, getAttrKind()))
|
||||
indicateOptimisticFixpoint();
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(Attributor &A).
|
||||
ChangeStatus updateImpl(Attributor &A) override;
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
ManifestPosition getManifestPosition() const override {
|
||||
return MP_CALL_SITE_ARGUMENT;
|
||||
};
|
||||
|
||||
// Return argument index of associated value.
|
||||
int getArgNo() const { return ArgNo; }
|
||||
|
||||
private:
|
||||
unsigned ArgNo;
|
||||
};
|
||||
ChangeStatus AANonNullArgument::updateImpl(Attributor &A) {
|
||||
Function &F = getAnchorScope();
|
||||
Argument &Arg = cast<Argument>(getAnchoredValue());
|
||||
|
||||
unsigned ArgNo = Arg.getArgNo();
|
||||
|
||||
// Callback function
|
||||
std::function<bool(CallSite)> CallSiteCheck = [&](CallSite CS) {
|
||||
assert(CS && "Sanity check: Call site was not initialized properly!");
|
||||
|
||||
auto *NonNullAA = A.getAAFor<AANonNull>(*this, *CS.getInstruction(), ArgNo);
|
||||
|
||||
// Check that NonNullAA is AANonNullCallSiteArgument.
|
||||
if (NonNullAA) {
|
||||
ImmutableCallSite ICS(&NonNullAA->getAnchoredValue());
|
||||
if (ICS && CS.getInstruction() == ICS.getInstruction())
|
||||
return NonNullAA->isAssumedNonNull();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CS.paramHasAttr(ArgNo, Attribute::NonNull))
|
||||
return true;
|
||||
|
||||
Value *V = CS.getArgOperand(ArgNo);
|
||||
if (isKnownNonZero(V, getAnchorScope().getParent()->getDataLayout()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
if (!A.checkForAllCallSites(F, CallSiteCheck, true)) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
return ChangeStatus::UNCHANGED;
|
||||
}
|
||||
|
||||
ChangeStatus AANonNullCallSiteArgument::updateImpl(Attributor &A) {
|
||||
// NOTE: Never look at the argument of the callee in this method.
|
||||
// If we do this, "nonnull" is always deduced because of the assumption.
|
||||
|
||||
Value &V = *getAssociatedValue();
|
||||
|
||||
auto *NonNullAA = A.getAAFor<AANonNull>(*this, V);
|
||||
|
||||
if (!NonNullAA || !NonNullAA->isAssumedNonNull()) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
return ChangeStatus::UNCHANGED;
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Attributor
|
||||
/// ----------------------------------------------------------------------------
|
||||
|
||||
bool Attributor::checkForAllCallSites(Function &F,
|
||||
std::function<bool(CallSite)> &Pred,
|
||||
bool RequireAllCallSites) {
|
||||
// We can try to determine information from
|
||||
// the call sites. However, this is only possible all call sites are known,
|
||||
// hence the function has internal linkage.
|
||||
if (RequireAllCallSites && !F.hasInternalLinkage()) {
|
||||
LLVM_DEBUG(
|
||||
dbgs()
|
||||
<< "Attributor: Function " << F.getName()
|
||||
<< " has no internal linkage, hence not all call sites are known\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Use &U : F.uses()) {
|
||||
|
||||
CallSite CS(U.getUser());
|
||||
dbgs() << *CS.getInstruction() << "\n";
|
||||
if (!CS || !CS.isCallee(&U) || !CS.getCaller()->hasExactDefinition()) {
|
||||
if (!RequireAllCallSites)
|
||||
continue;
|
||||
|
||||
LLVM_DEBUG(dbgs() << "Attributor: User " << *U.getUser()
|
||||
<< " is an invalid use of " << F.getName() << "\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Pred(CS))
|
||||
continue;
|
||||
|
||||
LLVM_DEBUG(dbgs() << "Attributor: Call site callback failed for "
|
||||
<< *CS.getInstruction() << "\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ChangeStatus Attributor::run() {
|
||||
// Initialize all abstract attributes.
|
||||
for (AbstractAttribute *AA : AllAbstractAttributes)
|
||||
@ -1128,6 +1390,17 @@ void Attributor::identifyDefaultAbstractAttributes(
|
||||
// though it is an argument attribute.
|
||||
if (!Whitelist || Whitelist->count(AAReturnedValues::ID))
|
||||
registerAA(*new AAReturnedValuesImpl(F, InfoCache));
|
||||
|
||||
// Every function with pointer return type might be marked nonnull.
|
||||
if (ReturnType->isPointerTy() &&
|
||||
(!Whitelist || Whitelist->count(AANonNullReturned::ID)))
|
||||
registerAA(*new AANonNullReturned(F, InfoCache));
|
||||
}
|
||||
|
||||
// Every argument with pointer type might be marked nonnull.
|
||||
for (Argument &Arg : F.args()) {
|
||||
if (Arg.getType()->isPointerTy())
|
||||
registerAA(*new AANonNullArgument(Arg, InfoCache));
|
||||
}
|
||||
|
||||
// Walk all instructions to find more attribute opportunities and also
|
||||
@ -1163,6 +1436,17 @@ void Attributor::identifyDefaultAbstractAttributes(
|
||||
InstOpcodeMap[I.getOpcode()].push_back(&I);
|
||||
if (I.mayReadOrWriteMemory())
|
||||
ReadOrWriteInsts.push_back(&I);
|
||||
|
||||
CallSite CS(&I);
|
||||
if (CS && CS.getCalledFunction()) {
|
||||
for (int i = 0, e = CS.getCalledFunction()->arg_size(); i < e; i++) {
|
||||
if (!CS.getArgument(i)->getType()->isPointerTy())
|
||||
continue;
|
||||
|
||||
// Call site argument attribute "non-null".
|
||||
registerAA(*new AANonNullCallSiteArgument(CS, i, InfoCache), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,34 @@
|
||||
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s
|
||||
; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s
|
||||
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s --check-prefixes=BOTH,FNATTR
|
||||
; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s --check-prefixes=BOTH,FNATTR
|
||||
; RUN: opt -attributor --attributor-disable=false -S < %s | FileCheck %s --check-prefixes=BOTH,ATTRIBUTOR
|
||||
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
declare nonnull i8* @ret_nonnull()
|
||||
|
||||
; Return a pointer trivially nonnull (call return attribute)
|
||||
define i8* @test1() {
|
||||
; CHECK: define nonnull i8* @test1
|
||||
; BOTH: define nonnull i8* @test1
|
||||
%ret = call i8* @ret_nonnull()
|
||||
ret i8* %ret
|
||||
}
|
||||
|
||||
; Return a pointer trivially nonnull (argument attribute)
|
||||
define i8* @test2(i8* nonnull %p) {
|
||||
; CHECK: define nonnull i8* @test2
|
||||
; BOTH: define nonnull i8* @test2
|
||||
ret i8* %p
|
||||
}
|
||||
|
||||
; Given an SCC where one of the functions can not be marked nonnull,
|
||||
; can we still mark the other one which is trivially nonnull
|
||||
define i8* @scc_binder() {
|
||||
; CHECK: define i8* @scc_binder
|
||||
; BOTH: define i8* @scc_binder
|
||||
call i8* @test3()
|
||||
ret i8* null
|
||||
}
|
||||
|
||||
define i8* @test3() {
|
||||
; CHECK: define nonnull i8* @test3
|
||||
; BOTH: define nonnull i8* @test3
|
||||
call i8* @scc_binder()
|
||||
%ret = call i8* @ret_nonnull()
|
||||
ret i8* %ret
|
||||
@ -35,13 +38,15 @@ define i8* @test3() {
|
||||
; nonnull if neither can ever return null. (In this case, they
|
||||
; just never return period.)
|
||||
define i8* @test4_helper() {
|
||||
; CHECK: define noalias nonnull i8* @test4_helper
|
||||
; FNATTR: define noalias nonnull i8* @test4_helper
|
||||
; ATTRIBUTOR: define nonnull i8* @test4_helper
|
||||
%ret = call i8* @test4()
|
||||
ret i8* %ret
|
||||
}
|
||||
|
||||
define i8* @test4() {
|
||||
; CHECK: define noalias nonnull i8* @test4
|
||||
; FNATTR: define noalias nonnull i8* @test4
|
||||
; ATTRIBUTOR: define nonnull i8* @test4
|
||||
%ret = call i8* @test4_helper()
|
||||
ret i8* %ret
|
||||
}
|
||||
@ -49,13 +54,15 @@ define i8* @test4() {
|
||||
; Given a mutual recursive set of functions which *can* return null
|
||||
; make sure we haven't marked them as nonnull.
|
||||
define i8* @test5_helper() {
|
||||
; CHECK: define noalias i8* @test5_helper
|
||||
; FNATTR: define noalias i8* @test5_helper
|
||||
; ATTRIBUTOR: define i8* @test5_helper
|
||||
%ret = call i8* @test5()
|
||||
ret i8* null
|
||||
}
|
||||
|
||||
define i8* @test5() {
|
||||
; CHECK: define noalias i8* @test5
|
||||
; FNATTR: define noalias i8* @test5
|
||||
; ATTRIBUTOR: define i8* @test5
|
||||
%ret = call i8* @test5_helper()
|
||||
ret i8* %ret
|
||||
}
|
||||
@ -63,7 +70,7 @@ define i8* @test5() {
|
||||
; Local analysis, but going through a self recursive phi
|
||||
define i8* @test6() {
|
||||
entry:
|
||||
; CHECK: define nonnull i8* @test6
|
||||
; BOTH: define nonnull i8* @test6
|
||||
%ret = call i8* @ret_nonnull()
|
||||
br label %loop
|
||||
loop:
|
||||
@ -73,6 +80,144 @@ exit:
|
||||
ret i8* %phi
|
||||
}
|
||||
|
||||
; BOTH: define i8* @test7
|
||||
define i8* @test7(i8* %a) {
|
||||
%b = getelementptr inbounds i8, i8* %a, i64 0
|
||||
ret i8* %b
|
||||
}
|
||||
|
||||
; BOTH: define nonnull i8* @test8
|
||||
define i8* @test8(i8* %a) {
|
||||
%b = getelementptr inbounds i8, i8* %a, i64 1
|
||||
ret i8* %b
|
||||
}
|
||||
|
||||
; BOTH: define i8* @test9
|
||||
define i8* @test9(i8* %a, i64 %n) {
|
||||
%b = getelementptr inbounds i8, i8* %a, i64 %n
|
||||
ret i8* %b
|
||||
}
|
||||
|
||||
declare void @llvm.assume(i1)
|
||||
; FNATTR: define i8* @test10
|
||||
; FIXME: missing nonnull
|
||||
; ATTRIBUTOR: define i8* @test10
|
||||
define i8* @test10(i8* %a, i64 %n) {
|
||||
%cmp = icmp ne i64 %n, 0
|
||||
call void @llvm.assume(i1 %cmp)
|
||||
%b = getelementptr inbounds i8, i8* %a, i64 %n
|
||||
ret i8* %b
|
||||
}
|
||||
|
||||
; TEST 11
|
||||
; char* test11(char *p) {
|
||||
; return p? p: nonnull();
|
||||
; }
|
||||
; FNATTR: define i8* @test11
|
||||
; FIXME: missing nonnull
|
||||
; ATTRIBUTOR: define i8* @test11
|
||||
define i8* @test11(i8*) local_unnamed_addr {
|
||||
%2 = icmp eq i8* %0, null
|
||||
br i1 %2, label %3, label %5
|
||||
|
||||
; <label>:3: ; preds = %1
|
||||
%4 = tail call i8* @ret_nonnull()
|
||||
br label %5
|
||||
|
||||
; <label>:5: ; preds = %3, %1
|
||||
%6 = phi i8* [ %4, %3 ], [ %0, %1 ]
|
||||
ret i8* %6
|
||||
}
|
||||
|
||||
; TEST 12
|
||||
; Simple CallSite Test
|
||||
declare void @test12_helper(i8*)
|
||||
define void @test12(i8* nonnull %a) {
|
||||
; ATTRIBUTOR: define void @test12(i8* nonnull %a)
|
||||
; ATTRIBUTOR-NEXT: tail call void @test12_helper(i8* nonnull %a)
|
||||
tail call void @test12_helper(i8* %a)
|
||||
ret void
|
||||
}
|
||||
|
||||
; TEST 13
|
||||
; Simple Argument Tests
|
||||
declare i8* @unknown()
|
||||
define void @test13_helper() {
|
||||
%nonnullptr = tail call i8* @ret_nonnull()
|
||||
%maybenullptr = tail call i8* @unknown()
|
||||
tail call void @test13(i8* %nonnullptr, i8* %nonnullptr, i8* %maybenullptr)
|
||||
tail call void @test13(i8* %nonnullptr, i8* %maybenullptr, i8* %nonnullptr)
|
||||
ret void
|
||||
}
|
||||
define internal void @test13(i8* %a, i8* %b, i8* %c) {
|
||||
; ATTRIBUTOR: define internal void @test13(i8* nonnull %a, i8* %b, i8* %c)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare nonnull i8* @nonnull()
|
||||
|
||||
; TEST 14
|
||||
; Complex propagation
|
||||
; Argument of f1, f2, f3 can be marked with nonnull.
|
||||
|
||||
; * Argument
|
||||
; 1. In f1:bb6, %arg can be marked with nonnull because of the comparison in bb1
|
||||
; 2. Because f2 is internal function, f2(i32* %arg) -> @f2(i32* nonnull %arg)
|
||||
; 3. In f1:bb4 %tmp5 is nonnull and f3 is internal function.
|
||||
; Then, f3(i32* %arg) -> @f3(i32* nonnull %arg)
|
||||
; 4. We get nonnull in whole f1 call sites so f1(i32* %arg) -> @f1(i32* nonnull %arg)
|
||||
|
||||
|
||||
define internal i32* @f1(i32* %arg) {
|
||||
; FIXME: missing nonnull It should be nonnull @f1(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: define internal nonnull i32* @f1(i32* %arg)
|
||||
|
||||
bb:
|
||||
%tmp = icmp eq i32* %arg, null
|
||||
br i1 %tmp, label %bb9, label %bb1
|
||||
|
||||
bb1: ; preds = %bb
|
||||
%tmp2 = load i32, i32* %arg, align 4
|
||||
%tmp3 = icmp eq i32 %tmp2, 0
|
||||
br i1 %tmp3, label %bb6, label %bb4
|
||||
|
||||
bb4: ; preds = %bb1
|
||||
%tmp5 = getelementptr inbounds i32, i32* %arg, i64 1
|
||||
; ATTRIBUTOR: %tmp5b = tail call i32* @f3(i32* nonnull %tmp5)
|
||||
%tmp5b = tail call i32* @f3(i32* %tmp5)
|
||||
br label %bb9
|
||||
|
||||
bb6: ; preds = %bb1
|
||||
; FIXME: missing nonnull. It should be @f2(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: %tmp7 = tail call i32* @f2(i32* %arg)
|
||||
%tmp7 = tail call i32* @f2(i32* %arg)
|
||||
ret i32* %tmp7
|
||||
|
||||
bb9: ; preds = %bb4, %bb
|
||||
%tmp10 = phi i32* [ %tmp5, %bb4 ], [ inttoptr (i64 4 to i32*), %bb ]
|
||||
ret i32* %tmp10
|
||||
}
|
||||
|
||||
define internal i32* @f2(i32* %arg) {
|
||||
; FIXME: missing nonnull. It should be nonnull @f2(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: define internal nonnull i32* @f2(i32* %arg)
|
||||
bb:
|
||||
|
||||
; FIXME: missing nonnull. It should be @f1(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: %tmp = tail call i32* @f1(i32* %arg)
|
||||
%tmp = tail call i32* @f1(i32* %arg)
|
||||
ret i32* %tmp
|
||||
}
|
||||
|
||||
define dso_local noalias i32* @f3(i32* %arg) {
|
||||
; FIXME: missing nonnull. It should be nonnull @f3(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: define dso_local noalias i32* @f3(i32* %arg)
|
||||
bb:
|
||||
; FIXME: missing nonnull. It should be @f1(i32* nonnull %arg)
|
||||
; ATTRIBUTOR: %tmp = call i32* @f1(i32* %arg)
|
||||
%tmp = call i32* @f1(i32* %arg)
|
||||
ret i32* null
|
||||
}
|
||||
; Test propagation of nonnull callsite args back to caller.
|
||||
|
||||
declare void @use1(i8* %x)
|
||||
@ -88,11 +233,11 @@ declare i8 @use1safecall(i8* %x) readonly nounwind ; readonly+nounwind guarantee
|
||||
; Can't extend non-null to parent for any argument because the 2nd call is not guaranteed to execute.
|
||||
|
||||
define void @parent1(i8* %a, i8* %b, i8* %c) {
|
||||
; CHECK-LABEL: @parent1(i8* %a, i8* %b, i8* %c)
|
||||
; CHECK-NEXT: call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
; CHECK-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a)
|
||||
; CHECK-NEXT: ret void
|
||||
;
|
||||
; BOTH-LABEL: @parent1(i8* %a, i8* %b, i8* %c)
|
||||
; BOTH-NEXT: call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
; FNATTR-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a)
|
||||
; ATTRIBUTOR-NEXT: call void @use3nonnull(i8* nonnull %b, i8* nonnull %c, i8* nonnull %a)
|
||||
; BOTH-NEXT: ret void
|
||||
call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
call void @use3nonnull(i8* %b, i8* %c, i8* %a)
|
||||
ret void
|
||||
@ -101,11 +246,20 @@ define void @parent1(i8* %a, i8* %b, i8* %c) {
|
||||
; Extend non-null to parent for all arguments.
|
||||
|
||||
define void @parent2(i8* %a, i8* %b, i8* %c) {
|
||||
; CHECK-LABEL: @parent2(i8* nonnull %a, i8* nonnull %b, i8* nonnull %c)
|
||||
; CHECK-NEXT: call void @use3nonnull(i8* %b, i8* %c, i8* %a)
|
||||
; CHECK-NEXT: call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
; CHECK-NEXT: ret void
|
||||
;
|
||||
; FNATTR-LABEL: @parent2(i8* nonnull %a, i8* nonnull %b, i8* nonnull %c)
|
||||
; 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-NEXT: call void @use3nonnull(i8* nonnull %b, i8* nonnull %c, i8* nonnull %a)
|
||||
; ATTRIBUTOR-NEXT: call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
|
||||
; BOTH-NEXT: ret void
|
||||
call void @use3nonnull(i8* %b, i8* %c, i8* %a)
|
||||
call void @use3(i8* %c, i8* %a, i8* %b)
|
||||
ret void
|
||||
@ -114,11 +268,20 @@ define void @parent2(i8* %a, i8* %b, i8* %c) {
|
||||
; Extend non-null to parent for 1st argument.
|
||||
|
||||
define void @parent3(i8* %a, i8* %b, i8* %c) {
|
||||
; CHECK-LABEL: @parent3(i8* nonnull %a, i8* %b, i8* %c)
|
||||
; CHECK-NEXT: call void @use1nonnull(i8* %a)
|
||||
; CHECK-NEXT: call void @use3(i8* %c, i8* %b, i8* %a)
|
||||
; CHECK-NEXT: ret void
|
||||
;
|
||||
; FNATTR-LABEL: @parent3(i8* nonnull %a, i8* %b, i8* %c)
|
||||
; 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-NEXT: call void @use1nonnull(i8* nonnull %a)
|
||||
; ATTRIBUTOR-NEXT: call void @use3(i8* %c, i8* %b, i8* %a)
|
||||
|
||||
; BOTH-NEXT: ret void
|
||||
|
||||
call void @use1nonnull(i8* %a)
|
||||
call void @use3(i8* %c, i8* %b, i8* %a)
|
||||
ret void
|
||||
@ -131,8 +294,20 @@ define void @parent4(i8* %a, i8* %b, i8* %c) {
|
||||
; CHECK-NEXT: call void @use2nonnull(i8* %c, i8* %b)
|
||||
; CHECK-NEXT: call void @use2(i8* %a, i8* %c)
|
||||
; CHECK-NEXT: call void @use1(i8* %b)
|
||||
; CHECK-NEXT: ret void
|
||||
;
|
||||
|
||||
; 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-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)
|
||||
|
||||
; BOTH: ret void
|
||||
|
||||
call void @use2nonnull(i8* %c, i8* %b)
|
||||
call void @use2(i8* %a, i8* %c)
|
||||
call void @use1(i8* %b)
|
||||
@ -144,14 +319,15 @@ define void @parent4(i8* %a, i8* %b, i8* %c) {
|
||||
; because it would incorrectly propagate the wrong information to its callers.
|
||||
|
||||
define void @parent5(i8* %a, i1 %a_is_notnull) {
|
||||
; CHECK-LABEL: @parent5(i8* %a, i1 %a_is_notnull)
|
||||
; CHECK-NEXT: br i1 %a_is_notnull, label %t, label %f
|
||||
; CHECK: t:
|
||||
; CHECK-NEXT: call void @use1nonnull(i8* %a)
|
||||
; CHECK-NEXT: ret void
|
||||
; CHECK: f:
|
||||
; CHECK-NEXT: ret void
|
||||
;
|
||||
; BOTH: @parent5(i8* %a, i1 %a_is_notnull)
|
||||
; BOTH-NEXT: br i1 %a_is_notnull, label %t, label %f
|
||||
; BOTH: t:
|
||||
; FNATTR-NEXT: call void @use1nonnull(i8* %a)
|
||||
; ATTRIBUTOR-NEXT: call void @use1nonnull(i8* nonnull %a)
|
||||
; BOTH-NEXT: ret void
|
||||
; BOTH: f:
|
||||
; BOTH-NEXT: ret void
|
||||
|
||||
br i1 %a_is_notnull, label %t, label %f
|
||||
t:
|
||||
call void @use1nonnull(i8* %a)
|
||||
@ -164,11 +340,12 @@ f:
|
||||
; The volatile load might trap, so there's no guarantee that we'll ever get to the call.
|
||||
|
||||
define i8 @parent6(i8* %a, i8* %b) {
|
||||
; CHECK-LABEL: @parent6(i8* %a, i8* %b)
|
||||
; CHECK-NEXT: [[C:%.*]] = load volatile i8, i8* %b
|
||||
; CHECK-NEXT: call void @use1nonnull(i8* %a)
|
||||
; CHECK-NEXT: ret i8 [[C]]
|
||||
;
|
||||
; BOTH-LABEL: @parent6(i8* %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)
|
||||
; BOTH-NEXT: ret i8 [[C]]
|
||||
|
||||
%c = load volatile i8, i8* %b
|
||||
call void @use1nonnull(i8* %a)
|
||||
ret i8 %c
|
||||
@ -177,11 +354,22 @@ define i8 @parent6(i8* %a, i8* %b) {
|
||||
; The nonnull callsite is guaranteed to execute, so the argument must be nonnull throughout the parent.
|
||||
|
||||
define i8 @parent7(i8* %a) {
|
||||
; CHECK-LABEL: @parent7(i8* nonnull %a)
|
||||
; CHECK-NEXT: [[RET:%.*]] = call i8 @use1safecall(i8* %a)
|
||||
; CHECK-NEXT: call void @use1nonnull(i8* %a)
|
||||
; CHECK-NEXT: ret i8 [[RET]]
|
||||
;
|
||||
; FNATTR-LABEL: @parent7(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-NEXT: call void @use1nonnull(i8* nonnull %a)
|
||||
|
||||
; BOTH-NEXT: ret i8 [[RET]]
|
||||
|
||||
%ret = call i8 @use1safecall(i8* %a)
|
||||
call void @use1nonnull(i8* %a)
|
||||
ret i8 %ret
|
||||
@ -192,18 +380,21 @@ define i8 @parent7(i8* %a) {
|
||||
declare i32 @esfp(...)
|
||||
|
||||
define i1 @parent8(i8* %a, i8* %bogus1, i8* %b) personality i8* bitcast (i32 (...)* @esfp to i8*){
|
||||
; CHECK-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b)
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: invoke void @use2nonnull(i8* %a, i8* %b)
|
||||
; CHECK-NEXT: to label %cont unwind label %exc
|
||||
; CHECK: cont:
|
||||
; CHECK-NEXT: [[NULL_CHECK:%.*]] = icmp eq i8* %b, null
|
||||
; CHECK-NEXT: ret i1 [[NULL_CHECK]]
|
||||
; CHECK: exc:
|
||||
; CHECK-NEXT: [[LP:%.*]] = landingpad { i8*, i32 }
|
||||
; CHECK-NEXT: filter [0 x i8*] zeroinitializer
|
||||
; CHECK-NEXT: unreachable
|
||||
;
|
||||
; 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)
|
||||
; BOTH-NEXT: entry:
|
||||
; FNATTR-NEXT: invoke void @use2nonnull(i8* %a, i8* %b)
|
||||
; ATTRIBUTOR-NEXT: invoke void @use2nonnull(i8* nonnull %a, i8* nonnull %b)
|
||||
; BOTH-NEXT: to label %cont unwind label %exc
|
||||
; BOTH: cont:
|
||||
; BOTH-NEXT: [[NULL_CHECK:%.*]] = icmp eq i8* %b, null
|
||||
; BOTH-NEXT: ret i1 [[NULL_CHECK]]
|
||||
; BOTH: exc:
|
||||
; BOTH-NEXT: [[LP:%.*]] = landingpad { i8*, i32 }
|
||||
; BOTH-NEXT: filter [0 x i8*] zeroinitializer
|
||||
; BOTH-NEXT: unreachable
|
||||
|
||||
entry:
|
||||
invoke void @use2nonnull(i8* %a, i8* %b)
|
||||
to label %cont unwind label %exc
|
||||
@ -218,7 +409,7 @@ exc:
|
||||
unreachable
|
||||
}
|
||||
|
||||
; CHECK: define nonnull i32* @gep1(
|
||||
; BOTH: define nonnull i32* @gep1(
|
||||
define i32* @gep1(i32* %p) {
|
||||
%q = getelementptr inbounds i32, i32* %p, i32 1
|
||||
ret i32* %q
|
||||
@ -226,24 +417,24 @@ define i32* @gep1(i32* %p) {
|
||||
|
||||
define i32* @gep1_no_null_opt(i32* %p) #0 {
|
||||
; Should't be able to derive nonnull based on gep.
|
||||
; CHECK: define i32* @gep1_no_null_opt(
|
||||
; BOTH: define i32* @gep1_no_null_opt(
|
||||
%q = getelementptr inbounds i32, i32* %p, i32 1
|
||||
ret i32* %q
|
||||
}
|
||||
|
||||
; CHECK: define i32 addrspace(3)* @gep2(
|
||||
; BOTH: define i32 addrspace(3)* @gep2(
|
||||
define i32 addrspace(3)* @gep2(i32 addrspace(3)* %p) {
|
||||
%q = getelementptr inbounds i32, i32 addrspace(3)* %p, i32 1
|
||||
ret i32 addrspace(3)* %q
|
||||
}
|
||||
|
||||
; CHECK: define internal nonnull i32* @f2()
|
||||
define internal i32* @f2() {
|
||||
; BOTH: define internal nonnull i32* @g2()
|
||||
define internal i32* @g2() {
|
||||
ret i32* inttoptr (i64 4 to i32*)
|
||||
}
|
||||
|
||||
define i32* @f1() {
|
||||
%c = call i32* @f2()
|
||||
define i32* @g1() {
|
||||
%c = call i32* @g2()
|
||||
ret i32* %c
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
; FNATTR: Function Attrs: norecurse nounwind optsize readnone ssp uwtable
|
||||
; FNATTR-NEXT: define nonnull i32* @foo(%struct.ST* readnone %s)
|
||||
; ATTRIBUTOR: Function Attrs: nofree nosync nounwind optsize readnone ssp uwtable
|
||||
; ATTRIBUTOR-NEXT: define i32* @foo(%struct.ST* %s)
|
||||
; ATTRIBUTOR-NEXT: define nonnull i32* @foo(%struct.ST* %s)
|
||||
define i32* @foo(%struct.ST* %s) nounwind uwtable readnone optsize ssp {
|
||||
entry:
|
||||
%arrayidx = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 1, i32 2, i32 1, i64 5, i64 13
|
||||
|
Loading…
x
Reference in New Issue
Block a user