[llvm] [InstCombine] Fold copysign(floor(fabs(X)), X) to trunc(X) (PR #200836)

Aayush Shrivastava via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 10 06:51:12 PDT 2026


https://github.com/iamaayushrivastava updated https://github.com/llvm/llvm-project/pull/200836

>From 6980a4367bde1d1eb3899f8794b2bf3f758899c4 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Mon, 1 Jun 2026 19:59:39 +0530
Subject: [PATCH 1/6] [InstCombine] Fold copysign(floor(fabs(X)), X) to
 trunc(X)

---
 .../InstCombine/InstCombineCalls.cpp          |  6 ++
 llvm/test/Transforms/InstCombine/copysign.ll  | 70 +++++++++++++++++++
 2 files changed, 76 insertions(+)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index aeb40939cd10e..d60337c540e15 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3116,6 +3116,12 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     if (match(Mag, m_FAbs(m_Value(X))) || match(Mag, m_FNeg(m_Value(X))))
       return replaceOperand(*II, 0, X);
 
+    // copysign(floor(fabs(X)), X) --> trunc(X)
+    if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
+      Value *Trunc = Builder.CreateUnaryIntrinsic(Intrinsic::trunc, Sign, II);
+      return replaceInstUsesWith(*II, Trunc);
+    }
+
     Type *SignEltTy = Sign->getType()->getScalarType();
 
     Value *CastSrc;
diff --git a/llvm/test/Transforms/InstCombine/copysign.ll b/llvm/test/Transforms/InstCombine/copysign.ll
index 4c7b4861b33c9..b4f96a790996b 100644
--- a/llvm/test/Transforms/InstCombine/copysign.ll
+++ b/llvm/test/Transforms/InstCombine/copysign.ll
@@ -2,9 +2,18 @@
 ; RUN: opt -S -passes=instcombine < %s | FileCheck %s
 
 declare float @llvm.fabs.f32(float)
+declare float @llvm.floor.f32(float)
+declare float @llvm.trunc.f32(float)
 declare float @llvm.copysign.f32(float, float)
 declare float @llvm.maxnum.f32(float, float)
+declare double @llvm.fabs.f64(double)
+declare double @llvm.floor.f64(double)
+declare double @llvm.trunc.f64(double)
+declare double @llvm.copysign.f64(double, double)
 declare <3 x double> @llvm.copysign.v3f64(<3 x double>, <3 x double>)
