[ValueTracking] Strengthen impliesPoison reasoning

Split impliesPoison into two recursive walks, one over V, the
other over ValAssumedPoison. This allows us to reason about poison
implications in a number of additional cases that are important
in practice. This is a generalized form of D94859, which handles
the cmp to cmp implication in particular.

Differential Revision: https://reviews.llvm.org/D94866
This commit is contained in:
Nikita Popov 2021-01-16 15:32:40 +01:00
parent 0808c7009a
commit 051ec9f5f4
3 changed files with 77 additions and 54 deletions

View File

@ -4806,64 +4806,49 @@ bool llvm::canCreatePoison(const Operator *Op) {
return ::canCreateUndefOrPoison(Op, /*PoisonOnly=*/true);
}
bool llvm::impliesPoison(const Value *ValAssumedPoison, const Value *V) {
// Construct a set of values which are known to be poison from the knowledge
// that ValAssumedPoison is poison.
SmallPtrSet<const Value *, 4> PoisonValues;
PoisonValues.insert(ValAssumedPoison);
const Instruction *PoisonI = dyn_cast<Instruction>(ValAssumedPoison);
unsigned Depth = 0;
const unsigned MaxDepth = 2;
while (PoisonI && Depth < MaxDepth) {
// We'd like to know whether an operand of PoisonI is also poison.
if (canCreatePoison(cast<Operator>(PoisonI)))
// PoisonI can be a poison-generating instruction, so don't look further
break;
const Value *NextVal = nullptr;
bool MoreThanOneCandidate = false;
// See which operand can be poison
for (const auto &Op : PoisonI->operands()) {
if (!isGuaranteedNotToBeUndefOrPoison(Op.get())) {
// Op can be poison.
if (NextVal) {
// There is more than one operand that can make PoisonI poison.
MoreThanOneCandidate = true;
break;
}
NextVal = Op.get();
}
}
if (NextVal == nullptr) {
// All operands are non-poison, so PoisonI cannot be poison.
// Since assumption is false, return true
return true;
} else if (MoreThanOneCandidate)
break;
Depth++;
PoisonValues.insert(NextVal);
PoisonI = dyn_cast<Instruction>(NextVal);
}
if (PoisonValues.contains(V))
static bool directlyImpliesPoison(const Value *ValAssumedPoison,
const Value *V, unsigned Depth) {
if (ValAssumedPoison == V)
return true;
// Let's look one level further, by seeing its arguments if I was an
// instruction.
// This happens when I is e.g. 'icmp X, const' where X is in PoisonValues.
const unsigned MaxDepth = 2;
if (Depth >= MaxDepth)
return false;
const auto *I = dyn_cast<Instruction>(V);
if (I && propagatesPoison(cast<Operator>(I))) {
for (const auto &Op : I->operands())
if (PoisonValues.count(Op.get()))
return true;
return any_of(I->operands(), [=](const Value *Op) {
return directlyImpliesPoison(ValAssumedPoison, Op, Depth + 1);
});
}
return false;
}
static bool impliesPoison(const Value *ValAssumedPoison, const Value *V,
unsigned Depth) {
if (isGuaranteedNotToBeUndefOrPoison(ValAssumedPoison))
return true;
if (directlyImpliesPoison(ValAssumedPoison, V, /* Depth */ 0))
return true;
const unsigned MaxDepth = 2;
if (Depth >= MaxDepth)
return false;
const auto *I = dyn_cast<Instruction>(ValAssumedPoison);
if (I && !canCreatePoison(cast<Operator>(I))) {
return all_of(I->operands(), [=](const Value *Op) {
return impliesPoison(Op, V, Depth + 1);
});
}
return false;
}
bool llvm::impliesPoison(const Value *ValAssumedPoison, const Value *V) {
return ::impliesPoison(ValAssumedPoison, V, /* Depth */ 0);
}
static bool programUndefinedIfUndefOrPoison(const Value *V,
bool PoisonOnly);

View File

@ -148,10 +148,9 @@ define i1 @logical_or_noundef_a(i1 noundef %a, i1 %b) {
}
; Noundef on false value allows conversion to or.
; TODO: impliesPoison doesn't handle this yet.
define i1 @logical_or_noundef_b(i1 %a, i1 noundef %b) {
; CHECK-LABEL: @logical_or_noundef_b(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[A:%.*]], i1 true, i1 [[B:%.*]]
; CHECK-NEXT: [[RES:%.*]] = or i1 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: ret i1 [[RES]]
;
%res = select i1 %a, i1 true, i1 %b
@ -169,10 +168,9 @@ define i1 @logical_and_noundef_a(i1 noundef %a, i1 %b) {
}
; Noundef on false value allows conversion to and.
; TODO: impliesPoison doesn't handle this yet.
define i1 @logical_and_noundef_b(i1 %a, i1 noundef %b) {
; CHECK-LABEL: @logical_and_noundef_b(
; CHECK-NEXT: [[RES:%.*]] = select i1 [[A:%.*]], i1 [[B:%.*]], i1 false
; CHECK-NEXT: [[RES:%.*]] = and i1 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: ret i1 [[RES]]
;
%res = select i1 %a, i1 %b, i1 false

View File

@ -748,6 +748,46 @@ TEST_F(ValueTrackingTest, impliesPoisonTest_AddNsw) {
EXPECT_FALSE(impliesPoison(A2, A));
}
TEST_F(ValueTrackingTest, impliesPoisonTest_Cmp) {
parseAssembly("define void @test(i32 %x, i32 %y, i1 %c) {\n"
" %A2 = icmp eq i32 %x, %y\n"
" %A0 = icmp ult i32 %x, %y\n"
" %A = or i1 %A0, %c\n"
" ret void\n"
"}");
EXPECT_TRUE(impliesPoison(A2, A));
}
TEST_F(ValueTrackingTest, impliesPoisonTest_FCmpFMF) {
parseAssembly("define void @test(float %x, float %y, i1 %c) {\n"
" %A2 = fcmp nnan oeq float %x, %y\n"
" %A0 = fcmp olt float %x, %y\n"
" %A = or i1 %A0, %c\n"
" ret void\n"
"}");
EXPECT_FALSE(impliesPoison(A2, A));
}
TEST_F(ValueTrackingTest, impliesPoisonTest_AddSubSameOps) {
parseAssembly("define void @test(i32 %x, i32 %y, i1 %c) {\n"
" %A2 = add i32 %x, %y\n"
" %A = sub i32 %x, %y\n"
" ret void\n"
"}");
EXPECT_TRUE(impliesPoison(A2, A));
}
TEST_F(ValueTrackingTest, impliesPoisonTest_MaskCmp) {
parseAssembly("define void @test(i32 %x, i32 %y, i1 %c) {\n"
" %M2 = and i32 %x, 7\n"
" %A2 = icmp eq i32 %M2, 1\n"
" %M = and i32 %x, 15\n"
" %A = icmp eq i32 %M, 3\n"
" ret void\n"
"}");
EXPECT_TRUE(impliesPoison(A2, A));
}
TEST_F(ValueTrackingTest, ComputeNumSignBits_Shuffle_Pointers) {
parseAssembly(
"define <2 x i32*> @test(<2 x i32*> %x) {\n"