Skip to content

Commit

Permalink
[ConstraintElimination] Handle solving-only ICMP_NE predicates
Browse files Browse the repository at this point in the history
Simplification of non-equality predicates for solving constraint
systems is now supported by checking the validity of related
inequalities and equalities.

Differential Revision: https://reviews.llvm.org/D152684
  • Loading branch information
antoniofrighetto committed Jun 29, 2023
1 parent 2f7d30d commit a2ba4e8
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 21 deletions.
45 changes: 30 additions & 15 deletions llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,10 @@ struct ConstraintTy {

ConstraintTy() = default;

ConstraintTy(SmallVector<int64_t, 8> Coefficients, bool IsSigned, bool IsEq)
: Coefficients(Coefficients), IsSigned(IsSigned), IsEq(IsEq) {}
ConstraintTy(SmallVector<int64_t, 8> Coefficients, bool IsSigned, bool IsEq,
bool IsNe)
: Coefficients(Coefficients), IsSigned(IsSigned), IsEq(IsEq), IsNe(IsNe) {
}

unsigned size() const { return Coefficients.size(); }

Expand All @@ -205,6 +207,8 @@ struct ConstraintTy {

bool isEq() const { return IsEq; }

bool isNe() const { return IsNe; }

/// Check if the current constraint is implied by the given ConstraintSystem.
///
/// \return true or false if the constraint is proven to be respectively true,
Expand All @@ -214,6 +218,7 @@ struct ConstraintTy {

private:
bool IsEq = false;
bool IsNe = false;
};

/// Wrapper encapsulating separate constraint systems and corresponding value
Expand Down Expand Up @@ -502,6 +507,8 @@ ConstraintInfo::getConstraint(CmpInst::Predicate Pred, Value *Op0, Value *Op1,
SmallVectorImpl<Value *> &NewVariables) const {
assert(NewVariables.empty() && "NewVariables must be empty when passed in");
bool IsEq = false;
bool IsNe = false;

// Try to convert Pred to one of ULE/SLT/SLE/SLT.
switch (Pred) {
case CmpInst::ICMP_UGT:
Expand All @@ -521,10 +528,13 @@ ConstraintInfo::getConstraint(CmpInst::Predicate Pred, Value *Op0, Value *Op1,
}
break;
case CmpInst::ICMP_NE:
if (!match(Op1, m_Zero()))
return {};
Pred = CmpInst::getSwappedPredicate(CmpInst::ICMP_UGT);
std::swap(Op0, Op1);
if (match(Op1, m_Zero())) {
Pred = CmpInst::getSwappedPredicate(CmpInst::ICMP_UGT);
std::swap(Op0, Op1);
} else {
IsNe = true;
Pred = CmpInst::ICMP_ULE;
}
break;
default:
break;
Expand Down Expand Up @@ -571,7 +581,7 @@ ConstraintInfo::getConstraint(CmpInst::Predicate Pred, Value *Op0, Value *Op1,
// subtracting all coefficients from B.
ConstraintTy Res(
SmallVector<int64_t, 8>(Value2Index.size() + NewVariables.size() + 1, 0),
IsSigned, IsEq);
IsSigned, IsEq, IsNe);
// Collect variables that are known to be positive in all uses in the
// constraint.
DenseMap<Value *, bool> KnownNonNegativeVariables;
Expand Down Expand Up @@ -653,15 +663,16 @@ std::optional<bool>
ConstraintTy::isImpliedBy(const ConstraintSystem &CS) const {
bool IsConditionImplied = CS.isConditionImplied(Coefficients);

if (IsEq) {
if (IsEq || IsNe) {
auto NegatedOrEqual = ConstraintSystem::negateOrEqual(Coefficients);
bool IsNegatedOrEqualImplied =
!NegatedOrEqual.empty() && CS.isConditionImplied(NegatedOrEqual);

// In order to check that `%a == %b` is true, we want to check that `%a >=
// %b` and `%a <= %b` must hold.
// In order to check that `%a == %b` is true (equality), both conditions `%a
// >= %b` and `%a <= %b` must hold true. When checking for equality (`IsEq`
// is true), we return true if they both hold, false in the other cases.
if (IsConditionImplied && IsNegatedOrEqualImplied)
return true;
return IsEq;

auto Negated = ConstraintSystem::negate(Coefficients);
bool IsNegatedImplied = !Negated.empty() && CS.isConditionImplied(Negated);
Expand All @@ -670,10 +681,12 @@ ConstraintTy::isImpliedBy(const ConstraintSystem &CS) const {
bool IsStrictLessThanImplied =
!StrictLessThan.empty() && CS.isConditionImplied(StrictLessThan);

// In order to check that `%a == %b` is false, we want to check whether
// either `%a > %b` or `%a < %b` holds.
// In order to check that `%a != %b` is true (non-equality), either
// condition `%a > %b` or `%a < %b` must hold true. When checking for
// non-equality (`IsNe` is true), we return true if one of the two holds,
// false in the other cases.
if (IsNegatedImplied || IsStrictLessThanImplied)
return false;
return IsNe;

return std::nullopt;
}
Expand Down Expand Up @@ -1141,7 +1154,9 @@ void ConstraintInfo::addFact(CmpInst::Predicate Pred, Value *A, Value *B,
// hold.
SmallVector<Value *> NewVariables;
auto R = getConstraint(Pred, A, B, NewVariables);
if (!R.isValid(*this))

// TODO: Support non-equality for facts as well.
if (!R.isValid(*this) || R.isNe())
return;

LLVM_DEBUG(dbgs() << "Adding '" << Pred << " ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ define i1 @test_ne() {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[T_0:%.*]] = icmp ne i8 10, 11
; CHECK-NEXT: [[F_0:%.*]] = icmp ne i8 10, 10
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 [[T_0]], [[F_0]]
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 true, false
; CHECK-NEXT: [[T_1:%.*]] = icmp ne i8 10, 9
; CHECK-NEXT: [[RES_2:%.*]] = xor i1 [[RES_1]], [[T_1]]
; CHECK-NEXT: [[RES_2:%.*]] = xor i1 [[RES_1]], true
; CHECK-NEXT: ret i1 [[RES_2]]
;
entry:
Expand Down
164 changes: 160 additions & 4 deletions llvm/test/Transforms/ConstraintElimination/ne.ll
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes
; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s

declare void @llvm.assume(i1)

define i1 @test_eq_ne_0(i8 %a, i8 %b) {
; CHECK-LABEL: @test_eq_ne_0(
; CHECK-NEXT: entry:
Expand All @@ -10,7 +12,7 @@ define i1 @test_eq_ne_0(i8 %a, i8 %b) {
; CHECK-NEXT: [[F_1:%.*]] = icmp ne i8 [[A]], 0
; CHECK-NEXT: [[C_1:%.*]] = icmp ne i8 [[A]], 1
; CHECK-NEXT: [[C_2:%.*]] = icmp ne i8 [[A]], [[B:%.*]]
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 false, [[C_1]]
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 false, true
; CHECK-NEXT: [[RES_2:%.*]] = xor i1 [[RES_1]], [[C_2]]
; CHECK-NEXT: ret i1 [[RES_2]]
; CHECK: else:
Expand Down Expand Up @@ -69,7 +71,7 @@ define i1 @test_ne_eq_0(i8 %a, i8 %b) {
; CHECK: else:
; CHECK-NEXT: [[F_1:%.*]] = icmp ne i8 [[A]], 0
; CHECK-NEXT: [[C_7:%.*]] = icmp ne i8 [[A]], 1
; CHECK-NEXT: [[RES_9:%.*]] = xor i1 false, [[C_7]]
; CHECK-NEXT: [[RES_9:%.*]] = xor i1 false, true
; CHECK-NEXT: [[C_8:%.*]] = icmp ne i8 [[A]], [[B]]
; CHECK-NEXT: [[RES_10:%.*]] = xor i1 [[RES_9]], [[C_8]]
; CHECK-NEXT: [[C_9:%.*]] = icmp eq i8 [[A]], [[B]]
Expand Down Expand Up @@ -156,7 +158,7 @@ define i1 @test_eq_ne_1(i8 %a, i8 %b) {
; CHECK-NEXT: [[F_1:%.*]] = icmp ne i8 [[A]], 0
; CHECK-NEXT: [[C_1:%.*]] = icmp ne i8 [[A]], 1
; CHECK-NEXT: [[C_2:%.*]] = icmp ne i8 [[A]], [[B:%.*]]
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 true, [[C_1]]
; CHECK-NEXT: [[RES_1:%.*]] = xor i1 true, false
; CHECK-NEXT: [[RES_2:%.*]] = xor i1 [[RES_1]], [[C_2]]
; CHECK-NEXT: ret i1 [[RES_2]]
; CHECK: else:
Expand Down Expand Up @@ -215,7 +217,7 @@ define i1 @test_ne_eq_1(i8 %a, i8 %b) {
; CHECK: else:
; CHECK-NEXT: [[T_2:%.*]] = icmp ne i8 [[A]], 0
; CHECK-NEXT: [[C_9:%.*]] = icmp ne i8 [[A]], 1
; CHECK-NEXT: [[RES_9:%.*]] = xor i1 true, [[C_9]]
; CHECK-NEXT: [[RES_9:%.*]] = xor i1 true, false
; CHECK-NEXT: [[C_10:%.*]] = icmp ne i8 [[A]], [[B]]
; CHECK-NEXT: [[RES_10:%.*]] = xor i1 [[RES_9]], [[C_10]]
; CHECK-NEXT: [[C_11:%.*]] = icmp eq i8 [[A]], [[B]]
Expand Down Expand Up @@ -292,3 +294,157 @@ else:

ret i1 %res.16
}

define i1 @assume_b_plus_1_ult_a(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_b_plus_1_ult_a(
; CHECK-NEXT: [[TMP1:%.*]] = add nuw i64 [[B:%.*]], 1
; CHECK-NEXT: [[TMP2:%.*]] = icmp ult i64 [[TMP1]], [[A:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[TMP2]])
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 true
;
%1 = add nuw i64 %b, 1
%2 = icmp ult i64 %1, %a
tail call void @llvm.assume(i1 %2)
%3 = icmp ne i64 %a, %b
ret i1 %3
}

define i1 @assume_a_gt_b_and_b_ge_c(i64 %a, i64 %b, i64 %c) {
; CHECK-LABEL: @assume_a_gt_b_and_b_ge_c(
; CHECK-NEXT: [[TMP1:%.*]] = icmp ugt i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[TMP1]])
; CHECK-NEXT: [[TMP2:%.*]] = icmp uge i64 [[B]], [[C:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[TMP2]])
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i64 [[A]], [[C]]
; CHECK-NEXT: ret i1 true
;
%1 = icmp ugt i64 %a, %b
tail call void @llvm.assume(i1 %1)
%2 = icmp uge i64 %b, %c
tail call void @llvm.assume(i1 %2)
%3 = icmp ne i64 %a, %c
ret i1 %3
}

define i1 @assume_a_ne_b_and_b_ne_c(i64 %a, i64 %b, i64 %c) {
; CHECK-LABEL: @assume_a_ne_b_and_b_ne_c(
; CHECK-NEXT: [[TMP1:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[TMP1]])
; CHECK-NEXT: [[TMP2:%.*]] = icmp ne i64 [[B]], [[C:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[TMP2]])
; CHECK-NEXT: [[TMP3:%.*]] = icmp ne i64 [[A]], [[C]]
; CHECK-NEXT: ret i1 [[TMP3]]
;
%1 = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %1)
%2 = icmp ne i64 %b, %c
tail call void @llvm.assume(i1 %2)
%3 = icmp ne i64 %a, %c
ret i1 %3
}

define i1 @assume_1a(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_1a(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp ugt i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp ugt i64 %a, %b
ret i1 %ret
}

define i1 @assume_1b(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_1b(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp uge i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp uge i64 %a, %b
ret i1 %ret
}

define i1 @assume_2a(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_2a(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp ult i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp ult i64 %a, %b
ret i1 %ret
}

define i1 @assume_2b(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_2b(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp ule i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp ule i64 %a, %b
ret i1 %ret
}

; TODO: extend to support signed comparisons
define i1 @assume_3a(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_3a(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp sgt i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp sgt i64 %a, %b
ret i1 %ret
}

define i1 @assume_3b(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_3b(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp sge i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp sge i64 %a, %b
ret i1 %ret
}

define i1 @assume_4a(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_4a(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp slt i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp slt i64 %a, %b
ret i1 %ret
}

define i1 @assume_4b(i64 %a, i64 %b) {
; CHECK-LABEL: @assume_4b(
; CHECK-NEXT: [[NE:%.*]] = icmp ne i64 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: tail call void @llvm.assume(i1 [[NE]])
; CHECK-NEXT: [[RET:%.*]] = icmp sle i64 [[A]], [[B]]
; CHECK-NEXT: ret i1 [[RET]]
;
%ne = icmp ne i64 %a, %b
tail call void @llvm.assume(i1 %ne)
%ret = icmp sle i64 %a, %b
ret i1 %ret
}

0 comments on commit a2ba4e8

Please sign in to comment.