Bug type: miscompilation
Component: InstCombine
Bug location: llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp: InstCombinerImpl::SimplifyDemandedUseFPClass
getFPClassConstant
Issue title: copysign not preserving sign bit of a NaN
Labels: miscompilation
llvm:instcombine
Reported by: regehr

we're optimizing this:
```llvm
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(ninf nzero nsub nnorm) float @f(float %0) {
  %2 = call float @llvm.copysign.f32(float 0x7FF8000000000000, float %0)
  ret float %2
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
```

to:
```llvm
; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
define noundef nofpclass(ninf nzero nsub nnorm) float @f(float %0) local_unnamed_addr #0 {
  ret float 0x7FF8000000000000
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
```

Alive complains that the optimized code will flip the sign bit of a NaN, while the unoptimized code preserved it as required by LangRef's specification of the copysign semantics:
> The returned value is completely identical to the first operand except for the sign bit; in particular, if the input is a NaN, then the quiet/signaling bit and payload are perfectly preserved.

If any of the individual flags in `nofpclass(ninf nzero nsub nnorm)` are removed, then we don't do this transformation. Am I missing something here where these flags, together, mean that it's legal to flip the sign of a NaN? Obviously the transformation is just fine when `nofpclass(nan)` is there

https://alive2.llvm.org/ce/z/ja96Yf

cc @dtcxzyw @nunoplopes 


Fix patch:
commit 1f69d6354a4b088bda5ee62c31411a75eead8b0c
Author: Yingwei Zheng <dtcxzyw2333@gmail.com>
Date:   Mon Apr 28 17:01:43 2025 +0800

    [InstCombine] Preserve the sign bit of NaN in `SimplifyDemandedUseFPClass` (#137287)
    
    Alive2: https://alive2.llvm.org/ce/z/uiUzEf
    
    Closes https://github.com/llvm/llvm-project/issues/137196.
    
    Note: To avoid regression in
    `ret_nofpclass_nopositives_copysign_nnan_flag`, the second commit takes
    FMF into account.

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 2c8939b5a051..a48854a191ca 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -1959,9 +1959,11 @@ static Constant *getFPClassConstant(Type *Ty, FPClassTest Mask) {
   }
 }
 
-Value *InstCombinerImpl::SimplifyDemandedUseFPClass(
-    Value *V, const FPClassTest DemandedMask, KnownFPClass &Known,
-    unsigned Depth, Instruction *CxtI) {
+Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
+                                                    FPClassTest DemandedMask,
+                                                    KnownFPClass &Known,
+                                                    unsigned Depth,
+                                                    Instruction *CxtI) {
   assert(Depth <= MaxAnalysisRecursionDepth && "Limit Search Depth");
   Type *VTy = V->getType();
 
@@ -1985,7 +1987,12 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(
   if (!I->hasOneUse())
     return nullptr;
 
-  // TODO: Should account for nofpclass/FastMathFlags on current instruction
+  if (auto *FPOp = dyn_cast<FPMathOperator>(I)) {
+    if (FPOp->hasNoNaNs())
+      DemandedMask &= ~fcNan;
+    if (FPOp->hasNoInfs())
+      DemandedMask &= ~fcInf;
+  }
   switch (I->getOpcode()) {
   case Instruction::FNeg: {
     if (SimplifyDemandedFPClass(I, 0, llvm::fneg(DemandedMask), Known,
@@ -2013,13 +2020,13 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(
       if (SimplifyDemandedFPClass(I, 0, DemandedMaskAnySign, Known, Depth + 1))
         return I;
 
-      if ((DemandedMask & fcPositive) == fcNone) {
+      if ((DemandedMask & fcNegative) == DemandedMask) {
         // Roundabout way of replacing with fneg(fabs)
         I->setOperand(1, ConstantFP::get(VTy, -1.0));
         return I;
       }
 
-      if ((DemandedMask & fcNegative) == fcNone) {
+      if ((DemandedMask & fcPositive) == DemandedMask) {
         // Roundabout way of replacing with fabs
         I->setOperand(1, ConstantFP::getZero(VTy));
         return I;


Regression tests:
file: llvm/test/Transforms/InstCombine/simplify-demanded-fpclass.ll
commands: opt -S -passes=instcombine < %s
tests: test_name: ret_nofpclass_nopositives_copysign
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(pinf pzero psub pnorm) float @ret_nofpclass_nopositives_copysign(float %x, float %unknown.sign) {
  %copysign = call float @llvm.copysign.f32(float %x, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_no_negatives__copysign_unknown_select_pinf_rhs
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(ninf nzero nsub nnorm) float @ret_nofpclass_no_negatives__copysign_unknown_select_pinf_rhs(i1 %cond, float %x, float %unknown.sign) {
  %select = select i1 %cond, float %x, float 0x7FF0000000000000
  %copysign = call float @llvm.copysign.f32(float %select, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_nonegatives_copysign
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(ninf nzero nsub nnorm) float @ret_nofpclass_nonegatives_copysign(float %x, float %unknown.sign) {
  %copysign = call float @llvm.copysign.f32(float %x, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_no_negatives_noinf__copysign_unknown_select_pinf_rhs
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(inf nzero nsub nnorm) float @ret_nofpclass_no_negatives_noinf__copysign_unknown_select_pinf_rhs(i1 %cond, float %x, float %unknown.sign) {
  %select = select i1 %cond, float %x, float 0x7FF0000000000000
  %copysign = call float @llvm.copysign.f32(float %select, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_no_positives_noinf__copysign_unknown_select_pinf_rhs
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(inf pzero psub pnorm) float @ret_nofpclass_no_positives_noinf__copysign_unknown_select_pinf_rhs(i1 %cond, float %x, float %unknown.sign) {
  %select = select i1 %cond, float %x, float 0x7FF0000000000000
  %copysign = call float @llvm.copysign.f32(float %select, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_nopositives_nonan_copysign
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(nan pinf pzero psub pnorm) float @ret_nofpclass_nopositives_nonan_copysign(float %x, float %unknown.sign) {
  %copysign = call float @llvm.copysign.f32(float %x, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_no_positives__copysign_unknown_select_pinf_rhs
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(pinf pzero psub pnorm) float @ret_nofpclass_no_positives__copysign_unknown_select_pinf_rhs(i1 %cond, float %x, float %unknown.sign) {
  %select = select i1 %cond, float %x, float 0x7FF0000000000000
  %copysign = call float @llvm.copysign.f32(float %select, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: ret_nofpclass_nopositive_finites_copysign_nnan_ninf_flag
test_body: ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.copysign.f32(float, float) #0

define nofpclass(pzero psub pnorm) float @ret_nofpclass_nopositive_finites_copysign_nnan_ninf_flag(float %x, float %unknown.sign) {
  %copysign = call nnan ninf float @llvm.copysign.f32(float %x, float %unknown.sign)
  ret float %copysign
}

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }