[llvm] [InstCombine] Don't fold `sin(-x)` to `-sin(x)` if `denormals` may flush to `+0.0` (PR #227039)

Akash Manna via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 28 09:46:13 PDT 2026


https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/227039

Fixes #226871

`f(-x) -> -f(x)` for odd functions (`sin`, `sinh`, `tan`, `tanh`, `erf` and the matching intrinsics) is only exact if denormals are kept or flushed with their sign. With `positivezero`, a denormal of either sign becomes `+0.0`, so with `denormal_fpenv(ieee|positivezero)`, `sin(-psub)` is `sin(+0.0) = +0.0`, while the folded `-sin(psub)` is `-0.0`. The report only covers the input side, but output flushing breaks it the same way, and `dynamic` may be `positivezero` at run time.

The fold is now skipped when the denormal mode for the call's type may flush an input or result to `+0.0`, using the existing `inputsMayBePositiveZero()`/`outputsMayBePositiveZero()` helpers. The intrinsic fold in InstCombineCalls gets the same check, since a `memory(none)` `sin` libcall is turned into `llvm.sin` and would be folded there anyway. I didn't make `nsz` an exception, because it only lets the sign of zero inputs flip, and the zero here comes from flushing. Even functions like `cos` are fine in every mode and are left alone.


>From cee92319b301c02bfc06f8b7c9aec9d2975accc9 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 28 Sep 2026 22:13:57 +0530
Subject: [PATCH] [InstCombine] Don't fold sin(-x) to -sin(x) if denormals may
 flush to +0.0

f(-x) -> -f(x) for odd functions (sin, sinh, tan, tanh, erf and the
matching intrinsics) is only exact if denormals are kept or flushed with
their sign. With positivezero, a denormal of either sign becomes +0.0,
so sin(-psub) = sin(+0.0) = +0.0 while the folded -sin(psub) is -0.0.
Output flushing breaks it the same way, and dynamic may be positivezero
at run time.

Skip the fold when the call's denormal mode may flush an input or result
to +0.0. The intrinsic fold in InstCombineCalls needs the same check,
since a memory(none) sin libcall is turned into llvm.sin and would be
folded there otherwise.

Fixes #226871.
---
 .../InstCombine/InstCombineCalls.cpp          |  10 +-
 .../lib/Transforms/Utils/SimplifyLibCalls.cpp |  10 +-
 llvm/test/Transforms/InstCombine/cos-1.ll     | 122 ++++++++++++++++++
 .../InstCombine/cos-sin-intrinsic.ll          |  56 ++++++++
 4 files changed, 196 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 7fae7b8cea710..30d2d7e333804 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -997,6 +997,13 @@ static bool inputDenormalIsDAZ(const Function &F, const Type *Ty) {
   return F.getDenormalMode(Ty->getFltSemantics()).inputsAreZero();
 }
 
+/// Flushing a denormal to +0.0 breaks f(-x) = -f(x) for odd f.
+static bool mayFlushDenormalsToPositiveZero(const CallInst *CI) {
+  DenormalMode Mode = CI->getFunction()->getDenormalMode(
+      CI->getType()->getScalarType()->getFltSemantics());
+  return Mode.inputsMayBePositiveZero() || Mode.outputsMayBePositiveZero();
+}
+
 /// \returns the compare predicate type if the test performed by
 /// llvm.is.fpclass(x, \p Mask) is equivalent to fcmp o__ x, 0.0 with the
 /// floating-point environment assumed for \p F for type \p Ty
