forked from OSchip/llvm-project
[Attributor] Deduce "align" attribute
Summary: Deduce "align" attribute in attributor. Reviewers: jdoerfert, sstefan1 Reviewed By: jdoerfert Subscribers: hiraditya, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D64152 llvm-svn: 367187
This commit is contained in:
parent
bd68a052f2
commit
e7bea9b73a
|
@ -905,6 +905,31 @@ struct AADereferenceable : public AbstractAttribute {
|
|||
static constexpr Attribute::AttrKind ID = Attribute::Dereferenceable;
|
||||
};
|
||||
|
||||
/// An abstract interface for all align attributes.
|
||||
struct AAAlign : public AbstractAttribute {
|
||||
|
||||
/// See AbstractAttribute::AbstractAttribute(...).
|
||||
AAAlign(Value &V, InformationCache &InfoCache)
|
||||
: AbstractAttribute(V, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::AbstractAttribute(...).
|
||||
AAAlign(Value *AssociatedVal, Value &AnchoredValue,
|
||||
InformationCache &InfoCache)
|
||||
: AbstractAttribute(AssociatedVal, AnchoredValue, InfoCache) {}
|
||||
|
||||
/// See AbastractState::getAttrKind().
|
||||
Attribute::AttrKind getAttrKind() const override { return ID; }
|
||||
|
||||
/// Return assumed alignment.
|
||||
virtual unsigned getAssumedAlign() const = 0;
|
||||
|
||||
/// Return known alignemnt.
|
||||
virtual unsigned getKnownAlign() const = 0;
|
||||
|
||||
/// The identifier used by the Attributor for this class of attributes.
|
||||
static constexpr Attribute::AttrKind ID = Attribute::Alignment;
|
||||
};
|
||||
|
||||
} // end namespace llvm
|
||||
|
||||
#endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H
|
||||
|
|
|
@ -72,6 +72,9 @@ STATISTIC(NumFnArgumentDereferenceable,
|
|||
"Number of function arguments marked dereferenceable");
|
||||
STATISTIC(NumCSArgumentDereferenceable,
|
||||
"Number of call site arguments marked dereferenceable");
|
||||
STATISTIC(NumFnReturnedAlign, "Number of function return values marked align");
|
||||
STATISTIC(NumFnArgumentAlign, "Number of function arguments marked align");
|
||||
STATISTIC(NumCSArgumentAlign, "Number of call site arguments marked align");
|
||||
|
||||
// TODO: Determine a good default value.
|
||||
//
|
||||
|
@ -115,6 +118,21 @@ static void bookkeeping(AbstractAttribute::ManifestPosition MP,
|
|||
return;
|
||||
|
||||
switch (Attr.getKindAsEnum()) {
|
||||
case Attribute::Alignment:
|
||||
switch (MP) {
|
||||
case AbstractAttribute::MP_RETURNED:
|
||||
NumFnReturnedAlign++;
|
||||
break;
|
||||
case AbstractAttribute::MP_ARGUMENT:
|
||||
NumFnArgumentAlign++;
|
||||
break;
|
||||
case AbstractAttribute::MP_CALL_SITE_ARGUMENT:
|
||||
NumCSArgumentAlign++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Attribute::Dereferenceable:
|
||||
switch (MP) {
|
||||
case AbstractAttribute::MP_RETURNED:
|
||||
|
@ -1974,6 +1992,212 @@ ChangeStatus AADereferenceableCallSiteArgument::updateImpl(Attributor &A) {
|
|||
: ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
// ------------------------ Align Argument Attribute ------------------------
|
||||
|
||||
struct AAAlignImpl : AAAlign, IntegerState {
|
||||
|
||||
// Max alignemnt value allowed in IR
|
||||
static const unsigned MAX_ALIGN = 1U << 29;
|
||||
|
||||
AAAlignImpl(Value *AssociatedVal, Value &AnchoredValue,
|
||||
InformationCache &InfoCache)
|
||||
: AAAlign(AssociatedVal, AnchoredValue, InfoCache),
|
||||
IntegerState(MAX_ALIGN) {}
|
||||
|
||||
AAAlignImpl(Value &V, InformationCache &InfoCache)
|
||||
: AAAlignImpl(&V, V, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getState()
|
||||
/// {
|
||||
AbstractState &getState() override { return *this; }
|
||||
const AbstractState &getState() const override { return *this; }
|
||||
/// }
|
||||
|
||||
virtual const std::string getAsStr() const override {
|
||||
return getAssumedAlign() ? ("align<" + std::to_string(getKnownAlign()) +
|
||||
"-" + std::to_string(getAssumedAlign()) + ">")
|
||||
: "unknown-align";
|
||||
}
|
||||
|
||||
/// See AAAlign::getAssumedAlign().
|
||||
unsigned getAssumedAlign() const override { return getAssumed(); }
|
||||
|
||||
/// See AAAlign::getKnownAlign().
|
||||
unsigned getKnownAlign() const override { return getKnown(); }
|
||||
|
||||
/// See AbstractAttriubute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
Function &F = getAnchorScope();
|
||||
|
||||
unsigned AttrIdx =
|
||||
getAttrIndex(getManifestPosition(), getArgNo(getAnchoredValue()));
|
||||
|
||||
// Already the function has align attribute on return value or argument.
|
||||
if (F.getAttributes().hasAttribute(AttrIdx, ID))
|
||||
addKnownBits(F.getAttribute(AttrIdx, ID).getAlignment());
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::getDeducedAttributes
|
||||
virtual void
|
||||
getDeducedAttributes(SmallVectorImpl<Attribute> &Attrs) const override {
|
||||
LLVMContext &Ctx = AnchoredVal.getContext();
|
||||
|
||||
Attrs.emplace_back(Attribute::getWithAlignment(Ctx, getAssumedAlign()));
|
||||
}
|
||||
};
|
||||
|
||||
/// Align attribute for function return value.
|
||||
struct AAAlignReturned : AAAlignImpl {
|
||||
|
||||
AAAlignReturned(Function &F, InformationCache &InfoCache)
|
||||
: AAAlignImpl(F, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
virtual ManifestPosition getManifestPosition() const override {
|
||||
return MP_RETURNED;
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(...).
|
||||
virtual ChangeStatus updateImpl(Attributor &A) override;
|
||||
};
|
||||
|
||||
ChangeStatus AAAlignReturned::updateImpl(Attributor &A) {
|
||||
Function &F = getAnchorScope();
|
||||
auto *AARetValImpl = A.getAAFor<AAReturnedValuesImpl>(*this, F);
|
||||
if (!AARetValImpl) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
// Currently, align<n> is deduced if alignments in return values are assumed
|
||||
// as greater than n. We reach pessimistic fixpoint if any of the return value
|
||||
// wouldn't have align. If no assumed state was used for reasoning, an
|
||||
// optimistic fixpoint is reached earlier.
|
||||
|
||||
base_t BeforeState = getAssumed();
|
||||
std::function<bool(Value &)> Pred = [&](Value &RV) -> bool {
|
||||
auto *AlignAA = A.getAAFor<AAAlign>(*this, RV);
|
||||
|
||||
if (AlignAA)
|
||||
takeAssumedMinimum(AlignAA->getAssumedAlign());
|
||||
else
|
||||
// Use IR information.
|
||||
takeAssumedMinimum(RV.getPointerAlignment(
|
||||
getAnchorScope().getParent()->getDataLayout()));
|
||||
|
||||
return isValidState();
|
||||
};
|
||||
|
||||
if (!AARetValImpl->checkForallReturnedValues(Pred)) {
|
||||
indicatePessimisticFixpoint();
|
||||
return ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
return (getAssumed() != BeforeState) ? ChangeStatus::CHANGED
|
||||
: ChangeStatus::UNCHANGED;
|
||||
}
|
||||
|
||||
/// Align attribute for function argument.
|
||||
struct AAAlignArgument : AAAlignImpl {
|
||||
|
||||
AAAlignArgument(Argument &A, InformationCache &InfoCache)
|
||||
: AAAlignImpl(A, InfoCache) {}
|
||||
|
||||
/// See AbstractAttribute::getManifestPosition().
|
||||
virtual ManifestPosition getManifestPosition() const override {
|
||||
return MP_ARGUMENT;
|
||||
}
|
||||
|
||||
/// See AbstractAttribute::updateImpl(...).
|
||||
virtual ChangeStatus updateImpl(Attributor &A) override;
|
||||
};
|
||||
|
||||
ChangeStatus AAAlignArgument::updateImpl(Attributor &A) {
|
||||
|
||||
Function &F = getAnchorScope();
|
||||
Argument &Arg = cast<Argument>(getAnchoredValue());
|
||||
|
||||
unsigned ArgNo = Arg.getArgNo();
|
||||
const DataLayout &DL = F.getParent()->getDataLayout();
|
||||
|
||||
auto BeforeState = getAssumed();
|
||||
|
||||
// Callback function
|
||||
std::function<bool(CallSite)> CallSiteCheck = [&](CallSite CS) {
|
||||
assert(CS && "Sanity check: Call site was not initialized properly!");
|
||||
|
||||
auto *AlignAA = A.getAAFor<AAAlign>(*this, *CS.getInstruction(), ArgNo);
|
||||
|
||||
// Check that AlignAA is AAAlignCallSiteArgument.
|
||||
if (AlignAA) {
|
||||
ImmutableCallSite ICS(&AlignAA->getAnchoredValue());
|
||||
if (ICS && CS.getInstruction() == ICS.getInstruction()) {
|
||||
takeAssumedMinimum(AlignAA->getAssumedAlign());
|
||||
return isValidState();
|
||||
}
|
||||
}
|
||||
|
||||
Value *V = CS.getArgOperand(ArgNo);
|
||||
takeAssumedMinimum(V->getPointerAlignment(DL));
|
||||
return isValidState();
|
||||
};
|
||||
|
||||
if (!A.checkForAllCallSites(F, CallSiteCheck, true))
|
||||
indicatePessimisticFixpoint();
|
||||
|
||||
return BeforeState == getAssumed() ? ChangeStatus::UNCHANGED
|
||||
: ChangeStatus ::CHANGED;
|
||||
}
|
||||
|
||||
struct AAAlignCallSiteArgument : AAAlignImpl {
|
||||
|
||||
/// See AANonNullImpl::AANonNullImpl(...).
|
||||
AAAlignCallSiteArgument(CallSite CS, unsigned ArgNo,
|
||||
InformationCache &InfoCache)
|
||||
: AAAlignImpl(CS.getArgOperand(ArgNo), *CS.getInstruction(), InfoCache),
|
||||
ArgNo(ArgNo) {}
|
||||
|
||||
/// See AbstractAttribute::initialize(...).
|
||||
void initialize(Attributor &A) override {
|
||||
CallSite CS(&getAnchoredValue());
|
||||
takeKnownMaximum(getAssociatedValue()->getPointerAlignment(
|
||||
getAnchorScope().getParent()->getDataLayout()));
|
||||
}
|
||||
|
||||
/// 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 AAAlignCallSiteArgument::updateImpl(Attributor &A) {
|
||||
// NOTE: Never look at the argument of the callee in this method.
|
||||
// If we do this, "align" is always deduced because of the assumption.
|
||||
|
||||
auto BeforeState = getAssumed();
|
||||
|
||||
Value &V = *getAssociatedValue();
|
||||
|
||||
auto *AlignAA = A.getAAFor<AAAlign>(*this, V);
|
||||
|
||||
if (AlignAA)
|
||||
takeAssumedMinimum(AlignAA->getAssumedAlign());
|
||||
else
|
||||
indicatePessimisticFixpoint();
|
||||
|
||||
return BeforeState == getAssumed() ? ChangeStatus::UNCHANGED
|
||||
: ChangeStatus::CHANGED;
|
||||
}
|
||||
|
||||
/// ----------------------------------------------------------------------------
|
||||
/// Attributor
|
||||
/// ----------------------------------------------------------------------------
|
||||
|
@ -2171,6 +2395,10 @@ void Attributor::identifyDefaultAbstractAttributes(
|
|||
registerAA(*new AAReturnedValuesImpl(F, InfoCache));
|
||||
|
||||
if (ReturnType->isPointerTy()) {
|
||||
// Every function with pointer return type might be marked align.
|
||||
if (!Whitelist || Whitelist->count(AAAlignReturned::ID))
|
||||
registerAA(*new AAAlignReturned(F, InfoCache));
|
||||
|
||||
// Every function with pointer return type might be marked nonnull.
|
||||
if (!Whitelist || Whitelist->count(AANonNullReturned::ID))
|
||||
registerAA(*new AANonNullReturned(F, InfoCache));
|
||||
|
@ -2196,6 +2424,10 @@ void Attributor::identifyDefaultAbstractAttributes(
|
|||
// Every argument with pointer type might be marked dereferenceable.
|
||||
if (!Whitelist || Whitelist->count(AADereferenceableArgument::ID))
|
||||
registerAA(*new AADereferenceableArgument(Arg, InfoCache));
|
||||
|
||||
// Every argument with pointer type might be marked align.
|
||||
if (!Whitelist || Whitelist->count(AAAlignArgument::ID))
|
||||
registerAA(*new AAAlignArgument(Arg, InfoCache));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2254,6 +2486,10 @@ void Attributor::identifyDefaultAbstractAttributes(
|
|||
Whitelist->count(AADereferenceableCallSiteArgument::ID))
|
||||
registerAA(*new AADereferenceableCallSiteArgument(CS, i, InfoCache),
|
||||
i);
|
||||
|
||||
// Call site argument attribute "align".
|
||||
if (!Whitelist || Whitelist->count(AAAlignCallSiteArgument::ID))
|
||||
registerAA(*new AAAlignCallSiteArgument(CS, i, InfoCache), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
|
||||
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
; Test cases specifically designed for "align" attribute.
|
||||
; We use FIXME's to indicate problems and missing attributes.
|
||||
|
||||
|
||||
; TEST 1
|
||||
; ATTRIBUTOR: define align 8 i32* @test1(i32* returned align 8)
|
||||
define i32* @test1(i32* align 8) #0 {
|
||||
ret i32* %0
|
||||
}
|
||||
|
||||
; TEST 2
|
||||
; ATTRIBUTOR: define i32* @test2(i32* returned)
|
||||
define i32* @test2(i32*) #0 {
|
||||
ret i32* %0
|
||||
}
|
||||
|
||||
; TEST 3
|
||||
; ATTRIBUTOR: define align 4 i32* @test3(i32* align 8, i32* align 4, i1)
|
||||
define i32* @test3(i32* align 8, i32* align 4, i1) #0 {
|
||||
%ret = select i1 %2, i32* %0, i32* %1
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
; TEST 4
|
||||
; ATTRIBUTOR: define align 32 i32* @test4(i32* align 32, i32* align 32, i1)
|
||||
define i32* @test4(i32* align 32, i32* align 32, i1) #0 {
|
||||
%ret = select i1 %2, i32* %0, i32* %1
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
; TEST 5
|
||||
declare i32* @unknown()
|
||||
declare align 8 i32* @align8()
|
||||
|
||||
|
||||
; ATTRIBUTOR: define align 8 i32* @test5_1()
|
||||
define i32* @test5_1() {
|
||||
%ret = tail call align 8 i32* @unknown()
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
; ATTRIBUTOR: define align 8 i32* @test5_2()
|
||||
define i32* @test5_2() {
|
||||
%ret = tail call i32* @align8()
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
; TEST 6
|
||||
; SCC
|
||||
; ATTRIBUTOR: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @test6_1()
|
||||
define i32* @test6_1() #0 {
|
||||
%ret = tail call i32* @test6_2()
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
; ATTRIBUTOR: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @test6_2()
|
||||
define i32* @test6_2() #0 {
|
||||
%ret = tail call i32* @test6_1()
|
||||
ret i32* %ret
|
||||
}
|
||||
|
||||
|
||||
; char a1 __attribute__((aligned(8)));
|
||||
; char a2 __attribute__((aligned(16)));
|
||||
;
|
||||
; char* f1(char* a ){
|
||||
; return a?a:f2(&a1);
|
||||
; }
|
||||
; char* f2(char* a){
|
||||
; return a?f1(a):f3(&a2);
|
||||
; }
|
||||
;
|
||||
; char* f3(char* a){
|
||||
; return a?&a1: f1(&a2);
|
||||
; }
|
||||
|
||||
@a1 = common global i8 0, align 8
|
||||
@a2 = common global i8 0, align 16
|
||||
|
||||
; Function Attrs: nounwind readnone ssp uwtable
|
||||
define internal i8* @f1(i8* readnone) local_unnamed_addr #0 {
|
||||
; ATTRIBUTOR: define internal nonnull align 8 i8* @f1(i8* nonnull readnone align 8)
|
||||
%2 = icmp eq i8* %0, null
|
||||
br i1 %2, label %3, label %5
|
||||
|
||||
; <label>:3: ; preds = %1
|
||||
; ATTRIBUTOR: %4 = tail call i8* @f2(i8* nonnull align 8 @a1)
|
||||
%4 = tail call i8* @f2(i8* nonnull @a1)
|
||||
br label %5
|
||||
|
||||
; <label>:5: ; preds = %1, %3
|
||||
%6 = phi i8* [ %4, %3 ], [ %0, %1 ]
|
||||
ret i8* %6
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind readnone ssp uwtable
|
||||
define internal i8* @f2(i8* readnone) local_unnamed_addr #0 {
|
||||
; ATTRIBUTOR: define internal nonnull align 8 i8* @f2(i8* nonnull readnone align 8)
|
||||
%2 = icmp eq i8* %0, null
|
||||
br i1 %2, label %5, label %3
|
||||
|
||||
; <label>:3: ; preds = %1
|
||||
|
||||
; ATTRIBUTOR: %4 = tail call i8* @f1(i8* nonnull align 8 %0)
|
||||
%4 = tail call i8* @f1(i8* nonnull %0)
|
||||
br label %7
|
||||
|
||||
; <label>:5: ; preds = %1
|
||||
; ATTRIBUTOR: %6 = tail call i8* @f3(i8* nonnull align 16 @a2)
|
||||
%6 = tail call i8* @f3(i8* nonnull @a2)
|
||||
br label %7
|
||||
|
||||
; <label>:7: ; preds = %5, %3
|
||||
%8 = phi i8* [ %4, %3 ], [ %6, %5 ]
|
||||
ret i8* %8
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind readnone ssp uwtable
|
||||
define internal i8* @f3(i8* readnone) local_unnamed_addr #0 {
|
||||
; ATTRIBUTOR: define internal nonnull align 8 i8* @f3(i8* nonnull readnone align 16)
|
||||
%2 = icmp eq i8* %0, null
|
||||
br i1 %2, label %3, label %5
|
||||
|
||||
; <label>:3: ; preds = %1
|
||||
; ATTRIBUTOR: %4 = tail call i8* @f1(i8* nonnull align 16 @a2)
|
||||
%4 = tail call i8* @f1(i8* nonnull @a2)
|
||||
br label %5
|
||||
|
||||
; <label>:5: ; preds = %1, %3
|
||||
%6 = phi i8* [ %4, %3 ], [ @a1, %1 ]
|
||||
ret i8* %6
|
||||
}
|
||||
|
||||
; TEST 7
|
||||
; Better than IR information
|
||||
; ATTRIBUTOR: define align 32 i32* @test7(i32* returned align 32 %p)
|
||||
define align 4 i32* @test7(i32* align 32 %p) #0 {
|
||||
ret i32* %p
|
||||
}
|
||||
|
||||
|
||||
; TEST 8
|
||||
define void @test8_helper() {
|
||||
%ptr0 = tail call i32* @unknown()
|
||||
%ptr1 = tail call align 4 i32* @unknown()
|
||||
%ptr2 = tail call align 8 i32* @unknown()
|
||||
|
||||
tail call void @test8(i32* %ptr1, i32* %ptr1, i32* %ptr0)
|
||||
; ATTRIBUTOR: tail call void @test8(i32* align 4 %ptr1, i32* align 4 %ptr1, i32* %ptr0)
|
||||
tail call void @test8(i32* %ptr2, i32* %ptr1, i32* %ptr1)
|
||||
; ATTRIBUTOR: tail call void @test8(i32* align 8 %ptr2, i32* align 4 %ptr1, i32* align 4 %ptr1)
|
||||
tail call void @test8(i32* %ptr2, i32* %ptr1, i32* %ptr1)
|
||||
; ATTRIBUTOR: tail call void @test8(i32* align 8 %ptr2, i32* align 4 %ptr1, i32* align 4 %ptr1)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @test8(i32* %a, i32* %b, i32* %c) {
|
||||
; ATTRIBUTOR: define internal void @test8(i32* align 4 %a, i32* align 4 %b, i32* %c)
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
attributes #0 = { nounwind uwtable noinline }
|
||||
attributes #1 = { uwtable noinline }
|
|
@ -88,7 +88,7 @@ entry:
|
|||
; Other arguments are possible here due to the no-return behavior.
|
||||
;
|
||||
; FIXME: no-return missing
|
||||
; CHECK: define noalias nonnull dereferenceable(4294967295) i32* @srec16(i32* nocapture readnone %a)
|
||||
; CHECK: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @srec16(i32* nocapture readnone %a)
|
||||
define i32* @srec16(i32* %a) #0 {
|
||||
entry:
|
||||
%call = call i32* @srec16(i32* %a)
|
||||
|
|
|
@ -79,13 +79,13 @@ declare i8* @baz(...) nounwind uwtable
|
|||
; TEST 5
|
||||
|
||||
; Returning global pointer. Should not be noalias.
|
||||
; CHECK: define nonnull dereferenceable(8) i8** @getter()
|
||||
; CHECK: define nonnull align 8 dereferenceable(8) i8** @getter()
|
||||
define i8** @getter() {
|
||||
ret i8** @G
|
||||
}
|
||||
|
||||
; Returning global pointer. Should not be noalias.
|
||||
; CHECK: define nonnull dereferenceable(8) i8** @calle1()
|
||||
; CHECK: define nonnull align 8 dereferenceable(8) i8** @calle1()
|
||||
define i8** @calle1(){
|
||||
%1 = call i8** @getter()
|
||||
ret i8** %1
|
||||
|
|
|
@ -40,14 +40,14 @@ define i8* @test3() {
|
|||
; just never return period.)
|
||||
define i8* @test4_helper() {
|
||||
; FNATTR: define noalias nonnull i8* @test4_helper
|
||||
; ATTRIBUTOR: define noalias nonnull dereferenceable(4294967295) i8* @test4_helper
|
||||
; ATTRIBUTOR: define noalias nonnull align 536870912 dereferenceable(4294967295) i8* @test4_helper
|
||||
%ret = call i8* @test4()
|
||||
ret i8* %ret
|
||||
}
|
||||
|
||||
define i8* @test4() {
|
||||
; FNATTR: define noalias nonnull i8* @test4
|
||||
; ATTRIBUTOR: define noalias nonnull dereferenceable(4294967295) i8* @test4
|
||||
; ATTRIBUTOR: define noalias nonnull align 536870912 dereferenceable(4294967295) i8* @test4
|
||||
%ret = call i8* @test4_helper()
|
||||
ret i8* %ret
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue