[llvm] [InstCombine] Optimize and of overflow checks (PR #141962)

Bobby SONG via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 07:22:22 PDT 2026


https://github.com/bobby-b-song updated https://github.com/llvm/llvm-project/pull/141962

>From 1cdb5b43b8f96e08fb5acebe1a1fa4c41025caae Mon Sep 17 00:00:00 2001
From: Bobby Song <me at bby.ee>
Date: Thu, 13 Aug 2026 08:15:00 +0100
Subject: [PATCH] [ValueTracking] Handle X Pred ~Y / Y Pred ~X in
 isImpliedCondICmps

X Pred ~Y is equivalent to Y Pred ~X for all predicates, so two compares
related by that transform effectively have matching operands and their
implication can be decided by isImpliedByMatchingCmp.

Handling this in isImpliedCondICmps rather than matching a specific and/or
form in simplifyAndInst means every isImpliedCondition() caller benefits,
and it also covers the commuted operand order that a pattern match on the
and operands misses.

Addresses #141479.
---
 llvm/lib/Analysis/ValueTracking.cpp           |   4 +
 .../and-comparison-not-always-false.ll        | 259 ++++++++++++++++++
 2 files changed, 263 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/and-comparison-not-always-false.ll

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 28d47d8f7df27..2d9c48ae7885c 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -9821,6 +9821,10 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
   if (L0 == R0 && L1 == R1)
     return ICmpInst::isImpliedByMatchingCmp(LPred, RPred);
 
+  // X Pred ~Y is equivalent to Y Pred ~X for all predicates.
+  if (match(L1, m_Not(m_Specific(R0))) && match(R1, m_Not(m_Specific(L0))))
+    return ICmpInst::isImpliedByMatchingCmp(LPred, RPred);
+
   // It only really makes sense in the context of signed comparison for "X - Y
   // must be positive if X >= Y and no overflow".
   // Take SGT as an example:  L0:x > L1:y and C >= 0
diff --git a/llvm/test/Transforms/InstCombine/and-comparison-not-always-false.ll b/llvm/test/Transforms/InstCombine/and-comparison-not-always-false.ll
new file mode 100644
index 0000000000000..f2c698f12539c
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/and-comparison-not-always-false.ll
@@ -0,0 +1,259 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+define i1 @test_pass_et(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_et(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ule i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_pass_et_commuted(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_et_commuted(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ule i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define <2 x i1> @test_pass_et_vec_splat(<2 x i8> %x0, <2 x i8> %x1) {
+; CHECK-LABEL: define <2 x i1> @test_pass_et_vec_splat(
+; CHECK-SAME: <2 x i8> [[X0:%.*]], <2 x i8> [[X1:%.*]]) {
+; CHECK-NEXT:    ret <2 x i1> zeroinitializer
+;
+  %not.x0 = xor <2 x i8> %x0, <i8 -1, i8 -1>
+  %cmp0 = icmp ule <2 x i8> %x1, %not.x0
+  %not.x1 = xor <2 x i8> %x1, <i8 -1, i8 -1>
+  %cmp1 = icmp ugt <2 x i8> %x0, %not.x1
+  %op = and <2 x i1> %cmp0, %cmp1
+  ret <2 x i1> %op
+}
+
+define <2 x i1> @test_pass_et_vec_splat_poison(<2 x i8> %x0, <2 x i8> %x1) {
+; CHECK-LABEL: define <2 x i1> @test_pass_et_vec_splat_poison(
+; CHECK-SAME: <2 x i8> [[X0:%.*]], <2 x i8> [[X1:%.*]]) {
+; CHECK-NEXT:    ret <2 x i1> zeroinitializer
+;
+  %not.x0 = xor <2 x i8> %x0, <i8 -1, i8 poison>
+  %cmp0 = icmp ule <2 x i8> %x1, %not.x0
+  %not.x1 = xor <2 x i8> %x1, <i8 -1, i8 poison>
+  %cmp1 = icmp ugt <2 x i8> %x0, %not.x1
+  %op = and <2 x i1> %cmp0, %cmp1
+  ret <2 x i1> %op
+}
+
+define <2 x i1> @test_pass_et_vec_non_splat(<2 x i8> %x0, <2 x i8> %x1) {
+; CHECK-LABEL: define <2 x i1> @test_pass_et_vec_non_splat(
+; CHECK-SAME: <2 x i8> [[X0:%.*]], <2 x i8> [[X1:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor <2 x i8> [[X0]], <i8 -1, i8 -2>
+; CHECK-NEXT:    [[CMP0:%.*]] = icmp ule <2 x i8> [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[NOT_X1:%.*]] = xor <2 x i8> [[X1]], <i8 -1, i8 -2>
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ugt <2 x i8> [[X0]], [[NOT_X1]]
+; CHECK-NEXT:    [[OP:%.*]] = and <2 x i1> [[CMP0]], [[CMP1]]
+; CHECK-NEXT:    ret <2 x i1> [[OP]]
+;
+  %not.x0 = xor <2 x i8> %x0, <i8 -1, i8 -2>
+  %cmp0 = icmp ule <2 x i8> %x1, %not.x0
+  %not.x1 = xor <2 x i8> %x1, <i8 -1, i8 -2>
+  %cmp1 = icmp ugt <2 x i8> %x0, %not.x1
+  %op = and <2 x i1> %cmp0, %cmp1
+  ret <2 x i1> %op
+}
+
+define i1 @test_pass_signed(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_signed(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp sle i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp sgt i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_pass_tt(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_tt(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_pass_tt_commuted(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_tt_commuted(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_pass_te(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_te(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp uge i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_pass_te_commuted(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_pass_te_commuted(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp uge i32 %x0, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_nopass_ee(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_nopass_ee(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ule i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[NOT_X1:%.*]] = xor i32 [[X1]], -1
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp uge i32 [[X0]], [[NOT_X1]]
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ule i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp uge i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_nopass_ee_commuted(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_nopass_ee_commuted(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[CMP0:%.*]] = icmp ule i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[NOT_X1:%.*]] = xor i32 [[X1]], -1
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp uge i32 [[X0]], [[NOT_X1]]
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[CMP1]], [[CMP0]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ule i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp uge i32 %x0, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_no_change_et(i32 %x0, i32 %x1, i32 %x2) {
+; CHECK-LABEL: define i1 @test_no_change_et(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]], i32 [[X2:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ule i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp slt i32 [[X1]], 0
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ule i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x1, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_no_change_te(i32 %x0, i32 %x1, i32 %x2) {
+; CHECK-LABEL: define i1 @test_no_change_te(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]], i32 [[X2:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp slt i32 [[X1]], 0
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp uge i32 %x1, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_no_change_tt(i32 %x0, i32 %x1, i32 %x2) {
+; CHECK-LABEL: define i1 @test_no_change_tt(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]], i32 [[X2:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp slt i32 [[X1]], 0
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp ult i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x1, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}
+
+define i1 @test_no_change_su(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_no_change_su(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp sle i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[NOT_X1:%.*]] = xor i32 [[X1]], -1
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp ugt i32 [[X0]], [[NOT_X1]]
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp sle i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp0, %cmp1
+  ret i1 %op
+}
+
+define i1 @test_no_change_su_commuted(i32 %x0, i32 %x1) {
+; CHECK-LABEL: define i1 @test_no_change_su_commuted(
+; CHECK-SAME: i32 [[X0:%.*]], i32 [[X1:%.*]]) {
+; CHECK-NEXT:    [[NOT_X0:%.*]] = xor i32 [[X0]], -1
+; CHECK-NEXT:    [[CMP0:%.*]] = icmp sle i32 [[X1]], [[NOT_X0]]
+; CHECK-NEXT:    [[NOT_X1:%.*]] = xor i32 [[X1]], -1
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ugt i32 [[X0]], [[NOT_X1]]
+; CHECK-NEXT:    [[OP:%.*]] = and i1 [[CMP1]], [[CMP0]]
+; CHECK-NEXT:    ret i1 [[OP]]
+;
+  %not.x0 = xor i32 %x0, -1
+  %cmp0 = icmp sle i32 %x1, %not.x0
+  %not.x1 = xor i32 %x1, -1
+  %cmp1 = icmp ugt i32 %x0, %not.x1
+  %op = and i1 %cmp1, %cmp0
+  ret i1 %op
+}



More information about the llvm-commits mailing list