[llvm] [ConstraintElim] Handle (X & Y) <s 0 as X <s 0 && Y <s 0. (PR #210033)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 16 04:10:55 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Florian Hahn (fhahn)
<details>
<summary>Changes</summary>
InstCombine canonicalizes X <s 0 && Y <s 0 as (X & Y) <s 0. Teach
ConstraintElimination to recover the signed-negative information by
looking through compares of binary ANDs.
Alive2 Proof: https://alive2.llvm.org/ce/z/R6DWqD
Depends on https://github.com/llvm/llvm-project/pull/209743 (included in PR)
---
Full diff: https://github.com/llvm/llvm-project/pull/210033.diff
4 Files Affected:
- (modified) llvm/lib/Transforms/Scalar/ConstraintElimination.cpp (+25)
- (added) llvm/test/Transforms/ConstraintElimination/and-negative.ll (+87)
- (modified) llvm/test/Transforms/ConstraintElimination/or-non-negative.ll (+2-4)
- (modified) llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll (+51-9)
``````````diff
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index b244445c6e51f..cafec46e6345e 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1996,6 +1996,31 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
DFSInStack);
}
+ // (X | Y) >s -1 implies X >s -1 and Y >s -1, because the sign bit of an
+ // OR is the OR of the operand sign bits. Similarly, (X & Y) <s 0 implies
+ // X <s 0 and Y <s 0. Look through these canonical forms produced by
+ // InstCombine so the sign facts on the operands are available to the
+ // solver.
+ if ((Pred == CmpInst::ICMP_SGT && match(B, m_AllOnes())) ||
+ (Pred == CmpInst::ICMP_SLT && match(B, m_Zero()))) {
+ unsigned Opc =
+ Pred == CmpInst::ICMP_SGT ? Instruction::Or : Instruction::And;
+ SmallVector<Value *> Worklist = {A};
+ SmallPtrSet<Value *, 4> Seen;
+ while (!Worklist.empty()) {
+ Value *Cur = Worklist.pop_back_val();
+ auto *BO = dyn_cast<BinaryOperator>(Cur);
+ if (!BO || BO->getOpcode() != Opc)
+ continue;
+ for (Value *Op : {BO->getOperand(0), BO->getOperand(1)}) {
+ if (!Seen.insert(Op).second)
+ continue;
+ Worklist.push_back(Op);
+ Info.addFact(Pred, Op, B, CB.NumIn, CB.NumOut, DFSInStack);
+ }
+ }
+ }
+
if (ReproducerModule && DFSInStack.size() > ReproducerCondStack.size()) {
// Add dummy entries to ReproducerCondStack to keep it in sync with
// DFSInStack.
diff --git a/llvm/test/Transforms/ConstraintElimination/and-negative.ll b/llvm/test/Transforms/ConstraintElimination/and-negative.ll
new file mode 100644
index 0000000000000..1bc94de7969cf
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/and-negative.ll
@@ -0,0 +1,87 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=constraint-elimination -S %s | FileCheck %s
+
+; (%a & %b) <s 0 implies both %a <s 0 and %b <s 0.
+define i1 @and_negative_slt(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @and_negative_slt(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT: [[O:%.*]] = and i64 [[A]], [[B]]
+; CHECK-NEXT: [[C:%.*]] = icmp slt i64 [[O]], 0
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: ret i1 true
+;
+ %o = and i64 %a, %b
+ %c = icmp slt i64 %o, 0
+ call void @llvm.assume(i1 %c)
+ %res = icmp slt i64 %b, 0
+ ret i1 %res
+}
+
+; sle -1 form of the same fact.
+define i1 @and_negative_sle(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @and_negative_sle(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT: [[O:%.*]] = and i64 [[A]], [[B]]
+; CHECK-NEXT: [[C:%.*]] = icmp sle i64 [[O]], -1
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[RES:%.*]] = icmp slt i64 [[A]], 0
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %o = and i64 %a, %b
+ %c = icmp sle i64 %o, -1
+ call void @llvm.assume(i1 %c)
+ %res = icmp slt i64 %a, 0
+ ret i1 %res
+}
+
+; Nested ANDs: (a & b & c) <s 0 implies c <s 0.
+define i1 @and_negative_nested(i64 %a, i64 %b, i64 %c) {
+; CHECK-LABEL: define i1 @and_negative_nested(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]], i64 [[C:%.*]]) {
+; CHECK-NEXT: [[O1:%.*]] = and i64 [[A]], [[B]]
+; CHECK-NEXT: [[O2:%.*]] = and i64 [[O1]], [[C]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[O2]], 0
+; CHECK-NEXT: call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT: ret i1 true
+;
+ %o1 = and i64 %a, %b
+ %o2 = and i64 %o1, %c
+ %cmp = icmp slt i64 %o2, 0
+ call void @llvm.assume(i1 %cmp)
+ %res = icmp slt i64 %c, 0
+ ret i1 %res
+}
+
+; Negative: (a & b) <s 0 does not imply any ordering between a and b.
+define i1 @and_negative_no_order(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @and_negative_no_order(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT: [[O:%.*]] = and i64 [[A]], [[B]]
+; CHECK-NEXT: [[C:%.*]] = icmp slt i64 [[O]], 0
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[RES:%.*]] = icmp sge i64 [[A]], [[B]]
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %o = and i64 %a, %b
+ %c = icmp slt i64 %o, 0
+ call void @llvm.assume(i1 %c)
+ %res = icmp sge i64 %a, %b
+ ret i1 %res
+}
+
+; The non-negative fact does not imply the operands are negative.
+define i1 @and_non_negative(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @and_non_negative(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT: [[O:%.*]] = and i64 [[A]], [[B]]
+; CHECK-NEXT: [[C:%.*]] = icmp sge i64 [[O]], 0
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[RES:%.*]] = icmp slt i64 [[A]], 0
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %o = and i64 %a, %b
+ %c = icmp sge i64 %o, 0
+ call void @llvm.assume(i1 %c)
+ %res = icmp slt i64 %a, 0
+ ret i1 %res
+}
diff --git a/llvm/test/Transforms/ConstraintElimination/or-non-negative.ll b/llvm/test/Transforms/ConstraintElimination/or-non-negative.ll
index b8413acbbf2cf..138ea41198b91 100644
--- a/llvm/test/Transforms/ConstraintElimination/or-non-negative.ll
+++ b/llvm/test/Transforms/ConstraintElimination/or-non-negative.ll
@@ -17,8 +17,7 @@ define i1 @or_non_negative_bounds(i64 %a, i64 %b, i64 %c, i64 %d) {
; CHECK-NEXT: br i1 [[AND_3]], label %[[THEN:.*]], label %[[ELSE:.*]]
; CHECK: [[THEN]]:
; CHECK-NEXT: [[IDX:%.*]] = add nsw i64 [[D]], [[B]]
-; CHECK-NEXT: [[RES:%.*]] = icmp ult i64 [[IDX]], [[A]]
-; CHECK-NEXT: ret i1 [[RES]]
+; CHECK-NEXT: ret i1 true
; CHECK: [[ELSE]]:
; CHECK-NEXT: ret i1 false
;
@@ -76,8 +75,7 @@ define i1 @or_non_negative_nested(i64 %a, i64 %b, i64 %c) {
; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[O2]], -1
; CHECK-NEXT: br i1 [[CMP]], label %[[THEN:.*]], label %[[ELSE:.*]]
; CHECK: [[THEN]]:
-; CHECK-NEXT: [[RES:%.*]] = icmp sge i64 [[C]], 0
-; CHECK-NEXT: ret i1 [[RES]]
+; CHECK-NEXT: ret i1 true
; CHECK: [[ELSE]]:
; CHECK-NEXT: ret i1 false
;
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index a5a1fcb97541a..ce7485ba98cfb 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -70,16 +70,10 @@ define noundef i64 @or_sge_canonicalization(ptr noundef %p, i64 noundef %a, i64
; CHECK-NEXT: [[TMP2:%.*]] = icmp sgt i64 [[TMP1]], -1
; CHECK-NEXT: [[AND_3:%.*]] = and i1 [[CMP_3]], [[TMP2]]
; CHECK-NEXT: [[AND_4:%.*]] = and i1 [[CMP_5]], [[AND_3]]
-; CHECK-NEXT: br i1 [[AND_4]], label %[[THEN:.*]], label %[[ELSE:.*]]
-; CHECK: [[THEN]]:
-; CHECK-NEXT: [[IDX:%.*]] = add nuw nsw i64 [[D]], [[B]]
-; CHECK-NEXT: [[OOB_NOT:%.*]] = icmp ult i64 [[IDX]], [[A]]
-; CHECK-NEXT: br i1 [[OOB_NOT]], label %[[CONT:.*]], label %[[TRAP:.*]]
-; CHECK: [[TRAP]]:
-; CHECK-NEXT: tail call void @llvm.trap()
-; CHECK-NEXT: unreachable
+; CHECK-NEXT: br i1 [[AND_4]], label %[[CONT:.*]], label %[[ELSE:.*]]
; CHECK: [[CONT]]:
-; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw [8 x i8], ptr [[P]], i64 [[IDX]]
+; CHECK-NEXT: [[TMP3:%.*]] = getelementptr inbounds nuw [8 x i8], ptr [[P]], i64 [[D]]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw [8 x i8], ptr [[TMP3]], i64 [[B]]
; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[ARRAYIDX]], align 8
; CHECK-NEXT: br label %[[ELSE]]
; CHECK: [[ELSE]]:
@@ -117,3 +111,51 @@ else:
%res = phi i64 [ %v, %cont ], [ 0, %entry ]
ret i64 %res
}
+
+; InstCombine canonicalizes the sign checks %b <s 0, %c <s 0 and %d <s 0 into a
+; single (%b & %c & %d) <s 0 test.
+define noundef i64 @and_slt_canonicalization(ptr noundef %p, i64 noundef %a, i64 noundef %b, i64 noundef %c, i64 noundef %d) {
+; CHECK-LABEL: define noundef i64 @and_slt_canonicalization(
+; CHECK-SAME: ptr nofree noundef readonly captures(none) [[P:%.*]], i64 noundef [[A:%.*]], i64 noundef [[B:%.*]], i64 noundef [[C:%.*]], i64 noundef [[D:%.*]]) local_unnamed_addr #[[ATTR1]] {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[CMP_3:%.*]] = icmp slt i64 [[A]], [[B]]
+; CHECK-NEXT: [[TMP0:%.*]] = and i64 [[C]], [[B]]
+; CHECK-NEXT: [[TMP1:%.*]] = and i64 [[TMP0]], [[D]]
+; CHECK-NEXT: [[TMP2:%.*]] = icmp slt i64 [[TMP1]], 0
+; CHECK-NEXT: [[AND_3:%.*]] = and i1 [[CMP_3]], [[TMP2]]
+; CHECK-NEXT: br i1 [[AND_3]], label %[[CONT1:.*]], label %[[ELSE:.*]]
+; CHECK: [[CONT1]]:
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], i64 [[D]]
+; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[ARRAYIDX]], align 8
+; CHECK-NEXT: br label %[[ELSE]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: [[RES:%.*]] = phi i64 [ [[V]], %[[CONT1]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: ret i64 [[RES]]
+;
+entry:
+ %cmp.1 = icmp slt i64 %b, 0
+ %cmp.2 = icmp slt i64 %c, 0
+ %cmp.3 = icmp slt i64 %a, %b
+ %cmp.4 = icmp slt i64 %d, 0
+ %and.1 = and i1 %cmp.1, %cmp.2
+ %and.2 = and i1 %and.1, %cmp.3
+ %and.3 = and i1 %and.2, %cmp.4
+ br i1 %and.3, label %then, label %else
+
+then:
+ %oob = icmp sge i64 %a, 0
+ br i1 %oob, label %trap, label %cont
+
+trap:
+ call void @llvm.trap()
+ br label %cont
+
+cont:
+ %arrayidx = getelementptr inbounds i64, ptr %p, i64 %d
+ %v = load i64, ptr %arrayidx, align 8
+ br label %else
+
+else:
+ %res = phi i64 [ %v, %cont ], [ 0, %entry ]
+ ret i64 %res
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/210033
More information about the llvm-commits
mailing list