Skip to content

Commit

Permalink
[ValueTracking] Strengthen impliesPoison reasoning
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nikic committed Jan 19, 2021
1 parent 0808c70 commit 051ec9f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 52 deletions.
81 changes: 33 additions & 48 deletions llvm/lib/Analysis/ValueTracking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
static bool directlyImpliesPoison(const Value *ValAssumedPoison,
const Value *V, unsigned Depth) {
if (ValAssumedPoison == V)
return true;

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;
const unsigned MaxDepth = 2;
if (Depth >= MaxDepth)
return false;

Depth++;
PoisonValues.insert(NextVal);
PoisonI = dyn_cast<Instruction>(NextVal);
const auto *I = dyn_cast<Instruction>(V);
if (I && propagatesPoison(cast<Operator>(I))) {
return any_of(I->operands(), [=](const Value *Op) {
return directlyImpliesPoison(ValAssumedPoison, Op, Depth + 1);
});
}
return false;
}

if (PoisonValues.contains(V))
static bool impliesPoison(const Value *ValAssumedPoison, const Value *V,
unsigned Depth) {
if (isGuaranteedNotToBeUndefOrPoison(ValAssumedPoison))
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 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;
}
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);

Expand Down
6 changes: 2 additions & 4 deletions llvm/test/Transforms/InstCombine/select-and-or.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
40 changes: 40 additions & 0 deletions llvm/unittests/Analysis/ValueTrackingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 051ec9f

Please sign in to comment.