@@ -3415,7 +3422,8 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
   case Intrinsic::tan:
   case Intrinsic::tanh: {
     Value *X;
-    if (match(II->getArgOperand(0), m_OneUse(m_FNeg(m_Value(X))))) {
+    if (match(II->getArgOperand(0), m_OneUse(m_FNeg(m_Value(X)))) &&
+        !mayFlushDenormalsToPositiveZero(II)) {
       // f(-x) --> -f(x)
       // for f in {sin, sinh, tan, tanh}
       Value *NewFunc = Builder.CreateUnaryIntrinsic(IID, X, II);
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index 5da7349b4d9c9..f6235b5b98fcb 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -3118,12 +3118,20 @@ static bool insertSinCosCall(IRBuilderBase &B, Function *OrigCallee, Value *Arg,
   return true;
 }
 
+/// Flushing a denormal to +0.0 breaks f(-x) = -f(x) for odd f.
+static bool mayFlushDenormalsToPositiveZero(const CallInst *CI) {
+  DenormalMode Mode = CI->getFunction()->getDenormalMode(
+      CI->getType()->getScalarType()->getFltSemantics());
+  return Mode.inputsMayBePositiveZero() || Mode.outputsMayBePositiveZero();
+}
+
 static Value *optimizeSymmetricCall(CallInst *CI, bool IsEven,
                                     IRBuilderBase &B) {
   Value *X;
   Value *Src = CI->getArgOperand(0);
 
-  if (match(Src, m_OneUse(m_FNeg(m_Value(X))))) {
+  if (match(Src, m_OneUse(m_FNeg(m_Value(X)))) &&
+      (IsEven || !mayFlushDenormalsToPositiveZero(CI))) {
     auto *Call = B.CreateCall(CI->getCalledFunction(), {X}, /*FMFSource=*/CI);
     auto *CallInst = copyFlags(*CI, Call);
     if (IsEven) {
diff --git a/llvm/test/Transforms/InstCombine/cos-1.ll b/llvm/test/Transforms/InstCombine/cos-1.ll
index 7c66f27a7f5c2..926daf836def0 100644
--- a/llvm/test/Transforms/InstCombine/cos-1.ll
+++ b/llvm/test/Transforms/InstCombine/cos-1.ll
@@ -108,6 +108,16 @@ define float @cosf_unary_negated_arg_FMF(float %x) {
   ret float %r
 }
 
+define float @cosf_unary_negated_arg_positivezero(float %x) denormal_fpenv(positivezero|positivezero) {
+; ANY-LABEL: @cosf_unary_negated_arg_positivezero(
+; ANY-NEXT:    [[R:%.*]] = call float @cosf(float [[X:%.*]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @cosf(float %neg)
+  ret float %r
+}
+
 ; cos(fabs(x)) -> cos(x)
 
 define double @cos_unary_fabs_arg(double %x) {
@@ -252,6 +262,118 @@ define float @sinf_unary_negated_arg_FMF(float %x) {
   ret float %r
 }
 
+; sin(-x) -> -sin(x) is invalid if denormals may be flushed to +0.0.
+
+define float @sinf_unary_negated_arg_dapz(float %x) denormal_fpenv(ieee|positivezero) {
+; ANY-LABEL: @sinf_unary_negated_arg_dapz(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call float @sinf(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_ftpz(float %x) denormal_fpenv(positivezero|ieee) {
+; ANY-LABEL: @sinf_unary_negated_arg_ftpz(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call float @sinf(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_dynamic(float %x) denormal_fpenv(dynamic|dynamic) {
+; ANY-LABEL: @sinf_unary_negated_arg_dynamic(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call float @sinf(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_dapz_memory_none(float %x) denormal_fpenv(ieee|positivezero) {
+; ANY-LABEL: @sinf_unary_negated_arg_dapz_memory_none(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call float @llvm.sin.f32(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg) memory(none)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_dapz_nsz(float %x) denormal_fpenv(ieee|positivezero) {
+; ANY-LABEL: @sinf_unary_negated_arg_dapz_nsz(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call nsz float @sinf(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call nsz float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_ftpz_nsz(float %x) denormal_fpenv(positivezero|ieee) {
+; ANY-LABEL: @sinf_unary_negated_arg_ftpz_nsz(
+; ANY-NEXT:    [[NEG:%.*]] = fneg float [[X:%.*]]
+; ANY-NEXT:    [[R:%.*]] = call nsz float @sinf(float [[NEG]])
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call nsz float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_ftz_daz(float %x) denormal_fpenv(preservesign|preservesign) {
+; ANY-LABEL: @sinf_unary_negated_arg_ftz_daz(
+; ANY-NEXT:    [[TMP1:%.*]] = call float @sinf(float [[X:%.*]])
+; ANY-NEXT:    [[R:%.*]] = fneg float [[TMP1]]
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_daz(float %x) denormal_fpenv(ieee|preservesign) {
+; ANY-LABEL: @sinf_unary_negated_arg_daz(
+; ANY-NEXT:    [[TMP1:%.*]] = call float @sinf(float [[X:%.*]])
+; ANY-NEXT:    [[R:%.*]] = fneg float [[TMP1]]
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define float @sinf_unary_negated_arg_f32_ieee(float %x) denormal_fpenv(positivezero, float: ieee) {
+; ANY-LABEL: @sinf_unary_negated_arg_f32_ieee(
+; ANY-NEXT:    [[TMP1:%.*]] = call float @sinf(float [[X:%.*]])
+; ANY-NEXT:    [[R:%.*]] = fneg float [[TMP1]]
+; ANY-NEXT:    ret float [[R]]
+;
+  %neg = fneg float %x
+  %r = call float @sinf(float %neg)
+  ret float %r
+}
+
+define double @sin_unary_negated_arg_f32_positivezero(double %x) denormal_fpenv(float: positivezero) {
+; ANY-LABEL: @sin_unary_negated_arg_f32_positivezero(
+; ANY-NEXT:    [[TMP1:%.*]] = call double @sin(double [[X:%.*]])
+; ANY-NEXT:    [[R:%.*]] = fneg double [[TMP1]]
+; ANY-NEXT:    ret double [[R]]
+;
+  %neg = fneg double %x
+  %r = call double @sin(double %neg)
+  ret double %r
+}
+
 declare void @use(double)
 
 define double @sin_negated_arg_extra_use(double %x) {
diff --git a/llvm/test/Transforms/InstCombine/cos-sin-intrinsic.ll b/llvm/test/Transforms/InstCombine/cos-sin-intrinsic.ll
index 7051e8f0f7ff5..6e7d0bc15e988 100644
--- a/llvm/test/Transforms/InstCombine/cos-sin-intrinsic.ll
+++ b/llvm/test/Transforms/InstCombine/cos-sin-intrinsic.ll
@@ -92,6 +92,16 @@ define <2 x float> @unary_fneg_cos_fmf(<2 x float> %x){
   ret <2 x float> %r
 }
 
+define <2 x float> @unary_fneg_cos_positivezero(<2 x float> %x) denormal_fpenv(positivezero|positivezero) {
+; CHECK-LABEL: @unary_fneg_cos_positivezero(
+; CHECK-NEXT:    [[R:%.*]] = call <2 x float> @llvm.cos.v2f32(<2 x float> [[X:%.*]])
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %negx = fneg <2 x float> %x
+  %r = call <2 x float> @llvm.cos.v2f32(<2 x float> %negx)
+  ret <2 x float> %r
+}
+
 define float @fabs_f32(float %x) {
 ; CHECK-LABEL: @fabs_f32(
 ; CHECK-NEXT:    [[COS:%.*]] = call float @llvm.cos.f32(float [[X:%.*]])
@@ -214,6 +224,52 @@ define <2 x float> @unary_fneg_sin_fmf(<2 x float> %x){
   ret <2 x float> %r
 }
 
+; sin(-x) -> -sin(x) is invalid if denormals may be flushed to +0.0.
+
+define <2 x float> @unary_fneg_sin_dapz(<2 x float> %x) denormal_fpenv(ieee|positivezero) {
+; CHECK-LABEL: @unary_fneg_sin_dapz(
+; CHECK-NEXT:    [[NEGX:%.*]] = fneg <2 x float> [[X:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = call <2 x float> @llvm.sin.v2f32(<2 x float> [[NEGX]])
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %negx = fneg <2 x float> %x
+  %r = call <2 x float> @llvm.sin.v2f32(<2 x float> %negx)
+  ret <2 x float> %r
+}
+
+define <2 x float> @unary_fneg_sin_ftpz(<2 x float> %x) denormal_fpenv(positivezero|ieee) {
+; CHECK-LABEL: @unary_fneg_sin_ftpz(
+; CHECK-NEXT:    [[NEGX:%.*]] = fneg <2 x float> [[X:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = call <2 x float> @llvm.sin.v2f32(<2 x float> [[NEGX]])
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %negx = fneg <2 x float> %x
+  %r = call <2 x float> @llvm.sin.v2f32(<2 x float> %negx)
+  ret <2 x float> %r
+}
+
+define <2 x float> @unary_fneg_sin_dynamic(<2 x float> %x) denormal_fpenv(dynamic|dynamic) {
+; CHECK-LABEL: @unary_fneg_sin_dynamic(
+; CHECK-NEXT:    [[NEGX:%.*]] = fneg <2 x float> [[X:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = call <2 x float> @llvm.sin.v2f32(<2 x float> [[NEGX]])
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %negx = fneg <2 x float> %x
+  %r = call <2 x float> @llvm.sin.v2f32(<2 x float> %negx)
+  ret <2 x float> %r
+}
+
+define <2 x float> @unary_fneg_sin_ftz_daz(<2 x float> %x) denormal_fpenv(preservesign|preservesign) {
+; CHECK-LABEL: @unary_fneg_sin_ftz_daz(
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x float> @llvm.sin.v2f32(<2 x float> [[X:%.*]])
+; CHECK-NEXT:    [[R:%.*]] = fneg <2 x float> [[TMP1]]
+; CHECK-NEXT:    ret <2 x float> [[R]]
+;
+  %negx = fneg <2 x float> %x
+  %r = call <2 x float> @llvm.sin.v2f32(<2 x float> %negx)
+  ret <2 x float> %r
+}
+
 define bfloat @constant_fold_sin_bf16() {
 ; CHECK-LABEL: @constant_fold_sin_bf16(
 ; CHECK-NEXT:    ret bfloat 4.785160e-01



More information about the llvm-commits mailing list