[llvm] [InstCombine] Fold (|x| + |y|) == 0 into (x == 0) && (y == 0) (PR #200702)

Aayush Shrivastava via llvm-commits llvm-commits at lists.llvm.org
Sun May 31 17:15:31 PDT 2026


https://github.com/iamaayushrivastava created https://github.com/llvm/llvm-project/pull/200702

Fixes #186306

This PR implements a missed optimization in InstCombine simplifying `(|x| + |y|) == 0 into (x == 0) && (y == 0)` (and the dual != form).

>From f4edb2a4cde12ed9500d045227a6c52ff23da233 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 1 Jun 2026 05:39:23 +0530
Subject: [PATCH] [InstCombine] Fold (|x| + |y|) == 0 into (x == 0) && (y == 0)

---
 .../InstCombine/InstCombineCompares.cpp       |  86 ++++++
 .../InstCombine/fabs-sum-fcmp-zero.ll         | 287 ++++++++++++++++++
 2 files changed, 373 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/fabs-sum-fcmp-zero.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 7b6d380acffe1..ed8f135ab56c7 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8681,6 +8681,89 @@ static Instruction *foldFabsWithFcmpZero(FCmpInst &I, InstCombinerImpl &IC) {
   }
 }
 
+/// Return true if V is always non-negative (i.e., never strictly negative).
+/// Recognises:
+///   1. llvm.fabs(x) - always >= 0
+///   2. Abs-like select patterns - select(fcmp P x, 0), x, fneg(x)) where P
+///      guarantees the true arm is non-negative, and variants thereof.
+///
+/// -0.0 and NaN are allowed: oeq/une compare treats +0 == -0, and NaN
+/// propagates identically on both sides of the surrounding transformation.
+static bool isFPAbsLike(Value *V) {
+  // fabs intrinsic is always non-negative.
+  if (match(V, m_FAbs(m_Value())))
+    return true;
+
+  // Abs-select pattern: (cond ? x : fneg(x)) or (cond ? fneg(x) : x) where
+  // the condition on x with respect to zero ensures the chosen arm is >= 0.
+  Value *Cond, *TrueVal, *FalseVal;
+  if (!match(V, m_Select(m_Value(Cond), m_Value(TrueVal), m_Value(FalseVal))))
+    return false;
+
+  for (bool Swap : {false, true}) {
+    Value *PosArm = Swap ? FalseVal : TrueVal;
+    Value *NegArm = Swap ? TrueVal : FalseVal;
+
+    // Condition must compare PosArm against zero.
+    CmpPredicate Pred;
+    if (!match(Cond, m_FCmp(Pred, m_Specific(PosArm), m_AnyZeroFP())))
+      continue;
+    // The other arm must be the negation of PosArm.
+    if (!match(NegArm, m_FNeg(m_Specific(PosArm))))
+      continue;
+
+    // When Swap=false the condition is TRUE - we pick PosArm - EffPred = Pred.
+    // When Swap=true  the condition is FALSE - we pick PosArm EffPred =
+    // !Pred.
+    FCmpInst::Predicate EffPred =
+        Swap ? FCmpInst::getInversePredicate(Pred) : (FCmpInst::Predicate)Pred;
+
+    // EffPred must imply PosArm >= 0 when it holds (OGE/OGT/UGE/UGT on zero).
+    if (EffPred == FCmpInst::FCMP_OGE || EffPred == FCmpInst::FCMP_OGT ||
+        EffPred == FCmpInst::FCMP_UGE || EffPred == FCmpInst::FCMP_UGT)
+      return true;
+  }
+  return false;
+}
+
+/// Fold: fcmp oeq/une (fadd A, B), 0.0 where A and B are both non-negative.
+///   oeq -> (fcmp oeq A, 0.0) and (fcmp oeq B, 0.0)
+///   une -> (fcmp une A, 0.0) or  (fcmp une B, 0.0)
+///
+/// If both addends are non-negative (e.g. fabs(x) or abs-like selects), their
+/// sum equals zero iff both are zero. NaN propagates symmetrically on both
+/// sides, so the transformation is always sound for oeq and une.
+static Instruction *foldFAbsSumFCmpZero(FCmpInst &I, InstCombinerImpl &IC) {
+  Value *Op0 = I.getOperand(0), *Op1 = I.getOperand(1);
+  CmpInst::Predicate Pred = I.getPredicate();
+
+  if (Pred != FCmpInst::FCMP_OEQ && Pred != FCmpInst::FCMP_UNE)
+    return nullptr;
+
+  if (!match(Op1, m_AnyZeroFP()))
+    return nullptr;
+
+  Value *A, *B;
+  if (!match(Op0, m_FAdd(m_Value(A), m_Value(B))))
+    return nullptr;
+
+  if (!isFPAbsLike(A) || !isFPAbsLike(B))
+    return nullptr;
+
+  // (|A| + |B|) oeq 0 → (A oeq 0) ∧ (B oeq 0)
+  // (|A| + |B|) une 0 → (A une 0) ∨ (B une 0)
+  // Subsequent folds (foldFabsWithFcmpZero / select-equality) will further
+  // simplify fabs(x) oeq 0 → x oeq 0 and select(_, x, -x) oeq 0 → x oeq 0.
+  Type *Ty = A->getType();
+  Constant *Zero = ConstantFP::getZero(Ty);
+  Value *CmpA = IC.Builder.CreateFCmp(Pred, A, Zero);
+  Value *CmpB = IC.Builder.CreateFCmp(Pred, B, Zero);
+
+  if (Pred == FCmpInst::FCMP_OEQ)
+    return BinaryOperator::CreateAnd(CmpA, CmpB);
+  return BinaryOperator::CreateOr(CmpA, CmpB);
+}
+
 /// Optimize sqrt(X) compared with zero.
 static Instruction *foldSqrtWithFcmpZero(FCmpInst &I, InstCombinerImpl &IC) {
   Value *X;
@@ -9221,6 +9304,9 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
   if (Instruction *R = foldFabsWithFcmpZero(I, *this))
     return R;
 
+  if (Instruction *R = foldFAbsSumFCmpZero(I, *this))
+    return R;
+
   if (Instruction *R = foldFCmpFAbsFSubIntToFP(I, *this))
     return R;
 
diff --git a/llvm/test/Transforms/InstCombine/fabs-sum-fcmp-zero.ll b/llvm/test/Transforms/InstCombine/fabs-sum-fcmp-zero.ll
new file mode 100644
index 0000000000000..3d5ae5efe74e7
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/fabs-sum-fcmp-zero.ll
@@ -0,0 +1,287 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+;
+; Verify the optimization: (|x| + |y|) == 0  -->  (x == 0) && (y == 0)
+; and the dual:            (|x| + |y|) != 0  -->  (x != 0) || (y != 0)
+;
+; The pattern appears naturally from code like:  if (x == 0 && y == 0)
+; compiled with abs-via-select idioms.
+;
+; Alive2 verification: https://alive2.llvm.org/ce/z/fiaFeZ
+
+declare double @llvm.fabs.f64(double)
+declare float @llvm.fabs.f32(float)
+declare <2 x double> @llvm.fabs.v2f64(<2 x double>)
+
+;------------------------------------------------------------------------------
+; Basic cases with fabs intrinsic
+;------------------------------------------------------------------------------
+
+define i1 @fabs_double_oeq(double %x, double %y) {
+; CHECK-LABEL: define i1 @fabs_double_oeq(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+define i1 @fabs_double_une(double %x, double %y) {
+; CHECK-LABEL: define i1 @fabs_double_une(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp une double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp une double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp une double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+define i1 @fabs_float_oeq(float %x, float %y) {
+; CHECK-LABEL: define i1 @fabs_float_oeq(
+; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq float [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq float [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call float @llvm.fabs.f32(float %x)
+  %ay = call float @llvm.fabs.f32(float %y)
+  %sum = fadd float %ax, %ay
+  %cmp = fcmp oeq float %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; Compare with -0.0 (canonicalized to 0.0 by earlier fold, then our fold fires)
+define i1 @fabs_double_oeq_negzero(double %x, double %y) {
+; CHECK-LABEL: define i1 @fabs_double_oeq_negzero(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, -0.000000e+00
+  ret i1 %cmp
+}
+
+;------------------------------------------------------------------------------
+; Select-abs patterns (common in compiler output for hand-coded abs)
+; Pattern: (x >= 0) ? x : -x  -- the canonical form in the reported issue
+;------------------------------------------------------------------------------
+
+define i1 @select_oge_oeq(double %x, double %y) {
+; CHECK-LABEL: define i1 @select_oge_oeq(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cx = fcmp oge double %x, 0.000000e+00
+  %nx = fneg double %x
+  %ax = select i1 %cx, double %x, double %nx
+  %cy = fcmp oge double %y, 0.000000e+00
+  %ny = fneg double %y
+  %ay = select i1 %cy, double %y, double %ny
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; This is the exact IR from the reported issue (GitHub #186306)
+define i1 @issue_186306(i32 %arg2, i32 %arg3, ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: define i1 @issue_186306(
+; CHECK-SAME: i32 [[ARG2:%.*]], i32 [[ARG3:%.*]], ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) {
+; CHECK-NEXT:    [[V0:%.*]] = add nsw i32 [[ARG3]], [[ARG2]]
+; CHECK-NEXT:    [[V1:%.*]] = sext i32 [[V0]] to i64
+; CHECK-NEXT:    [[V2:%.*]] = getelementptr inbounds [8 x i8], ptr [[ARG1]], i64 [[V1]]
+; CHECK-NEXT:    [[V3:%.*]] = load double, ptr [[V2]], align 8
+; CHECK-NEXT:    [[V7:%.*]] = load double, ptr [[ARG0]], align 8
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[V3]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[V7]], 0.000000e+00
+; CHECK-NEXT:    [[V12:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[V12]]
+;
+  %v0 = add nsw i32 %arg3, %arg2
+  %v1 = sext i32 %v0 to i64
+  %v2 = getelementptr inbounds double, ptr %arg1, i64 %v1
+  %v3 = load double, ptr %v2, align 8
+  %v4 = fcmp oge double %v3, 0.000000e+00
+  %v5 = fneg double %v3
+  %v6 = select i1 %v4, double %v3, double %v5
+  %v7 = load double, ptr %arg0, align 8
+  %v8 = fcmp oge double %v7, 0.000000e+00
+  %v9 = fneg double %v7
+  %v10 = select i1 %v8, double %v7, double %v9
+  %v11 = fadd double %v6, %v10
+  %v12 = fcmp oeq double %v11, 0.000000e+00
+  ret i1 %v12
+}
+
+; Select with OGT predicate: (x > 0) ? x : -x
+define i1 @select_ogt_oeq(double %x, double %y) {
+; CHECK-LABEL: define i1 @select_ogt_oeq(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cx = fcmp ogt double %x, 0.000000e+00
+  %nx = fneg double %x
+  %ax = select i1 %cx, double %x, double %nx
+  %cy = fcmp ogt double %y, 0.000000e+00
+  %ny = fneg double %y
+  %ay = select i1 %cy, double %y, double %ny
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; Reversed select: (x < 0) ? -x : x
+define i1 @select_olt_reversed_oeq(double %x, double %y) {
+; CHECK-LABEL: define i1 @select_olt_reversed_oeq(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cx = fcmp olt double %x, 0.000000e+00
+  %nx = fneg double %x
+  %ax = select i1 %cx, double %nx, double %x
+  %cy = fcmp olt double %y, 0.000000e+00
+  %ny = fneg double %y
+  %ay = select i1 %cy, double %ny, double %y
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; Mixed: one fabs, one abs-select
+define i1 @mixed_fabs_select_oeq(double %x, double %y) {
+; CHECK-LABEL: define i1 @mixed_fabs_select_oeq(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp oeq double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp oeq double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %cy = fcmp oge double %y, 0.000000e+00
+  %ny = fneg double %y
+  %ay = select i1 %cy, double %y, double %ny
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; une with select-abs
+define i1 @select_oge_une(double %x, double %y) {
+; CHECK-LABEL: define i1 @select_oge_une(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = fcmp une double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[TMP2:%.*]] = fcmp une double [[Y]], 0.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cx = fcmp oge double %x, 0.000000e+00
+  %nx = fneg double %x
+  %ax = select i1 %cx, double %x, double %nx
+  %cy = fcmp oge double %y, 0.000000e+00
+  %ny = fneg double %y
+  %ay = select i1 %cy, double %y, double %ny
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp une double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+;------------------------------------------------------------------------------
+; Negative tests — should NOT be folded
+;------------------------------------------------------------------------------
+
+; The select condition is unrelated to the value — not an abs pattern.
+define i1 @no_fold_unrelated_cond(i1 %cond, double %x, double %y) {
+; CHECK-LABEL: define i1 @no_fold_unrelated_cond(
+; CHECK-SAME: i1 [[COND:%.*]], double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[NX:%.*]] = fneg double [[X]]
+; CHECK-NEXT:    [[AX:%.*]] = select i1 [[COND]], double [[X]], double [[NX]]
+; CHECK-NEXT:    [[AY:%.*]] = call double @llvm.fabs.f64(double [[Y]])
+; CHECK-NEXT:    [[SUM:%.*]] = fadd double [[AX]], [[AY]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[SUM]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %nx = fneg double %x
+  %ax = select i1 %cond, double %x, double %nx
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; One operand is not abs-like.
+define i1 @no_fold_mixed_neg(double %x, double %y) {
+; CHECK-LABEL: define i1 @no_fold_mixed_neg(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[AX:%.*]] = call double @llvm.fabs.f64(double [[X]])
+; CHECK-NEXT:    [[SUM:%.*]] = fadd double [[AX]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[SUM]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %sum = fadd double %ax, %y
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; Wrong predicate (ogt is not oeq or une).
+define i1 @no_fold_ogt_pred(double %x, double %y) {
+; CHECK-LABEL: define i1 @no_fold_ogt_pred(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[AX:%.*]] = call double @llvm.fabs.f64(double [[X]])
+; CHECK-NEXT:    [[AY:%.*]] = call double @llvm.fabs.f64(double [[Y]])
+; CHECK-NEXT:    [[SUM:%.*]] = fadd double [[AX]], [[AY]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ogt double [[SUM]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ax = call double @llvm.fabs.f64(double %x)
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp ogt double %sum, 0.000000e+00
+  ret i1 %cmp
+}
+
+; Select condition is OLE (x <= 0 picks x, which could be negative) — not abs.
+define i1 @no_fold_select_ole(double %x, double %y) {
+; CHECK-LABEL: define i1 @no_fold_select_ole(
+; CHECK-SAME: double [[X:%.*]], double [[Y:%.*]]) {
+; CHECK-NEXT:    [[CX:%.*]] = fcmp ole double [[X]], 0.000000e+00
+; CHECK-NEXT:    [[NX:%.*]] = fneg double [[X]]
+; CHECK-NEXT:    [[AX:%.*]] = select i1 [[CX]], double [[X]], double [[NX]]
+; CHECK-NEXT:    [[AY:%.*]] = call double @llvm.fabs.f64(double [[Y]])
+; CHECK-NEXT:    [[SUM:%.*]] = fadd double [[AX]], [[AY]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[SUM]], 0.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cx = fcmp ole double %x, 0.000000e+00
+  %nx = fneg double %x
+  %ax = select i1 %cx, double %x, double %nx
+  %ay = call double @llvm.fabs.f64(double %y)
+  %sum = fadd double %ax, %ay
+  %cmp = fcmp oeq double %sum, 0.000000e+00
+  ret i1 %cmp
+}



More information about the llvm-commits mailing list