Repository: cython/cython

Issue:
Optimized abs should be nogil-safe
From the following:
```cython
cdef double foo(double x):
    return abs(x)

cdef int bar(int x):
    return abs(x)
```
the resulting C/C++ code calls `fabs`/`abs` thanks to #1255. If you add `nogil` on these functions, then Cython will complain with:
```
    return abs(x)
             ^
------------------------------------------------------------
test_abs.pyx:2:14: Calling gil-requiring function not allowed without gil
```
Because of #1837, I'm not sure about `bar`, but at least `foo` should not require the GIL.


Discussion:
True. It's probably as easy as adding nogil to the declaration of the C functions in `Builtin.py`. PR welcome. Tests should go into `tests/run/builtin_abs.pyx`.

Resolving patch:
diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py
--- a/Cython/Compiler/Builtin.py
+++ b/Cython/Compiler/Builtin.py
@@ -30,17 +30,19 @@
 class _BuiltinOverride(object):
     def __init__(self, py_name, args, ret_type, cname, py_equiv="*",
                  utility_code=None, sig=None, func_type=None,
-                 is_strict_signature=False, builtin_return_type=None):
+                 is_strict_signature=False, builtin_return_type=None,
+                 nogil=None):
         self.py_name, self.cname, self.py_equiv = py_name, cname, py_equiv
         self.args, self.ret_type = args, ret_type
         self.func_type, self.sig = func_type, sig
         self.builtin_return_type = builtin_return_type
         self.is_strict_signature = is_strict_signature
         self.utility_code = utility_code
+        self.nogil = nogil
 
     def build_func_type(self, sig=None, self_arg=None):
         if sig is None:
-            sig = Signature(self.args, self.ret_type)
+            sig = Signature(self.args, self.ret_type, nogil=self.nogil)
             sig.exception_check = False  # not needed for the current builtins
         func_type = sig.function_type(self_arg)
         if self.is_strict_signature:
@@ -92,13 +94,13 @@ def declare_in_type(self, self_type):
 builtin_function_table = [
     # name,        args,   return,  C API func,           py equiv = "*"
     BuiltinFunction('abs',        "d",    "d",     "fabs",
-                    is_strict_signature = True),
+                    is_strict_signature=True, nogil=True),
     BuiltinFunction('abs',        "f",    "f",     "fabsf",
-                    is_strict_signature = True),
+                    is_strict_signature=True, nogil=True),
     BuiltinFunction('abs',        "i",    "i",     "abs",
-                    is_strict_signature = True),
+                    is_strict_signature=True, nogil=True),
     BuiltinFunction('abs',        "l",    "l",     "labs",
-                    is_strict_signature = True),
+                    is_strict_signature=True, nogil=True),
     BuiltinFunction('abs',        None,    None,   "__Pyx_abs_longlong",
                 utility_code = UtilityCode.load("abs_longlong", "Builtins.c"),
                 func_type = PyrexTypes.CFuncType(
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py
--- a/Cython/Compiler/TypeSlots.py
+++ b/Cython/Compiler/TypeSlots.py
@@ -86,7 +86,7 @@ class Signature(object):
         'z': "-1",
     }
 
-    def __init__(self, arg_format, ret_format):
+    def __init__(self, arg_format, ret_format, nogil=0):
         self.has_dummy_arg = 0
         self.has_generic_args = 0
         if arg_format[:1] == '-':
@@ -100,6 +100,7 @@ def __init__(self, arg_format, ret_format):
         self.error_value = self.error_value_map.get(ret_format, None)
         self.exception_check = ret_format != 'r' and self.error_value is not None
         self.is_staticmethod = False
+        self.nogil = nogil
 
     def __repr__(self):
         return '<Signature[%s(%s%s)]>' % (
@@ -149,7 +150,8 @@ def function_type(self, self_arg_override=None):
         exc_value = self.exception_value()
         return PyrexTypes.CFuncType(
             ret_type, args, exception_value=exc_value,
-            exception_check=self.exception_check)
+            exception_check=self.exception_check,
+            nogil=self.nogil)
 
     def method_flags(self):
         if self.ret_format == "O":


Test patch:
diff --git a/tests/run/builtin_abs.pyx b/tests/run/builtin_abs.pyx
--- a/tests/run/builtin_abs.pyx
+++ b/tests/run/builtin_abs.pyx
@@ -59,6 +59,27 @@ def int_abs(int a):
     """
     return abs(a)
 
+@cython.overflowcheck(True)
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = 'abs']")
+cdef int c_int_abs(int a) nogil except *:
+    return abs(a)
+
+def test_c_int_abs(int a):
+    """
+    >>> test_c_int_abs(-5) == 5
+    True
+    >>> test_c_int_abs(-5.1) == 5
+    True
+    >>> test_c_int_abs(-max_int-1)     #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    OverflowError: ...
+    >>> test_c_int_abs(max_int) == abs(max_int)  or (max_int, test_c_int_abs(max_int), abs(max_int))
+    True
+    """
+    return c_int_abs(a)
+
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']")
 @cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']",
                                  "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
@@ -69,6 +90,19 @@ def uint_abs(unsigned int a):
     """
     return abs(a)
 
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']")
+@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']",
+                                 "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
+cdef unsigned int c_uint_abs(unsigned int a) nogil:
+    return abs(a)
+
+def test_c_uint_abs(unsigned int a):
+    """
+    >>> test_c_uint_abs(max_int) == abs(max_int)  or (max_int, test_c_uint_abs(max_int), abs(max_int))
+    True
+    """
+    return c_uint_abs(a)
+
 @cython.overflowcheck(True)
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
                                 "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
@@ -87,6 +121,27 @@ def long_abs(long a):
     """
     return abs(a)
 
+@cython.overflowcheck(True)
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
+cdef long c_long_abs(long a) nogil except *:
+    return abs(a)
+
+def test_c_long_abs(long a):
+    """
+    >>> test_c_long_abs(-5) == 5
+    True
+    >>> test_c_long_abs(-5.1) == 5
+    True
+    >>> test_c_long_abs(-max_long-1)     #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    OverflowError: ...
+    >>> test_c_long_abs(max_long) == abs(max_long)  or (max_long, test_c_long_abs(max_long), abs(max_long))
+    True
+    """
+    return c_long_abs(a)
+
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']")
 @cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']",
                                  "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
@@ -99,6 +154,21 @@ def ulong_abs(unsigned long a):
     """
     return abs(a)
 
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']")
+@cython.test_fail_if_path_exists("//ReturnStatNode//NameNode[@entry.cname = 'abs']",
+                                 "//ReturnStatNode//NameNode[@entry.cname = 'labs']")
+cdef unsigned long c_ulong_abs(unsigned long a) nogil:
+    return abs(a)
+
+def test_c_ulong_abs(unsigned long a):
+    """
+    >>> test_c_ulong_abs(max_long) == abs(max_long)  or (max_int, test_c_ulong_abs(max_long), abs(max_long))
+    True
+    >>> test_c_ulong_abs(max_long + 5) == abs(max_long + 5)  or (max_long + 5, test_c_ulong_abs(max_long + 5), abs(max_long + 5))
+    True
+    """
+    return c_ulong_abs(a)
+
 @cython.overflowcheck(True)
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
                                 "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_longlong']")
