From 73bb454f66a07a654e88a9f4511f37fc0be9ed29 Mon Sep 17 00:00:00 2001
From: Rex Xu <rex.xu@amd.com>
Date: Wed, 8 Nov 2023 15:37:44 +0800
Subject: [PATCH] Fix issues of fract(x)

The problem is similar to modf(x) when x=-0.0 or +INF/-INF. Although
SPIR-V spec says nothing about such cases, the OpenCL spec does define
the results of those operations as follow:

  fract(-0.0) = -0.0
  fract(+INF) = 0.0
  fract(-INF) = -0.0

Hence, we follow what have been done for modf.
---
 lgc/include/lgc/state/IntrinsDefs.h       | 14 ---------
 lgc/interface/lgc/Builder.h               | 14 +++++++++
 llpc/translator/lib/SPIRV/SPIRVReader.cpp | 37 +++++++++++++++++++++--
 3 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/lgc/include/lgc/state/IntrinsDefs.h b/lgc/include/lgc/state/IntrinsDefs.h
index f025fec45b..05b2920fba 100644
--- a/lgc/include/lgc/state/IntrinsDefs.h
+++ b/lgc/include/lgc/state/IntrinsDefs.h
@@ -466,20 +466,6 @@ enum BufDstSel {
   BUF_DST_SEL_W = 7, // SEL_W (W)
 };
 
-// Bits in mask supplied to v_cmp_class
-enum CmpClass {
-  SignalingNaN = 1,
-  QuietNaN = 2,
-  NegativeInfinity = 4,
-  NegativeNormal = 8,
-  NegativeSubnormal = 0x10,
-  NegativeZero = 0x20,
-  PositiveZero = 0x40,
-  PositiveSubnormal = 0x80,
-  PositiveNormal = 0x100,
-  PositiveInfinity = 0x200
-};
-
 // Represents register fields of SPI_PS_INPUT_ADDR.
 union SpiPsInputAddr {
   struct {
diff --git a/lgc/interface/lgc/Builder.h b/lgc/interface/lgc/Builder.h
index b8a64f1b51..4639ed4a7e 100644
--- a/lgc/interface/lgc/Builder.h
+++ b/lgc/interface/lgc/Builder.h
@@ -345,6 +345,20 @@ class BuilderDefs : public BuilderCommon {
     ImageAtomicFMax = 12, // Atomic operation: fmax
     ImageAtomicFAdd = 13  // Atomic operation: fadd
   };
+
+  // Bits in mask supplied to createIsFPClass
+  enum CmpClass {
+    SignalingNaN = 1,
+    QuietNaN = 2,
+    NegativeInfinity = 4,
+    NegativeNormal = 8,
+    NegativeSubnormal = 0x10,
+    NegativeZero = 0x20,
+    PositiveZero = 0x40,
+    PositiveSubnormal = 0x80,
+    PositiveNormal = 0x100,
+    PositiveInfinity = 0x200
+  };
 };
 
 // =====================================================================================================================
diff --git a/llpc/translator/lib/SPIRV/SPIRVReader.cpp b/llpc/translator/lib/SPIRV/SPIRVReader.cpp
index 6d677c897a..8011085926 100644
--- a/llpc/translator/lib/SPIRV/SPIRVReader.cpp
+++ b/llpc/translator/lib/SPIRV/SPIRVReader.cpp
@@ -8808,9 +8808,42 @@ Value *SPIRVToLLVM::transGLSLExtInst(SPIRVExtInst *extInst, BasicBlock *bb) {
     // Round up to whole number
     return getBuilder()->CreateUnaryIntrinsic(Intrinsic::ceil, args[0]);
 
-  case GLSLstd450Fract:
+  case GLSLstd450Fract: {
     // Get fractional part
-    return getBuilder()->CreateFract(args[0]);
+    auto fract = getBuilder()->CreateFract(args[0]);
+
+    // NOTE: Although SPIR-V spec says nothing about such cases: fract(-0.0), fract(+INF), fract(-INF), OpenCL spec does
+    // have following definitions:
+    //
+    //  fract(-0.0) = -0.0
+    //  fract(+INF) = +0.0
+    //  fract(-INF) = -0.0
+    //
+    // When we follow it, we have two issues that are similar to modf(x):
+    //
+    //   1. When we input x=+INF/-INF to above formula, we finally get the computation of (-INF) + INF or INF - INF.
+    //      The result is NaN returned by HW.
+    //   2. When we input x=-0.0 to above formula, we finally get the addition of (-0.0) + 0.0. The result is +0.0
+    //      returned by HW.
+    //
+    // Hence, we have to manually check those special cases:
+    //
+    //   y = fract(x)
+    //   y = x == -0.0 || x == INF ? copysign(0.0, x) : y, when either NSZ or NoInfs is not present
+    unsigned checkFlags = 0;
+    if (!getBuilder()->getFastMathFlags().noSignedZeros())
+      checkFlags |= lgc::Builder::CmpClass::NegativeZero;
+    if (!getBuilder()->getFastMathFlags().noInfs())
+      checkFlags |= (lgc::Builder::CmpClass::NegativeZero | lgc::Builder::CmpClass::NegativeZero);
+
+    if (checkFlags) {
+      Value *isNegZeroOrInf = getBuilder()->createIsFPClass(args[0], checkFlags);
+      Value *signedZero = getBuilder()->CreateCopySign(ConstantFP::getNullValue(args[0]->getType()), args[0]);
+      fract = getBuilder()->CreateSelect(isNegZeroOrInf, signedZero, fract);
+    }
+
+    return fract;
+  }
 
   case GLSLstd450Radians:
     // Convert from degrees to radians