[llvm] [InstCombine] Do not apply unsigned sub identities when flipping icmp strictness in `foldICmpBinOp` (PR #219904)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 31 00:41:31 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Antonio Frighetto (antoniofrighetto)
<details>
<summary>Changes</summary>
Folding `icmp ult A -nuw B, Op1 -> icmp ule A, Op1` may be unsound for unsigned predicates with negative B, when the subtraction appears as a `or disjoint`, previously matched as `add nuw`. nuw in `A + (-B)` ensures that `A <u B` does not wrap unsigned, though the identity would require proving `A >=u B` for the subtraction not to wrap. As the fold relies on `NoOp0WrapProblem`, which proves the opposite of the latter, simply avoid performing such an optimization.
Fixes: https://github.com/llvm/llvm-project/issues/218540.
---
Full diff: https://github.com/llvm/llvm-project/pull/219904.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+13-6)
- (modified) llvm/test/Transforms/InstCombine/icmp.ll (+38)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 6c486f2cc5e79..72f08b398e45d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5395,6 +5395,8 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
(CmpInst::isUnsigned(Pred) && HasNUW) ||
(CmpInst::isSigned(Pred) && HasNSW);
} else if (BO.getOpcode() == Instruction::Or) {
+ // The invariant here is that we are handling m_AddLike instructions,
+ // which can only be a or disjoint, which is equivalent to an add nuw nsw.
HasNUW = true;
HasNSW = true;
return true;
@@ -5474,9 +5476,13 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
return isMultipleOf(X, C, Q) && isMultipleOf(Y, C, Q);
};
- // TODO: The subtraction-related identities shown below also hold, but
- // canonicalization from (X -nuw 1) to (X + -1) means that the combinations
- // wouldn't happen even if they were implemented.
+ // The subtraction-related identities (A -nuw B) shown below require that
+ // the subtraction does not wrap unsigned (i.e., A >=u B). Canonicalization
+ // from (A -nuw 1) to (A + -1) means that such combinations ought to never
+ // occur, as sub nuw ops should have been canonicalized to add ones. It may
+ // however appear in the form of a or disjoint. Though, or disjoint A, -B
+ // requires proving A <u B, for which the nowrap precondition can never be
+ // satisfied. These are therefore skipped.
//
// icmp ult (A - 1), Op1 -> icmp ule A, Op1
// icmp uge (A - 1), Op1 -> icmp ugt A, Op1
@@ -5489,9 +5495,10 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
// icmp sgt (A + 1), Op1 -> icmp sge A, Op1
// icmp ule (A + 1), Op0 -> icmp ult A, Op1
// icmp ugt (A + 1), Op0 -> icmp uge A, Op1
- if (A && NoOp0WrapProblem &&
- ShareCommonDivisor(A, Op1, B,
- ICmpInst::isLT(Pred) || ICmpInst::isGE(Pred)))
+ bool IsNegative = ICmpInst::isLT(Pred) || ICmpInst::isGE(Pred);
+ bool IsAddOrSignedPred = !IsNegative || ICmpInst::isSigned(Pred);
+ if (A && NoOp0WrapProblem && IsAddOrSignedPred &&
+ ShareCommonDivisor(A, Op1, B, IsNegative))
return new ICmpInst(ICmpInst::getFlippedStrictnessPredicate(Pred), A,
Op1);
diff --git a/llvm/test/Transforms/InstCombine/icmp.ll b/llvm/test/Transforms/InstCombine/icmp.ll
index 61ae46ba08d2d..c8217273ebbde 100644
--- a/llvm/test/Transforms/InstCombine/icmp.ll
+++ b/llvm/test/Transforms/InstCombine/icmp.ll
@@ -2122,6 +2122,44 @@ define i1 @icmp_ule_offset_with_common_divisor(i64 %x, i64 %y) {
ret i1 %cmp
}
+; Ensure the identity icmp ult (A - B), Op1 to icmp ule A, Op1 does not occur
+; when the or disjoint is matched as an add, as nuw of add does not imply nowrap
+; of the unsigned subtraction.
+; FIXME: This is a miscompilation.
+define i1 @icmp_ult_neg_offset_or_disjoint(i16 %arg) "instcombine-no-verify-fixpoint" {
+; CHECK-LABEL: define i1 @icmp_ult_neg_offset_or_disjoint(
+; CHECK-SAME: i16 [[ARG:%.*]]) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[ZEXT:%.*]] = zext nneg i16 [[ARG]] to i32
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i16 [[ARG]], 0
+; CHECK-NEXT: call void @use_i32(i32 [[ZEXT]])
+; CHECK-NEXT: br i1 [[CMP]], label %[[IF:.*]], label %[[ELSE:.*]]
+; CHECK: [[IF]]:
+; CHECK-NEXT: [[XOR:%.*]] = xor i16 [[ARG]], -32768
+; CHECK-NEXT: [[OR:%.*]] = or disjoint i32 [[ZEXT]], -32768
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[XOR]] to i32
+; CHECK-NEXT: [[RES:%.*]] = icmp ult i32 [[OR]], [[SEXT]]
+; CHECK-NEXT: ret i1 [[RES]]
+; CHECK: [[ELSE]]:
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %zext = zext nneg i16 %arg to i32
+ %or = or i32 %zext, -32768
+ %xor = xor i16 %arg, -32768
+ %cmp = icmp ult i16 %arg, 1
+ call void @use_i32(i32 %zext)
+ br i1 %cmp, label %if, label %else
+
+if:
+ %sext = sext i16 %xor to i32
+ %res = icmp ult i32 %or, %sext
+ ret i1 %res
+
+else:
+ ret i1 false
+}
+
; TODO: Handle non-power-of-2 divisors
define i1 @icmp_ule_offset_with_common_non_pow2_divisor(i64 %x, i64 %y) {
; CHECK-LABEL: define i1 @icmp_ule_offset_with_common_non_pow2_divisor(
``````````
</details>
https://github.com/llvm/llvm-project/pull/219904
More information about the llvm-commits
mailing list