+declare <3 x double> @llvm.fabs.v3f64(<3 x double>)
+declare <3 x double> @llvm.floor.v3f64(<3 x double>)
+declare <3 x double> @llvm.trunc.v3f64(<3 x double>)
 
 define float @positive_sign_arg(float %x) {
 ; CHECK-LABEL: @positive_sign_arg(
@@ -285,3 +294,64 @@ define <2 x bfloat> @copysign_simplify_demanded_bits_sign_bitcast_not_int_vec(<2
   %result = call <2 x bfloat> @llvm.copysign.v2bf16(<2 x bfloat> %mag, <2 x bfloat> %cast.sign)
   ret <2 x bfloat> %result
 }
+
+; copysign(floor(fabs(X)), X) --> trunc(X)
+
+define double @copysign_floor_fabs_to_trunc_f64(double %x) {
+; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f64(
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    ret double [[RESULT]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
+define float @copysign_floor_fabs_to_trunc_f32(float %x) {
+; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f32(
+; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.trunc.f32(float [[X:%.*]])
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+  %abs = call float @llvm.fabs.f32(float %x)
+  %fl = call float @llvm.floor.f32(float %abs)
+  %result = call float @llvm.copysign.f32(float %fl, float %x)
+  ret float %result
+}
+
+define <3 x double> @copysign_floor_fabs_to_trunc_vec(<3 x double> %x) {
+; CHECK-LABEL: @copysign_floor_fabs_to_trunc_vec(
+; CHECK-NEXT:    [[RESULT:%.*]] = call <3 x double> @llvm.trunc.v3f64(<3 x double> [[X:%.*]])
+; CHECK-NEXT:    ret <3 x double> [[RESULT]]
+;
+  %abs = call <3 x double> @llvm.fabs.v3f64(<3 x double> %x)
+  %fl = call <3 x double> @llvm.floor.v3f64(<3 x double> %abs)
+  %result = call <3 x double> @llvm.copysign.v3f64(<3 x double> %fl, <3 x double> %x)
+  ret <3 x double> %result
+}
+
+; FMF flags from copysign propagate to the resulting trunc.
+define double @copysign_floor_fabs_to_trunc_fmf(double %x) {
+; CHECK-LABEL: @copysign_floor_fabs_to_trunc_fmf(
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan ninf double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    ret double [[RESULT]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call nnan ninf double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
+; Negative test: sign argument differs from fabs argument -- must not fold.
+define double @copysign_floor_fabs_no_fold_different_sign(double %x, double %y) {
+; CHECK-LABEL: @copysign_floor_fabs_no_fold_different_sign(
+; CHECK-NEXT:    [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[FL:%.*]] = call double @llvm.floor.f64(double [[ABS]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.copysign.f64(double [[FL]], double [[Y:%.*]])
+; CHECK-NEXT:    ret double [[RESULT]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call double @llvm.copysign.f64(double %fl, double %y)
+  ret double %result
+}

>From be4be892bf2e4e5338588eb0bb63e30278f3cccc Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Tue, 2 Jun 2026 02:54:16 +0530
Subject: [PATCH 2/6] [InstCombine] Guard copysign(floor(fabs(X)), X) ->
 trunc(X) with nnan

---
 .../InstCombine/InstCombineCalls.cpp          |  6 +++-
 llvm/test/Transforms/InstCombine/copysign.ll  | 28 ++++++++++++++-----
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index d60337c540e15..734b82d7edbae 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3117,7 +3117,11 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
       return replaceOperand(*II, 0, X);
 
     // copysign(floor(fabs(X)), X) --> trunc(X)
-    if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
+    // Requires nnan: for NaN X the source returns a NaN with X's sign bit
+    // preserved (via copysign), but trunc(X) may return any NaN, making the
+    // target more undefined.
+    if (II->hasNoNaNs() &&
+        match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
       Value *Trunc = Builder.CreateUnaryIntrinsic(Intrinsic::trunc, Sign, II);
       return replaceInstUsesWith(*II, Trunc);
     }
diff --git a/llvm/test/Transforms/InstCombine/copysign.ll b/llvm/test/Transforms/InstCombine/copysign.ll
index b4f96a790996b..275201088534d 100644
--- a/llvm/test/Transforms/InstCombine/copysign.ll
+++ b/llvm/test/Transforms/InstCombine/copysign.ll
@@ -295,38 +295,38 @@ define <2 x bfloat> @copysign_simplify_demanded_bits_sign_bitcast_not_int_vec(<2
   ret <2 x bfloat> %result
 }
 
-; copysign(floor(fabs(X)), X) --> trunc(X)
+; copysign(floor(fabs(X)), X) --> trunc(X)  (requires nnan)
 
 define double @copysign_floor_fabs_to_trunc_f64(double %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f64(
-; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan double @llvm.trunc.f64(double [[X:%.*]])
 ; CHECK-NEXT:    ret double [[RESULT]]
 ;
   %abs = call double @llvm.fabs.f64(double %x)
   %fl = call double @llvm.floor.f64(double %abs)
-  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  %result = call nnan double @llvm.copysign.f64(double %fl, double %x)
   ret double %result
 }
 
 define float @copysign_floor_fabs_to_trunc_f32(float %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f32(
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.trunc.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.trunc.f32(float [[X:%.*]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %abs = call float @llvm.fabs.f32(float %x)
   %fl = call float @llvm.floor.f32(float %abs)
-  %result = call float @llvm.copysign.f32(float %fl, float %x)
+  %result = call nnan float @llvm.copysign.f32(float %fl, float %x)
   ret float %result
 }
 
 define <3 x double> @copysign_floor_fabs_to_trunc_vec(<3 x double> %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_vec(
-; CHECK-NEXT:    [[RESULT:%.*]] = call <3 x double> @llvm.trunc.v3f64(<3 x double> [[X:%.*]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan <3 x double> @llvm.trunc.v3f64(<3 x double> [[X:%.*]])
 ; CHECK-NEXT:    ret <3 x double> [[RESULT]]
 ;
   %abs = call <3 x double> @llvm.fabs.v3f64(<3 x double> %x)
   %fl = call <3 x double> @llvm.floor.v3f64(<3 x double> %abs)
-  %result = call <3 x double> @llvm.copysign.v3f64(<3 x double> %fl, <3 x double> %x)
+  %result = call nnan <3 x double> @llvm.copysign.v3f64(<3 x double> %fl, <3 x double> %x)
   ret <3 x double> %result
 }
 
@@ -342,6 +342,20 @@ define double @copysign_floor_fabs_to_trunc_fmf(double %x) {
   ret double %result
 }
 
+; Negative test: missing nnan -- NaN inputs would give different sign bits.
+define double @copysign_floor_fabs_no_fold_no_nnan(double %x) {
+; CHECK-LABEL: @copysign_floor_fabs_no_fold_no_nnan(
+; CHECK-NEXT:    [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[FL:%.*]] = call double @llvm.floor.f64(double [[ABS]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.copysign.f64(double [[FL]], double [[X:%.*]])
+; CHECK-NEXT:    ret double [[RESULT]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
 ; Negative test: sign argument differs from fabs argument -- must not fold.
 define double @copysign_floor_fabs_no_fold_different_sign(double %x, double %y) {
 ; CHECK-LABEL: @copysign_floor_fabs_no_fold_different_sign(

>From acfa4f9f04edffee3df7a6d99f677d525cdf2e0d Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Wed, 3 Jun 2026 01:14:47 +0530
Subject: [PATCH 3/6] [InstCombine] Fold copysign(floor(fabs(X)), X) to
 copysign(trunc(X), X)

---
 .../InstCombine/InstCombineCalls.cpp          | 13 +++---
 llvm/test/Transforms/InstCombine/copysign.ll  | 44 ++++++++-----------
 2 files changed, 24 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 734b82d7edbae..801c5978426e6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3116,14 +3116,13 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     if (match(Mag, m_FAbs(m_Value(X))) || match(Mag, m_FNeg(m_Value(X))))
       return replaceOperand(*II, 0, X);
 
-    // copysign(floor(fabs(X)), X) --> trunc(X)
-    // Requires nnan: for NaN X the source returns a NaN with X's sign bit
-    // preserved (via copysign), but trunc(X) may return any NaN, making the
-    // target more undefined.
-    if (II->hasNoNaNs() &&
-        match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
+    // copysign(floor(fabs(X)), X) --> copysign(trunc(X), X)
+    // copysign ignores the sign bit of its magnitude argument (implicit fabs),
+    // so replacing floor(fabs(X)) with trunc(X) is correct for all inputs
+    // including NaN without requiring nnan.
+    if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
       Value *Trunc = Builder.CreateUnaryIntrinsic(Intrinsic::trunc, Sign, II);
-      return replaceInstUsesWith(*II, Trunc);
+      return replaceOperand(*II, 0, Trunc);
     }
 
     Type *SignEltTy = Sign->getType()->getScalarType();
diff --git a/llvm/test/Transforms/InstCombine/copysign.ll b/llvm/test/Transforms/InstCombine/copysign.ll
index 275201088534d..445c3b32143b8 100644
--- a/llvm/test/Transforms/InstCombine/copysign.ll
+++ b/llvm/test/Transforms/InstCombine/copysign.ll
@@ -295,46 +295,52 @@ define <2 x bfloat> @copysign_simplify_demanded_bits_sign_bitcast_not_int_vec(<2
   ret <2 x bfloat> %result
 }
 
-; copysign(floor(fabs(X)), X) --> trunc(X)  (requires nnan)
+; copysign(floor(fabs(X)), X) --> copysign(trunc(X), X)
+; copysign ignores the sign of its magnitude arg, so trunc(X) is equivalent
+; to floor(fabs(X)) for the magnitude. Valid for all inputs including NaN.
 
 define double @copysign_floor_fabs_to_trunc_f64(double %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f64(
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan double @llvm.trunc.f64(double [[X:%.*]])
-; CHECK-NEXT:    ret double [[RESULT]]
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[RESULT1:%.*]] = call double @llvm.copysign.f64(double [[RESULT]], double [[X]])
+; CHECK-NEXT:    ret double [[RESULT1]]
 ;
   %abs = call double @llvm.fabs.f64(double %x)
   %fl = call double @llvm.floor.f64(double %abs)
-  %result = call nnan double @llvm.copysign.f64(double %fl, double %x)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
   ret double %result
 }
 
 define float @copysign_floor_fabs_to_trunc_f32(float %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_f32(
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.trunc.f32(float [[X:%.*]])
-; CHECK-NEXT:    ret float [[RESULT]]
+; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.trunc.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[RESULT1:%.*]] = call float @llvm.copysign.f32(float [[RESULT]], float [[X]])
+; CHECK-NEXT:    ret float [[RESULT1]]
 ;
   %abs = call float @llvm.fabs.f32(float %x)
   %fl = call float @llvm.floor.f32(float %abs)
-  %result = call nnan float @llvm.copysign.f32(float %fl, float %x)
+  %result = call float @llvm.copysign.f32(float %fl, float %x)
   ret float %result
 }
 
 define <3 x double> @copysign_floor_fabs_to_trunc_vec(<3 x double> %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_vec(
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan <3 x double> @llvm.trunc.v3f64(<3 x double> [[X:%.*]])
-; CHECK-NEXT:    ret <3 x double> [[RESULT]]
+; CHECK-NEXT:    [[RESULT:%.*]] = call <3 x double> @llvm.trunc.v3f64(<3 x double> [[X:%.*]])
+; CHECK-NEXT:    [[RESULT1:%.*]] = call <3 x double> @llvm.copysign.v3f64(<3 x double> [[RESULT]], <3 x double> [[X]])
+; CHECK-NEXT:    ret <3 x double> [[RESULT1]]
 ;
   %abs = call <3 x double> @llvm.fabs.v3f64(<3 x double> %x)
   %fl = call <3 x double> @llvm.floor.v3f64(<3 x double> %abs)
-  %result = call nnan <3 x double> @llvm.copysign.v3f64(<3 x double> %fl, <3 x double> %x)
+  %result = call <3 x double> @llvm.copysign.v3f64(<3 x double> %fl, <3 x double> %x)
   ret <3 x double> %result
 }
 
-; FMF flags from copysign propagate to the resulting trunc.
+; FMF flags from copysign propagate to the resulting instructions.
 define double @copysign_floor_fabs_to_trunc_fmf(double %x) {
 ; CHECK-LABEL: @copysign_floor_fabs_to_trunc_fmf(
 ; CHECK-NEXT:    [[RESULT:%.*]] = call nnan ninf double @llvm.trunc.f64(double [[X:%.*]])
-; CHECK-NEXT:    ret double [[RESULT]]
+; CHECK-NEXT:    [[RESULT1:%.*]] = call nnan ninf double @llvm.copysign.f64(double [[RESULT]], double [[X]])
+; CHECK-NEXT:    ret double [[RESULT1]]
 ;
   %abs = call double @llvm.fabs.f64(double %x)
   %fl = call double @llvm.floor.f64(double %abs)
@@ -342,20 +348,6 @@ define double @copysign_floor_fabs_to_trunc_fmf(double %x) {
   ret double %result
 }
 
-; Negative test: missing nnan -- NaN inputs would give different sign bits.
-define double @copysign_floor_fabs_no_fold_no_nnan(double %x) {
-; CHECK-LABEL: @copysign_floor_fabs_no_fold_no_nnan(
-; CHECK-NEXT:    [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
-; CHECK-NEXT:    [[FL:%.*]] = call double @llvm.floor.f64(double [[ABS]])
-; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.copysign.f64(double [[FL]], double [[X:%.*]])
-; CHECK-NEXT:    ret double [[RESULT]]
-;
-  %abs = call double @llvm.fabs.f64(double %x)
-  %fl = call double @llvm.floor.f64(double %abs)
-  %result = call double @llvm.copysign.f64(double %fl, double %x)
-  ret double %result
-}
-
 ; Negative test: sign argument differs from fabs argument -- must not fold.
 define double @copysign_floor_fabs_no_fold_different_sign(double %x, double %y) {
 ; CHECK-LABEL: @copysign_floor_fabs_no_fold_different_sign(

>From 8719cff23d2c164123e1f83b6adc3e1722695397 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Wed, 3 Jun 2026 17:43:59 +0530
Subject: [PATCH 4/6] [InstCombine] Fold copysign(floor(fabs(X)), X) to
 copysign(trunc(X), X)

---
 .../InstCombine/InstCombineCalls.cpp          |  7 +++--
 llvm/test/Transforms/InstCombine/copysign.ll  | 27 +++++++++++++++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 801c5978426e6..8edb27658d9f2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3119,8 +3119,11 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     // copysign(floor(fabs(X)), X) --> copysign(trunc(X), X)
     // copysign ignores the sign bit of its magnitude argument (implicit fabs),
     // so replacing floor(fabs(X)) with trunc(X) is correct for all inputs
-    // including NaN without requiring nnan.
-    if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Specific(Sign))))) {
+    // including NaN without requiring nnan. Use stripSignOnlyFPOps inside the
+    // fabs to also handle fabs(fneg(X)) and fabs(copysign(X,Y)) patterns.
+    Value *FAbsArg;
+    if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Value(FAbsArg)))) &&
+        stripSignOnlyFPOps(FAbsArg) == Sign) {
       Value *Trunc = Builder.CreateUnaryIntrinsic(Intrinsic::trunc, Sign, II);
       return replaceOperand(*II, 0, Trunc);
     }
diff --git a/llvm/test/Transforms/InstCombine/copysign.ll b/llvm/test/Transforms/InstCombine/copysign.ll
index 445c3b32143b8..9c6e514a14b62 100644
--- a/llvm/test/Transforms/InstCombine/copysign.ll
+++ b/llvm/test/Transforms/InstCombine/copysign.ll
@@ -348,6 +348,33 @@ define double @copysign_floor_fabs_to_trunc_fmf(double %x) {
   ret double %result
 }
 
+; Fold applies without any FMF -- copysign's implicit fabs handles NaN.
+define double @copysign_floor_fabs_to_trunc_no_fmf(double %x) {
+; CHECK-LABEL: @copysign_floor_fabs_to_trunc_no_fmf(
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[RESULT1:%.*]] = call double @llvm.copysign.f64(double [[RESULT]], double [[X]])
+; CHECK-NEXT:    ret double [[RESULT1]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
+; stripSignOnlyFPOps folds fabs(fneg(X)) inside floor.
+define double @copysign_floor_fabs_fneg_to_trunc(double %x) {
+; CHECK-LABEL: @copysign_floor_fabs_fneg_to_trunc(
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[RESULT1:%.*]] = call double @llvm.copysign.f64(double [[RESULT]], double [[X]])
+; CHECK-NEXT:    ret double [[RESULT1]]
+;
+  %neg = fneg double %x
+  %abs = call double @llvm.fabs.f64(double %neg)
+  %fl = call double @llvm.floor.f64(double %abs)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
 ; Negative test: sign argument differs from fabs argument -- must not fold.
 define double @copysign_floor_fabs_no_fold_different_sign(double %x, double %y) {
 ; CHECK-LABEL: @copysign_floor_fabs_no_fold_different_sign(

>From e864d7fb2e291e1faac7b772d9d01a72be08d60e Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Thu, 4 Jun 2026 01:21:20 +0530
Subject: [PATCH 5/6] Update comment and tests for copysign(floor(fabs(X)), X)
 fold

---
 .../InstCombine/InstCombineCalls.cpp          |  5 ++--
 llvm/test/Transforms/InstCombine/copysign.ll  | 26 +++++++++++++++----
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 8edb27658d9f2..9f5955fe0f61a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3119,8 +3119,9 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     // copysign(floor(fabs(X)), X) --> copysign(trunc(X), X)
     // copysign ignores the sign bit of its magnitude argument (implicit fabs),
     // so replacing floor(fabs(X)) with trunc(X) is correct for all inputs
-    // including NaN without requiring nnan. Use stripSignOnlyFPOps inside the
-    // fabs to also handle fabs(fneg(X)) and fabs(copysign(X,Y)) patterns.
+    // including NaN without requiring nnan. The m_FAbs ensures the floor arg
+    // is non-negative; stripSignOnlyFPOps handles sign variants inside fabs
+    // such as fabs(copysign(X,Y)).
     Value *FAbsArg;
     if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Value(FAbsArg)))) &&
         stripSignOnlyFPOps(FAbsArg) == Sign) {
diff --git a/llvm/test/Transforms/InstCombine/copysign.ll b/llvm/test/Transforms/InstCombine/copysign.ll
index 9c6e514a14b62..35ed9a8da8993 100644
--- a/llvm/test/Transforms/InstCombine/copysign.ll
+++ b/llvm/test/Transforms/InstCombine/copysign.ll
@@ -361,20 +361,36 @@ define double @copysign_floor_fabs_to_trunc_no_fmf(double %x) {
   ret double %result
 }
 
-; stripSignOnlyFPOps folds fabs(fneg(X)) inside floor.
-define double @copysign_floor_fabs_fneg_to_trunc(double %x) {
-; CHECK-LABEL: @copysign_floor_fabs_fneg_to_trunc(
+; Fold applies when fabs wraps a sign-only op: fabs(copysign(X, Y)).
+define double @copysign_floor_fabs_copysign_to_trunc(double %x, double %y) {
+; CHECK-LABEL: @copysign_floor_fabs_copysign_to_trunc(
 ; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.trunc.f64(double [[X:%.*]])
 ; CHECK-NEXT:    [[RESULT1:%.*]] = call double @llvm.copysign.f64(double [[RESULT]], double [[X]])
 ; CHECK-NEXT:    ret double [[RESULT1]]
 ;
-  %neg = fneg double %x
-  %abs = call double @llvm.fabs.f64(double %neg)
+  %cs = call double @llvm.copysign.f64(double %x, double %y)
+  %abs = call double @llvm.fabs.f64(double %cs)
   %fl = call double @llvm.floor.f64(double %abs)
   %result = call double @llvm.copysign.f64(double %fl, double %x)
   ret double %result
 }
 
+; Negative test: fneg(fabs(X)) is not non-negative so floor differs from trunc.
+define double @copysign_floor_fneg_fabs_no_fold(double %x) {
+; CHECK-LABEL: @copysign_floor_fneg_fabs_no_fold(
+; CHECK-NEXT:    [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT:    [[FNEG:%.*]] = fneg double [[ABS]]
+; CHECK-NEXT:    [[FL:%.*]] = call double @llvm.floor.f64(double [[FNEG]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call double @llvm.copysign.f64(double [[FL]], double [[X]])
+; CHECK-NEXT:    ret double [[RESULT]]
+;
+  %abs = call double @llvm.fabs.f64(double %x)
+  %fneg = fneg double %abs
+  %fl = call double @llvm.floor.f64(double %fneg)
+  %result = call double @llvm.copysign.f64(double %fl, double %x)
+  ret double %result
+}
+
 ; Negative test: sign argument differs from fabs argument -- must not fold.
 define double @copysign_floor_fabs_no_fold_different_sign(double %x, double %y) {
 ; CHECK-LABEL: @copysign_floor_fabs_no_fold_different_sign(

>From 151dd525657ebe7f1536c5164d3f991926da7518 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Wed, 10 Jun 2026 19:20:46 +0530
Subject: [PATCH 6/6] [InstCombine] Drop unnecessary stripSignOnlyFPOps in
 copysign(floor(fabs(X)), X) fold

---
 llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 9f5955fe0f61a..915218b12cf8f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -3119,12 +3119,11 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
     // copysign(floor(fabs(X)), X) --> copysign(trunc(X), X)
     // copysign ignores the sign bit of its magnitude argument (implicit fabs),
     // so replacing floor(fabs(X)) with trunc(X) is correct for all inputs
-    // including NaN without requiring nnan. The m_FAbs ensures the floor arg
-    // is non-negative; stripSignOnlyFPOps handles sign variants inside fabs
-    // such as fabs(copysign(X,Y)).
+    // including NaN without requiring nnan. The m_FAbs match also ensures
+    // the floor argument is non-negative, so floor == trunc.
     Value *FAbsArg;
     if (match(Mag, m_Intrinsic<Intrinsic::floor>(m_FAbs(m_Value(FAbsArg)))) &&
-        stripSignOnlyFPOps(FAbsArg) == Sign) {
+        FAbsArg == Sign) {
       Value *Trunc = Builder.CreateUnaryIntrinsic(Intrinsic::trunc, Sign, II);
       return replaceOperand(*II, 0, Trunc);
     }



More information about the llvm-commits mailing list