Repository: cupy/cupy
Pull request: Fix compile error from `inf`/`nan` in cupy.fuse
Close #7118.

Problem statement:
[Discussion needed] Add ways to write `inf` and `nan` as literals in fused and user-defined kernels.
### Description

Currently, literals such as `inf` and `nan` cannot be used inside a `ElementwiseKernel` or a fused kernel. 
To test whether that's the case, we can write down the following code.
```python
@cp.fuse
def test_inf_nan(arr):
    arr[:] = cp.inf
    arr[:] = cp.nan
    arr[:] = 0
    return arr
print(test_inf_nan(cp.linspace(0,4,10)))
```
The compiler will spit out two errors regarding the fused kernel:
```
error: identifier "inf" is undefined
error: a value of type "double (*)(const char *)" cannot be used to initialize an entity of type "const double"
```
However, I am not so sure whether this is something that could be useful. Here are some of the pros and cons that I can think of:

pros:
- Functions that accept those literals will have a easier time when being fused. Such as `cupy.clip`.
- It might be helpful for compile-time-optimizations.

cons:
- The actual use case for those literals is quite scarce.
- We can always store those literals inside a variable and pass them into the kernels when calling the functions instead.

Any suggestions would be appreciated, thanks in advance!

### Additional Information

_No response_

Hints from issue discussion:
I guess it's only a matter of float-to-string conversion here.
https://github.com/cupy/cupy/blob/3ac3f9d6bffac33bccd236fc4306a2f83b90c4f5/cupy/_core/fusion.pyx#L121-L122

Maybe @asi1024 can better follow up on this :)
@berryboy2012 Thank you for your reporting. Fixed in #7122!

Commit message: Fix compile error from inf/nan in cupy.fuse

Patch to review:
diff --git a/cupy/_core/fusion.pyx b/cupy/_core/fusion.pyx
index 472235de138..89f6bb803c8 100644
--- a/cupy/_core/fusion.pyx
+++ b/cupy/_core/fusion.pyx
@@ -119,7 +119,15 @@ class _FusionVarCUDA(object):
         elif isinstance(val, complex):
             init = '({}, {})'.format(c.real, c.imag)
         elif isinstance(val, (int, float)):
-            init = '= {}'.format(c)
+            if numpy.isnan(val):
+                init = '= __int_as_float(0x7f800001)'
+            elif numpy.isinf(val):
+                if val > 0:
+                    init = '= __int_as_float(0x7f800000)'
+                else:
+                    init = '= __int_as_float(0xff800000)'
+            else:
+                init = '= {}'.format(c)
         else:
             raise TypeError('Invalid constant type: {}'.format(type(c)))
         return 'const {} v{} {};\n'.format(ctype, self.index, init)
diff --git a/tests/cupy_tests/core_tests/fusion_tests/test_misc.py b/tests/cupy_tests/core_tests/fusion_tests/test_misc.py
index b33c10f57a1..7bca94a60cf 100644
--- a/tests/cupy_tests/core_tests/fusion_tests/test_misc.py
+++ b/tests/cupy_tests/core_tests/fusion_tests/test_misc.py
@@ -153,6 +153,33 @@ def f(x, y, z):
         return f(x, None, z) + f(x, y, z)
 
 
+class TestSpecialValues(FusionTestBase):
+
+    @testing.for_float_dtypes()
+    @fusion_utils.check_fusion(generate_inputs_args=(1,))
+    def test_nan(self, xp, dtype):
+        def func(x):
+            x[:] = cupy.nan
+            return x
+        return func
+
+    @testing.for_float_dtypes()
+    @fusion_utils.check_fusion(generate_inputs_args=(1,))
+    def test_inf(self, xp, dtype):
+        def func(x):
+            x[:] = cupy.inf
+            return x
+        return func
+
+    @testing.for_float_dtypes()
+    @fusion_utils.check_fusion(generate_inputs_args=(1,))
+    def test_neginf(self, xp, dtype):
+        def func(x):
+            x[:] = -cupy.inf
+            return x
+        return func
+
+
 @testing.gpu
 class TestFusionDecorator(unittest.TestCase):
     def test_without_paren(self):


Code review comments:
diff_hunk: @@ -119,7 +119,15 @@ class _FusionVarCUDA(object):
         elif isinstance(val, complex):
             init = '({}, {})'.format(c.real, c.imag)
         elif isinstance(val, (int, float)):
-            init = '= {}'.format(c)
+            if numpy.isnan(val):
+                init = '= __int_as_float(0x7f800001)'
path: cupy/_core/fusion.pyx
text: @user1:
nit:
`cuda/include/math_constants.h`
seems to define `CUDART_NAN` `CUDART_NAN_F` and INF ones
This is purely a cosmetic change as these values will not change, but you can use the mnemonic if you feel in the mood :)

@author:
Fixed!

diff_hunk: @@ -119,7 +119,15 @@ class _FusionVarCUDA(object):
         elif isinstance(val, complex):
             init = '({}, {})'.format(c.real, c.imag)
         elif isinstance(val, (int, float)):
-            init = '= {}'.format(c)
+            if numpy.isnan(val):
path: cupy/_core/fusion.pyx
text: @user1:
(As discussed offline) there are constants defined in CUDA header file, but is not included by default so I think it's ok to remain as-is.
https://github.com/cupy/cupy/blob/master/cupy/_core/include/cupy/math_constants.h

Btw, the variable could be a double?