[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