mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-31 20:51:52 +01:00
[Attributor] AAUndefinedBehavior: Check for branches on undef value.
A branch is considered UB if it depends on an undefined / uninitialized value. At this point this handles simple UB branches in the form: `br i1 undef, ...` We query `AAValueSimplify` to get a value for the branch condition, so the branch can be more complicated than just: `br i1 undef, ...`. Patch By: Stefanos Baziotis (@baziotis) Reviewers: jdoerfert, sstefan1, uenoku Reviewed By: uenoku Differential Revision: https://reviews.llvm.org/D71799
This commit is contained in:
parent
4edae66924
commit
df074b4b16
@ -823,6 +823,38 @@ struct Attributor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get pointer operand of memory accessing instruction. If \p I is
|
||||||
|
/// not a memory accessing instruction, return nullptr. If \p AllowVolatile,
|
||||||
|
/// is set to false and the instruction is volatile, return nullptr.
|
||||||
|
static const Value *getPointerOperand(const Instruction *I,
|
||||||
|
bool AllowVolatile) {
|
||||||
|
if (auto *LI = dyn_cast<LoadInst>(I)) {
|
||||||
|
if (!AllowVolatile && LI->isVolatile())
|
||||||
|
return nullptr;
|
||||||
|
return LI->getPointerOperand();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto *SI = dyn_cast<StoreInst>(I)) {
|
||||||
|
if (!AllowVolatile && SI->isVolatile())
|
||||||
|
return nullptr;
|
||||||
|
return SI->getPointerOperand();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(I)) {
|
||||||
|
if (!AllowVolatile && CXI->isVolatile())
|
||||||
|
return nullptr;
|
||||||
|
return CXI->getPointerOperand();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto *RMWI = dyn_cast<AtomicRMWInst>(I)) {
|
||||||
|
if (!AllowVolatile && RMWI->isVolatile())
|
||||||
|
return nullptr;
|
||||||
|
return RMWI->getPointerOperand();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/// Record that \p I is to be replaced with `unreachable` after information
|
/// Record that \p I is to be replaced with `unreachable` after information
|
||||||
/// was manifested.
|
/// was manifested.
|
||||||
void changeToUnreachableAfterManifest(Instruction *I) {
|
void changeToUnreachableAfterManifest(Instruction *I) {
|
||||||
@ -1710,6 +1742,9 @@ struct AAUndefinedBehavior
|
|||||||
/// Return true if "undefined behavior" is known.
|
/// Return true if "undefined behavior" is known.
|
||||||
bool isKnownToCauseUB() const { return getKnown(); }
|
bool isKnownToCauseUB() const { return getKnown(); }
|
||||||
|
|
||||||
|
/// Return true if "undefined behavior" is known for a specific instruction.
|
||||||
|
virtual bool isKnownToCauseUB(Instruction *I) const = 0;
|
||||||
|
|
||||||
/// Return an IR position, see struct IRPosition.
|
/// Return an IR position, see struct IRPosition.
|
||||||
const IRPosition &getIRPosition() const override { return *this; }
|
const IRPosition &getIRPosition() const override { return *this; }
|
||||||
|
|
||||||
|
@ -332,30 +332,13 @@ static bool addIfNotExistent(LLVMContext &Ctx, const Attribute &Attr,
|
|||||||
|
|
||||||
llvm_unreachable("Expected enum or string attribute!");
|
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 *
|
static const Value *
|
||||||
getBasePointerOfAccessPointerOperand(const Instruction *I, int64_t &BytesOffset,
|
getBasePointerOfAccessPointerOperand(const Instruction *I, int64_t &BytesOffset,
|
||||||
const DataLayout &DL,
|
const DataLayout &DL,
|
||||||
bool AllowNonInbounds = false) {
|
bool AllowNonInbounds = false) {
|
||||||
const Value *Ptr = getPointerOperand(I);
|
const Value *Ptr =
|
||||||
|
Attributor::getPointerOperand(I, /* AllowVolatile */ false);
|
||||||
if (!Ptr)
|
if (!Ptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@ -1734,7 +1717,8 @@ static int64_t getKnownNonNullAndDerefBytesForUse(
|
|||||||
|
|
||||||
int64_t Offset;
|
int64_t Offset;
|
||||||
if (const Value *Base = getBasePointerOfAccessPointerOperand(I, Offset, DL)) {
|
if (const Value *Base = getBasePointerOfAccessPointerOperand(I, Offset, DL)) {
|
||||||
if (Base == &AssociatedValue && getPointerOperand(I) == UseV) {
|
if (Base == &AssociatedValue &&
|
||||||
|
Attributor::getPointerOperand(I, /* AllowVolatile */ false) == UseV) {
|
||||||
int64_t DerefBytes =
|
int64_t DerefBytes =
|
||||||
(int64_t)DL.getTypeStoreSize(PtrTy->getPointerElementType()) + Offset;
|
(int64_t)DL.getTypeStoreSize(PtrTy->getPointerElementType()) + Offset;
|
||||||
|
|
||||||
@ -1747,7 +1731,7 @@ static int64_t getKnownNonNullAndDerefBytesForUse(
|
|||||||
if (const Value *Base = getBasePointerOfAccessPointerOperand(
|
if (const Value *Base = getBasePointerOfAccessPointerOperand(
|
||||||
I, Offset, DL, /*AllowNonInbounds*/ true)) {
|
I, Offset, DL, /*AllowNonInbounds*/ true)) {
|
||||||
if (Offset == 0 && Base == &AssociatedValue &&
|
if (Offset == 0 && Base == &AssociatedValue &&
|
||||||
getPointerOperand(I) == UseV) {
|
Attributor::getPointerOperand(I, /* AllowVolatile */ false) == UseV) {
|
||||||
int64_t DerefBytes =
|
int64_t DerefBytes =
|
||||||
(int64_t)DL.getTypeStoreSize(PtrTy->getPointerElementType());
|
(int64_t)DL.getTypeStoreSize(PtrTy->getPointerElementType());
|
||||||
IsNonNull |= !NullPointerIsDefined;
|
IsNonNull |= !NullPointerIsDefined;
|
||||||
@ -1993,28 +1977,29 @@ struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior {
|
|||||||
AAUndefinedBehaviorImpl(const IRPosition &IRP) : AAUndefinedBehavior(IRP) {}
|
AAUndefinedBehaviorImpl(const IRPosition &IRP) : AAUndefinedBehavior(IRP) {}
|
||||||
|
|
||||||
/// See AbstractAttribute::updateImpl(...).
|
/// See AbstractAttribute::updateImpl(...).
|
||||||
// TODO: We should not only check instructions that access memory
|
|
||||||
// through a pointer (i.e. also branches etc.)
|
// through a pointer (i.e. also branches etc.)
|
||||||
ChangeStatus updateImpl(Attributor &A) override {
|
ChangeStatus updateImpl(Attributor &A) override {
|
||||||
const size_t PrevSize = NoUBMemAccessInsts.size();
|
const size_t UBPrevSize = KnownUBInsts.size();
|
||||||
|
const size_t NoUBPrevSize = AssumedNoUBInsts.size();
|
||||||
|
|
||||||
auto InspectMemAccessInstForUB = [&](Instruction &I) {
|
auto InspectMemAccessInstForUB = [&](Instruction &I) {
|
||||||
// Skip instructions that are already saved.
|
// Skip instructions that are already saved.
|
||||||
if (NoUBMemAccessInsts.count(&I) || UBMemAccessInsts.count(&I))
|
if (AssumedNoUBInsts.count(&I) || KnownUBInsts.count(&I))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// `InspectMemAccessInstForUB` is only called on instructions
|
// If we reach here, we know we have an instruction
|
||||||
// for which getPointerOperand() should give us their
|
// that accesses memory through a pointer operand,
|
||||||
// pointer operand unless they're volatile.
|
// for which getPointerOperand() should give it to us.
|
||||||
const Value *PtrOp = getPointerOperand(&I);
|
const Value *PtrOp =
|
||||||
if (!PtrOp)
|
Attributor::getPointerOperand(&I, /* AllowVolatile */ true);
|
||||||
return true;
|
assert(PtrOp &&
|
||||||
|
"Expected pointer operand of memory accessing instruction");
|
||||||
|
|
||||||
// A memory access through a pointer is considered UB
|
// A memory access through a pointer is considered UB
|
||||||
// only if the pointer has constant null value.
|
// only if the pointer has constant null value.
|
||||||
// TODO: Expand it to not only check constant values.
|
// TODO: Expand it to not only check constant values.
|
||||||
if (!isa<ConstantPointerNull>(PtrOp)) {
|
if (!isa<ConstantPointerNull>(PtrOp)) {
|
||||||
NoUBMemAccessInsts.insert(&I);
|
AssumedNoUBInsts.insert(&I);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const Type *PtrTy = PtrOp->getType();
|
const Type *PtrTy = PtrOp->getType();
|
||||||
@ -2025,10 +2010,35 @@ struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior {
|
|||||||
|
|
||||||
// A memory access using constant null pointer is only considered UB
|
// A memory access using constant null pointer is only considered UB
|
||||||
// if null pointer is _not_ defined for the target platform.
|
// if null pointer is _not_ defined for the target platform.
|
||||||
if (!llvm::NullPointerIsDefined(F, PtrTy->getPointerAddressSpace()))
|
if (llvm::NullPointerIsDefined(F, PtrTy->getPointerAddressSpace()))
|
||||||
UBMemAccessInsts.insert(&I);
|
AssumedNoUBInsts.insert(&I);
|
||||||
else
|
else
|
||||||
NoUBMemAccessInsts.insert(&I);
|
KnownUBInsts.insert(&I);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto InspectBrInstForUB = [&](Instruction &I) {
|
||||||
|
// A conditional branch instruction is considered UB if it has `undef`
|
||||||
|
// condition.
|
||||||
|
|
||||||
|
// Skip instructions that are already saved.
|
||||||
|
if (AssumedNoUBInsts.count(&I) || KnownUBInsts.count(&I))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// We know we have a branch instruction.
|
||||||
|
auto BrInst = cast<BranchInst>(&I);
|
||||||
|
|
||||||
|
// Unconditional branches are never considered UB.
|
||||||
|
if (BrInst->isUnconditional())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Either we stopped and the appropriate action was taken,
|
||||||
|
// or we got back a simplified value to continue.
|
||||||
|
Optional<Value *> SimplifiedCond =
|
||||||
|
stopOnUndefOrAssumed(A, BrInst->getCondition(), BrInst);
|
||||||
|
if (!SimplifiedCond.hasValue())
|
||||||
|
return true;
|
||||||
|
AssumedNoUBInsts.insert(&I);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2036,19 +2046,46 @@ struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior {
|
|||||||
{Instruction::Load, Instruction::Store,
|
{Instruction::Load, Instruction::Store,
|
||||||
Instruction::AtomicCmpXchg,
|
Instruction::AtomicCmpXchg,
|
||||||
Instruction::AtomicRMW});
|
Instruction::AtomicRMW});
|
||||||
if (PrevSize != NoUBMemAccessInsts.size())
|
A.checkForAllInstructions(InspectBrInstForUB, *this, {Instruction::Br});
|
||||||
|
if (NoUBPrevSize != AssumedNoUBInsts.size() ||
|
||||||
|
UBPrevSize != KnownUBInsts.size())
|
||||||
return ChangeStatus::CHANGED;
|
return ChangeStatus::CHANGED;
|
||||||
return ChangeStatus::UNCHANGED;
|
return ChangeStatus::UNCHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isKnownToCauseUB(Instruction *I) const override {
|
||||||
|
return KnownUBInsts.count(I);
|
||||||
|
}
|
||||||
|
|
||||||
bool isAssumedToCauseUB(Instruction *I) const override {
|
bool isAssumedToCauseUB(Instruction *I) const override {
|
||||||
return UBMemAccessInsts.count(I);
|
// In simple words, if an instruction is not in the assumed to _not_
|
||||||
|
// cause UB, then it is assumed UB (that includes those
|
||||||
|
// in the KnownUBInsts set). The rest is boilerplate
|
||||||
|
// is to ensure that it is one of the instructions we test
|
||||||
|
// for UB.
|
||||||
|
|
||||||
|
switch (I->getOpcode()) {
|
||||||
|
case Instruction::Load:
|
||||||
|
case Instruction::Store:
|
||||||
|
case Instruction::AtomicCmpXchg:
|
||||||
|
case Instruction::AtomicRMW:
|
||||||
|
return !AssumedNoUBInsts.count(I);
|
||||||
|
case Instruction::Br: {
|
||||||
|
auto BrInst = cast<BranchInst>(I);
|
||||||
|
if (BrInst->isUnconditional())
|
||||||
|
return false;
|
||||||
|
return !AssumedNoUBInsts.count(I);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangeStatus manifest(Attributor &A) override {
|
ChangeStatus manifest(Attributor &A) override {
|
||||||
if (!UBMemAccessInsts.size())
|
if (KnownUBInsts.empty())
|
||||||
return ChangeStatus::UNCHANGED;
|
return ChangeStatus::UNCHANGED;
|
||||||
for (Instruction *I : UBMemAccessInsts)
|
for (Instruction *I : KnownUBInsts)
|
||||||
A.changeToUnreachableAfterManifest(I);
|
A.changeToUnreachableAfterManifest(I);
|
||||||
return ChangeStatus::CHANGED;
|
return ChangeStatus::CHANGED;
|
||||||
}
|
}
|
||||||
@ -2058,22 +2095,69 @@ struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior {
|
|||||||
return getAssumed() ? "undefined-behavior" : "no-ub";
|
return getAssumed() ? "undefined-behavior" : "no-ub";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Note: The correctness of this analysis depends on the fact that the
|
||||||
|
/// following 2 sets will stop changing after some point.
|
||||||
|
/// "Change" here means that their size changes.
|
||||||
|
/// The size of each set is monotonically increasing
|
||||||
|
/// (we only add items to them) and it is upper bounded by the number of
|
||||||
|
/// instructions in the processed function (we can never save more
|
||||||
|
/// elements in either set than this number). Hence, at some point,
|
||||||
|
/// they will stop increasing.
|
||||||
|
/// Consequently, at some point, both sets will have stopped
|
||||||
|
/// changing, effectively making the analysis reach a fixpoint.
|
||||||
|
|
||||||
|
/// Note: These 2 sets are disjoint and an instruction can be considered
|
||||||
|
/// one of 3 things:
|
||||||
|
/// 1) Known to cause UB (AAUndefinedBehavior could prove it) and put it in
|
||||||
|
/// the KnownUBInsts set.
|
||||||
|
/// 2) Assumed to cause UB (in every updateImpl, AAUndefinedBehavior
|
||||||
|
/// has a reason to assume it).
|
||||||
|
/// 3) Assumed to not cause UB. very other instruction - AAUndefinedBehavior
|
||||||
|
/// could not find a reason to assume or prove that it can cause UB,
|
||||||
|
/// hence it assumes it doesn't. We have a set for these instructions
|
||||||
|
/// so that we don't reprocess them in every update.
|
||||||
|
/// Note however that instructions in this set may cause UB.
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// A set of all the (live) memory accessing instructions that _are_ assumed to
|
/// A set of all live instructions _known_ to cause UB.
|
||||||
// cause UB.
|
SmallPtrSet<Instruction *, 8> KnownUBInsts;
|
||||||
SmallPtrSet<Instruction *, 8> UBMemAccessInsts;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// A set of all the (live) memory accessing instructions
|
/// A set of all the (live) instructions that are assumed to _not_ cause UB.
|
||||||
// that are _not_ assumed to cause UB.
|
SmallPtrSet<Instruction *, 8> AssumedNoUBInsts;
|
||||||
// Note: The correctness of the procedure depends on the fact that this
|
|
||||||
// set stops changing after some point. "Change" here means that the size
|
// Should be called on updates in which if we're processing an instruction
|
||||||
// of the set changes. The size of this set is monotonically increasing
|
// \p I that depends on a value \p V, one of the following has to happen:
|
||||||
// (we only add items to it) and is upper bounded by the number of memory
|
// - If the value is assumed, then stop.
|
||||||
// accessing instructions in the processed function (we can never save more
|
// - If the value is known but undef, then consider it UB.
|
||||||
// elements in this set than this number). Hence, the size of this set, at
|
// - Otherwise, do specific processing with the simplified value.
|
||||||
// some point, will stop increasing, effectively reaching a fixpoint.
|
// We return None in the first 2 cases to signify that an appropriate
|
||||||
SmallPtrSet<Instruction *, 8> NoUBMemAccessInsts;
|
// action was taken and the caller should stop.
|
||||||
|
// Otherwise, we return the simplified value that the caller should
|
||||||
|
// use for specific processing.
|
||||||
|
Optional<Value *> stopOnUndefOrAssumed(Attributor &A, const Value *V,
|
||||||
|
Instruction *I) {
|
||||||
|
const auto &ValueSimplifyAA =
|
||||||
|
A.getAAFor<AAValueSimplify>(*this, IRPosition::value(*V));
|
||||||
|
Optional<Value *> SimplifiedV =
|
||||||
|
ValueSimplifyAA.getAssumedSimplifiedValue(A);
|
||||||
|
if (!ValueSimplifyAA.isKnown()) {
|
||||||
|
// Don't depend on assumed values.
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
if (!SimplifiedV.hasValue()) {
|
||||||
|
// If it is known (which we tested above) but it doesn't have a value,
|
||||||
|
// then we can assume `undef` and hence the instruction is UB.
|
||||||
|
KnownUBInsts.insert(I);
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
Value *Val = SimplifiedV.getValue();
|
||||||
|
if (isa<UndefValue>(Val)) {
|
||||||
|
KnownUBInsts.insert(I);
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
return Val;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl {
|
struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl {
|
||||||
@ -2085,7 +2169,7 @@ struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl {
|
|||||||
STATS_DECL(UndefinedBehaviorInstruction, Instruction,
|
STATS_DECL(UndefinedBehaviorInstruction, Instruction,
|
||||||
"Number of instructions known to have UB");
|
"Number of instructions known to have UB");
|
||||||
BUILD_STAT_NAME(UndefinedBehaviorInstruction, Instruction) +=
|
BUILD_STAT_NAME(UndefinedBehaviorInstruction, Instruction) +=
|
||||||
UBMemAccessInsts.size();
|
KnownUBInsts.size();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3101,7 +3185,8 @@ struct AADereferenceableImpl : AADereferenceable {
|
|||||||
int64_t Offset;
|
int64_t Offset;
|
||||||
if (const Value *Base = getBasePointerOfAccessPointerOperand(
|
if (const Value *Base = getBasePointerOfAccessPointerOperand(
|
||||||
I, Offset, DL, /*AllowNonInbounds*/ true)) {
|
I, Offset, DL, /*AllowNonInbounds*/ true)) {
|
||||||
if (Base == &getAssociatedValue() && getPointerOperand(I) == UseV) {
|
if (Base == &getAssociatedValue() &&
|
||||||
|
Attributor::getPointerOperand(I, /* AllowVolatile */ false) == UseV) {
|
||||||
uint64_t Size = DL.getTypeStoreSize(PtrTy->getPointerElementType());
|
uint64_t Size = DL.getTypeStoreSize(PtrTy->getPointerElementType());
|
||||||
addAccessedBytes(Offset, Size);
|
addAccessedBytes(Offset, Size);
|
||||||
}
|
}
|
||||||
@ -5592,6 +5677,7 @@ void Attributor::initializeInformationCache(Function &F) {
|
|||||||
case Instruction::CatchSwitch:
|
case Instruction::CatchSwitch:
|
||||||
case Instruction::AtomicRMW:
|
case Instruction::AtomicRMW:
|
||||||
case Instruction::AtomicCmpXchg:
|
case Instruction::AtomicCmpXchg:
|
||||||
|
case Instruction::Br:
|
||||||
case Instruction::Resume:
|
case Instruction::Resume:
|
||||||
case Instruction::Ret:
|
case Instruction::Ret:
|
||||||
IsInterestingOpcode = true;
|
IsInterestingOpcode = true;
|
||||||
|
@ -9,13 +9,12 @@ define void @fn2(i32* %P) {
|
|||||||
; CHECK-NEXT: entry:
|
; CHECK-NEXT: entry:
|
||||||
; CHECK-NEXT: br label [[IF_END:%.*]]
|
; CHECK-NEXT: br label [[IF_END:%.*]]
|
||||||
; CHECK: for.cond1:
|
; CHECK: for.cond1:
|
||||||
; CHECK-NEXT: br i1 undef, label [[IF_END]], label [[IF_END]]
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.end:
|
; CHECK: if.end:
|
||||||
; CHECK-NEXT: [[E_2:%.*]] = phi i32* [ undef, [[ENTRY:%.*]] ], [ null, [[FOR_COND1:%.*]] ], [ null, [[FOR_COND1]] ]
|
; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* undef, align 4
|
||||||
; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* [[E_2]], align 4
|
|
||||||
; CHECK-NEXT: [[CALL:%.*]] = call i32 @fn1(i32 [[TMP0]])
|
; CHECK-NEXT: [[CALL:%.*]] = call i32 @fn1(i32 [[TMP0]])
|
||||||
; CHECK-NEXT: store i32 [[CALL]], i32* [[P]]
|
; CHECK-NEXT: store i32 [[CALL]], i32* [[P]]
|
||||||
; CHECK-NEXT: br label [[FOR_COND1]]
|
; CHECK-NEXT: br label %for.cond1
|
||||||
;
|
;
|
||||||
entry:
|
entry:
|
||||||
br label %if.end
|
br label %if.end
|
||||||
@ -51,13 +50,12 @@ define void @fn_no_null_opt(i32* %P) #0 {
|
|||||||
; CHECK-NEXT: entry:
|
; CHECK-NEXT: entry:
|
||||||
; CHECK-NEXT: br label [[IF_END:%.*]]
|
; CHECK-NEXT: br label [[IF_END:%.*]]
|
||||||
; CHECK: for.cond1:
|
; CHECK: for.cond1:
|
||||||
; CHECK-NEXT: br i1 undef, label [[IF_END]], label [[IF_END]]
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.end:
|
; CHECK: if.end:
|
||||||
; CHECK-NEXT: [[E_2:%.*]] = phi i32* [ undef, [[ENTRY:%.*]] ], [ null, [[FOR_COND1:%.*]] ], [ null, [[FOR_COND1]] ]
|
; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* undef, align 4
|
||||||
; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* [[E_2]], align 4
|
|
||||||
; CHECK-NEXT: [[CALL:%.*]] = call i32 @fn0(i32 [[TMP0]])
|
; CHECK-NEXT: [[CALL:%.*]] = call i32 @fn0(i32 [[TMP0]])
|
||||||
; CHECK-NEXT: store i32 [[CALL]], i32* [[P]]
|
; CHECK-NEXT: store i32 [[CALL]], i32* [[P]]
|
||||||
; CHECK-NEXT: br label [[FOR_COND1]]
|
; CHECK-NEXT: br label %for.cond1
|
||||||
;
|
;
|
||||||
entry:
|
entry:
|
||||||
br label %if.end
|
br label %if.end
|
||||||
|
@ -7,7 +7,7 @@ define void @test(i32 signext %n) {
|
|||||||
; CHECK-LABEL: define {{[^@]+}}@test
|
; CHECK-LABEL: define {{[^@]+}}@test
|
||||||
; CHECK-SAME: (i32 signext [[N:%.*]])
|
; CHECK-SAME: (i32 signext [[N:%.*]])
|
||||||
; CHECK-NEXT: entry:
|
; CHECK-NEXT: entry:
|
||||||
; CHECK-NEXT: br i1 undef, label [[IF_THEN:%.*]], label [[IF_END:%.*]]
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.then:
|
; CHECK: if.then:
|
||||||
; CHECK-NEXT: unreachable
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.end:
|
; CHECK: if.end:
|
||||||
|
@ -7,7 +7,7 @@ define internal i32 @testf(i1 %c) {
|
|||||||
; CHECK-NEXT: entry:
|
; CHECK-NEXT: entry:
|
||||||
; CHECK-NEXT: br i1 [[C]], label [[IF_COND:%.*]], label [[IF_END:%.*]]
|
; CHECK-NEXT: br i1 [[C]], label [[IF_COND:%.*]], label [[IF_END:%.*]]
|
||||||
; CHECK: if.cond:
|
; CHECK: if.cond:
|
||||||
; CHECK-NEXT: br i1 undef, label [[IF_THEN:%.*]], label [[IF_END]]
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.then:
|
; CHECK: if.then:
|
||||||
; CHECK-NEXT: unreachable
|
; CHECK-NEXT: unreachable
|
||||||
; CHECK: if.end:
|
; CHECK: if.end:
|
||||||
|
@ -8,9 +8,10 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
|||||||
|
|
||||||
; -- Load tests --
|
; -- Load tests --
|
||||||
|
|
||||||
; ATTRIBUTOR-LABEL: define void @load_wholly_unreachable()
|
|
||||||
define void @load_wholly_unreachable() {
|
define void @load_wholly_unreachable() {
|
||||||
|
; ATTRIBUTOR-LABEL: @load_wholly_unreachable(
|
||||||
; ATTRIBUTOR-NEXT: unreachable
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
;
|
||||||
%a = load i32, i32* null
|
%a = load i32, i32* null
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
@ -40,6 +41,22 @@ define void @load_null_pointer_is_defined() "null-pointer-is-valid"="true" {
|
|||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define internal i32* @ret_null() {
|
||||||
|
ret i32* null
|
||||||
|
}
|
||||||
|
|
||||||
|
; FIXME: null is propagated but the instruction
|
||||||
|
; is not changed to unreachable.
|
||||||
|
define void @load_null_propagated() {
|
||||||
|
; ATTRIBUTOR-LABEL: @load_null_propagated(
|
||||||
|
; ATTRIBUTOR-NEXT: [[A:%.*]] = load i32, i32* null
|
||||||
|
; ATTRIBUTOR-NEXT: ret void
|
||||||
|
;
|
||||||
|
%ptr = call i32* @ret_null()
|
||||||
|
%a = load i32, i32* %ptr
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
; -- Store tests --
|
; -- Store tests --
|
||||||
|
|
||||||
define void @store_wholly_unreachable() {
|
define void @store_wholly_unreachable() {
|
||||||
@ -144,3 +161,135 @@ define void @atomiccmpxchg_null_pointer_is_defined() "null-pointer-is-valid"="tr
|
|||||||
%a = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
%a = cmpxchg i32* null, i32 2, i32 3 acq_rel monotonic
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Note: The unreachable on %t and %e is _not_ from AAUndefinedBehavior
|
||||||
|
|
||||||
|
define i32 @cond_br_on_undef() {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef(
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: t:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: e:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
;
|
||||||
|
|
||||||
|
br i1 undef, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret i32 1
|
||||||
|
e:
|
||||||
|
ret i32 2
|
||||||
|
}
|
||||||
|
|
||||||
|
; More complicated branching
|
||||||
|
define void @cond_br_on_undef2(i1 %cond) {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef2(
|
||||||
|
; ATTRIBUTOR-NEXT: br i1 [[COND:%.*]], label [[T1:%.*]], label [[E1:%.*]]
|
||||||
|
; ATTRIBUTOR: t1:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: t2:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: e2:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: e1:
|
||||||
|
; ATTRIBUTOR-NEXT: ret void
|
||||||
|
;
|
||||||
|
|
||||||
|
; Valid branch - verify that this is not converted
|
||||||
|
; to unreachable.
|
||||||
|
br i1 %cond, label %t1, label %e1
|
||||||
|
t1:
|
||||||
|
br i1 undef, label %t2, label %e2
|
||||||
|
t2:
|
||||||
|
ret void
|
||||||
|
e2:
|
||||||
|
ret void
|
||||||
|
e1:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i1 @ret_undef() {
|
||||||
|
ret i1 undef
|
||||||
|
}
|
||||||
|
|
||||||
|
define void @cond_br_on_undef_interproc() {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_interproc(
|
||||||
|
; ATTRIBUTOR-NEXT: %cond = call i1 @ret_undef()
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: t:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: e:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
|
||||||
|
%cond = call i1 @ret_undef()
|
||||||
|
br i1 %cond, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret void
|
||||||
|
e:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i1 @ret_undef2() {
|
||||||
|
br i1 true, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret i1 undef
|
||||||
|
e:
|
||||||
|
ret i1 undef
|
||||||
|
}
|
||||||
|
|
||||||
|
; More complicated interproc deduction of undef
|
||||||
|
define void @cond_br_on_undef_interproc2() {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_interproc2(
|
||||||
|
; ATTRIBUTOR-NEXT: %cond = call i1 @ret_undef2()
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: t:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
; ATTRIBUTOR: e:
|
||||||
|
; ATTRIBUTOR-NEXT: unreachable
|
||||||
|
%cond = call i1 @ret_undef2()
|
||||||
|
br i1 %cond, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret void
|
||||||
|
e:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Branch on undef that depends on propagation of
|
||||||
|
; undef of a previous instruction.
|
||||||
|
; FIXME: Currently it doesn't propagate the undef.
|
||||||
|
define i32 @cond_br_on_undef3() {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef3(
|
||||||
|
; ATTRIBUTOR-NEXT: %cond = icmp ne i32 1, undef
|
||||||
|
; ATTRIBUTOR-NEXT: br i1 %cond, label %t, label %e
|
||||||
|
; ATTRIBUTOR: t:
|
||||||
|
; ATTRIBUTOR-NEXT: ret i32 1
|
||||||
|
; ATTRIBUTOR: e:
|
||||||
|
; ATTRIBUTOR-NEXT: ret i32 2
|
||||||
|
|
||||||
|
%cond = icmp ne i32 1, undef
|
||||||
|
br i1 %cond, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret i32 1
|
||||||
|
e:
|
||||||
|
ret i32 2
|
||||||
|
}
|
||||||
|
|
||||||
|
; Branch on undef because of uninitialized value.
|
||||||
|
; FIXME: Currently it doesn't propagate the undef.
|
||||||
|
define i32 @cond_br_on_undef_uninit() {
|
||||||
|
; ATTRIBUTOR-LABEL: @cond_br_on_undef_uninit(
|
||||||
|
; ATTRIBUTOR-NEXT: %alloc = alloca i1
|
||||||
|
; ATTRIBUTOR-NEXT: %cond = load i1, i1* %alloc
|
||||||
|
; ATTRIBUTOR-NEXT: br i1 %cond, label %t, label %e
|
||||||
|
; ATTRIBUTOR: t:
|
||||||
|
; ATTRIBUTOR-NEXT: ret i32 1
|
||||||
|
; ATTRIBUTOR: e:
|
||||||
|
; ATTRIBUTOR-NEXT: ret i32 2
|
||||||
|
|
||||||
|
%alloc = alloca i1
|
||||||
|
%cond = load i1, i1* %alloc
|
||||||
|
br i1 %cond, label %t, label %e
|
||||||
|
t:
|
||||||
|
ret i32 1
|
||||||
|
e:
|
||||||
|
ret i32 2
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user