@@ -115,6 +185,25 @@ def long_long_abs(long long a):
     """
     return abs(a)
 
+@cython.overflowcheck(True)
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_abs_longlong']")
+cdef long long c_long_long_abs(long long a) nogil except *:
+    return abs(a)
+
+def test_c_long_long_abs(long long a):
+    """
+    >>> test_c_long_long_abs(-(2**33)) == 2**33
+    True
+    >>> test_c_long_long_abs(-max_long_long-1)     #doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    OverflowError: ...
+    >>> test_c_long_long_abs(max_long_long) == abs(max_long_long) or (max_long_long, test_c_long_long_abs(max_long_long), abs(max_long_long))
+    True
+    """
+    return c_long_long_abs(a)
+
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
                                 "//ReturnStatNode//NameNode[@entry.cname = 'fabs']")
 def double_abs(double a):
@@ -126,6 +215,20 @@ def double_abs(double a):
     """
     return abs(a)
 
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = 'fabs']")
+cdef double c_double_abs(double a) nogil:
+    return abs(a)
+
+def test_c_double_abs(double a):
+    """
+    >>> test_c_double_abs(-5)
+    5.0
+    >>> test_c_double_abs(-5.5)
+    5.5
+    """
+    return c_double_abs(a)
+
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
                                 "//ReturnStatNode//NameNode[@entry.cname = 'fabsf']")
 def float_abs(float a):
@@ -137,6 +240,20 @@ def float_abs(float a):
     """
     return abs(a)
 
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = 'fabsf']")
+cdef float c_float_abs(float a) nogil:
+    return abs(a)
+
+def test_c_float_abs(float a):
+    """
+    >>> test_c_float_abs(-5)
+    5.0
+    >>> test_c_float_abs(-5.5)
+    5.5
+    """
+    return c_float_abs(a)
+
 @cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
                                 "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_c_abs_double']")
 def complex_abs(complex a):
@@ -147,3 +264,17 @@ def complex_abs(complex a):
     5.5
     """
     return abs(a)
+
+@cython.test_assert_path_exists("//ReturnStatNode//NameNode[@entry.name = 'abs']",
+                                "//ReturnStatNode//NameNode[@entry.cname = '__Pyx_c_abs_double']")
+cdef double c_complex_abs(complex a) nogil:
+    return abs(a)
+
+def test_c_complex_abs(complex a):
+    """
+    >>> test_c_complex_abs(-5j)
+    5.0
+    >>> test_c_complex_abs(-5.5j)
+    5.5
+    """
+    return c_complex_abs(a)