We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fatal Python error: PyEval_SaveThread when creating ufunc with nogil [BUG] 
### Describe the bug

I'm generating ufuncs for my fortran library by the new version 3.0.0b1 of cython, using the @cython.ufunc function decorator.
I declare the function as nogil. and call it in python script. It works fine when the input array length is less than 500. When the input array length is greater than 500, it stops and throws a thread error.
```
Fatal Python error: PyEval_SaveThread: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)
Python runtime state: initialized
```
I'm using the mingw-w64 compiler provided by msys2 on windows, but that doesn't seem to be the cause of the error. I transferred the program to an ubuntu machine and found the same error.

### Code to reproduce the behaviour:

```cython
# test_ufunc.pyx
cimport cython

@cython.ufunc
cdef double add_one(double x) nogil:
    return x+1
```

```python
import test_ufunc
import numpy as np

x=np.linspace(-1,1,501)
x_add=test_ufunc.add_one(x)
print(x_add)
```

### Expected behaviour

No errors, result returns successfully.

### OS

Ubuntu and Windows

### Python version

3.10.6 (Ubuntu), 3.11.2 (Windows)

### Cython version

3.0.0b1

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Cython/Compiler/UFuncs.py`
Content:
```
1 from . import (
2     Nodes,
3     ExprNodes,
4     FusedNode,
5     TreeFragment,
6     Pipeline,
7     ParseTreeTransforms,
8     Naming,
9     UtilNodes,
10 )
11 from .Errors import error
12 from . import PyrexTypes
13 from .UtilityCode import CythonUtilityCode
14 from .Code import TempitaUtilityCode, UtilityCode
15 from .Visitor import PrintTree, TreeVisitor, VisitorTransform
16 
17 numpy_int_types = [
18     "NPY_BYTE",
19     "NPY_INT8",
20     "NPY_SHORT",
21     "NPY_INT16",
22     "NPY_INT",
23     "NPY_INT32",
24     "NPY_LONG",
25     "NPY_LONGLONG",
26     "NPY_INT64",
27 ]
28 numpy_uint_types = [tp.replace("NPY_", "NPY_U") for tp in numpy_int_types]
29 # note: half float type is deliberately omitted
30 numpy_numeric_types = (
31     numpy_int_types
32     + numpy_uint_types
33     + [
34         "NPY_FLOAT",
35         "NPY_FLOAT32",
36         "NPY_DOUBLE",
37         "NPY_FLOAT64",
38         "NPY_LONGDOUBLE",
39     ]
40 )
41 
42 
43 def _get_type_constant(pos, type_):
44     if type_.is_complex:
45         # 'is' checks don't seem to work for complex types
46         if type_ == PyrexTypes.c_float_complex_type:
47             return "NPY_CFLOAT"
48         elif type_ == PyrexTypes.c_double_complex_type:
49             return "NPY_CDOUBLE"
50         elif type_ == PyrexTypes.c_longdouble_complex_type:
51             return "NPY_CLONGDOUBLE"
52     elif type_.is_numeric:
53         postfix = type_.empty_declaration_code().upper().replace(" ", "")
54         typename = "NPY_%s" % postfix
55         if typename in numpy_numeric_types:
56             return typename
57     elif type_.is_pyobject:
58         return "NPY_OBJECT"
59     # TODO possible NPY_BOOL to bint but it needs a cast?
60     # TODO NPY_DATETIME, NPY_TIMEDELTA, NPY_STRING, NPY_UNICODE and maybe NPY_VOID might be handleable
61     error(pos, "Type '%s' cannot be used as a ufunc argument" % type_)
62 
63 
64 class _FindCFuncDefNode(TreeVisitor):
65     """
66     Finds the CFuncDefNode in the tree
67 
68     The assumption is that there's only one CFuncDefNode
69     """
70 
71     found_node = None
72 
73     def visit_Node(self, node):
74         if self.found_node:
75             return
76         else:
77             self.visitchildren(node)
78 
79     def visit_CFuncDefNode(self, node):
80         self.found_node = node
81 
82     def __call__(self, tree):
83         self.visit(tree)
84         return self.found_node
85 
86 
87 def get_cfunc_from_tree(tree):
88     return _FindCFuncDefNode()(tree)
89 
90 
91 class _ArgumentInfo(object):
92     """
93     Everything related to defining an input/output argument for a ufunc
94 
95     type  - PyrexType
96     type_constant  - str such as "NPY_INT8" representing numpy dtype constants
97     """
98 
99     def __init__(self, type, type_constant):
100         self.type = type
101         self.type_constant = type_constant
102 
103 
104 class UFuncConversion(object):
105     def __init__(self, node):
106         self.node = node
107         self.global_scope = node.local_scope.global_scope()
108 
109         self.in_definitions = self.get_in_type_info()
110         self.out_definitions = self.get_out_type_info()
111 
112     def get_in_type_info(self):
113         definitions = []
114         for n, arg in enumerate(self.node.args):
115             type_const = _get_type_constant(self.node.pos, arg.type)
116             definitions.append(_ArgumentInfo(arg.type, type_const))
117         return definitions
118 
119     def get_out_type_info(self):
120         if self.node.return_type.is_ctuple:
121             components = self.node.return_type.components
122         else:
123             components = [self.node.return_type]
124         definitions = []
125         for n, type in enumerate(components):
126             definitions.append(
127                 _ArgumentInfo(type, _get_type_constant(self.node.pos, type))
128             )
129         return definitions
130 
131     def generate_cy_utility_code(self):
132         arg_types = [a.type for a in self.in_definitions]
133         out_types = [a.type for a in self.out_definitions]
134         inline_func_decl = self.node.entry.type.declaration_code(
135             self.node.entry.cname, pyrex=True
136         )
137         self.node.entry.used = True
138 
139         ufunc_cname = self.global_scope.next_id(self.node.entry.name + "_ufunc_def")
140 
141         context = dict(
142             func_cname=ufunc_cname,
143             in_types=arg_types,
144             out_types=out_types,
145             inline_func_call=self.node.entry.cname,
146             inline_func_declaration=inline_func_decl,
147             nogil=self.node.entry.type.nogil,
148         )
149 
150         code = CythonUtilityCode.load(
151             "UFuncDefinition",
152             "UFuncs.pyx",
153             context=context,
154             outer_module_scope=self.global_scope,
155         )
156 
157         tree = code.get_tree(entries_only=True)
158         return tree
159 
160     def use_generic_utility_code(self):
161         # use the invariant C utility code
162         self.global_scope.use_utility_code(
163             UtilityCode.load_cached("UFuncsInit", "UFuncs_C.c")
164         )
165         self.global_scope.use_utility_code(
166             UtilityCode.load_cached("NumpyImportUFunc", "NumpyImportArray.c")
167         )
168 
169 
170 def convert_to_ufunc(node):
171     if isinstance(node, Nodes.CFuncDefNode):
172         if node.local_scope.parent_scope.is_c_class_scope:
173             error(node.pos, "Methods cannot currently be converted to a ufunc")
174             return node
175         converters = [UFuncConversion(node)]
176         original_node = node
177     elif isinstance(node, FusedNode.FusedCFuncDefNode) and isinstance(
178         node.node, Nodes.CFuncDefNode
179     ):
180         if node.node.local_scope.parent_scope.is_c_class_scope:
181             error(node.pos, "Methods cannot currently be converted to a ufunc")
182             return node
183         converters = [UFuncConversion(n) for n in node.nodes]
184         original_node = node.node
185     else:
186         error(node.pos, "Only C functions can be converted to a ufunc")
187         return node
188 
189     if not converters:
190         return  # this path probably shouldn't happen
191 
192     del converters[0].global_scope.entries[original_node.entry.name]
193     # the generic utility code is generic, so there's no reason to do it multiple times
194     converters[0].use_generic_utility_code()
195     return [node] + _generate_stats_from_converters(converters, original_node)
196 
197 
198 def generate_ufunc_initialization(converters, cfunc_nodes, original_node):
199     global_scope = converters[0].global_scope
200     ufunc_funcs_name = global_scope.next_id(Naming.pyrex_prefix + "funcs")
201     ufunc_types_name = global_scope.next_id(Naming.pyrex_prefix + "types")
202     ufunc_data_name = global_scope.next_id(Naming.pyrex_prefix + "data")
203     type_constants = []
204     narg_in = None
205     narg_out = None
206     for c in converters:
207         in_const = [d.type_constant for d in c.in_definitions]
208         if narg_in is not None:
209             assert narg_in == len(in_const)
210         else:
211             narg_in = len(in_const)
212         type_constants.extend(in_const)
213         out_const = [d.type_constant for d in c.out_definitions]
214         if narg_out is not None:
215             assert narg_out == len(out_const)
216         else:
217             narg_out = len(out_const)
218         type_constants.extend(out_const)
219 
220     func_cnames = [cfnode.entry.cname for cfnode in cfunc_nodes]
221 
222     context = dict(
223         ufunc_funcs_name=ufunc_funcs_name,
224         func_cnames=func_cnames,
225         ufunc_types_name=ufunc_types_name,
226         type_constants=type_constants,
227         ufunc_data_name=ufunc_data_name,
228     )
229     global_scope.use_utility_code(
230         TempitaUtilityCode.load("UFuncConsts", "UFuncs_C.c", context=context)
231     )
232 
233     pos = original_node.pos
234     func_name = original_node.entry.name
235     docstr = original_node.doc
236     args_to_func = '%s(), %s, %s(), %s, %s, %s, PyUFunc_None, "%s", %s, 0' % (
237         ufunc_funcs_name,
238         ufunc_data_name,
239         ufunc_types_name,
240         len(func_cnames),
241         narg_in,
242         narg_out,
243         func_name,
244         docstr.as_c_string_literal() if docstr else "NULL",
245     )
246 
247     call_node = ExprNodes.PythonCapiCallNode(
248         pos,
249         function_name="PyUFunc_FromFuncAndData",
250         # use a dummy type because it's honestly too fiddly
251         func_type=PyrexTypes.CFuncType(
252             PyrexTypes.py_object_type,
253             [PyrexTypes.CFuncTypeArg("dummy", PyrexTypes.c_void_ptr_type, None)],
254         ),
255         args=[
256             ExprNodes.ConstNode(
257                 pos, type=PyrexTypes.c_void_ptr_type, value=args_to_func
258             )
259         ],
260     )
261     lhs_entry = global_scope.declare_var(func_name, PyrexTypes.py_object_type, pos)
262     assgn_node = Nodes.SingleAssignmentNode(
263         pos,
264         lhs=ExprNodes.NameNode(
265             pos, name=func_name, type=PyrexTypes.py_object_type, entry=lhs_entry
266         ),
267         rhs=call_node,
268     )
269     return assgn_node
270 
271 
272 def _generate_stats_from_converters(converters, node):
273     stats = []
274     for converter in converters:
275         tree = converter.generate_cy_utility_code()
276         ufunc_node = get_cfunc_from_tree(tree)
277         # merge in any utility code
278         converter.global_scope.utility_code_list.extend(tree.scope.utility_code_list)
279         stats.append(ufunc_node)
280 
281     stats.append(generate_ufunc_initialization(converters, stats, node))
282     return stats
283
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Cython/Compiler/UFuncs.py b/Cython/Compiler/UFuncs.py
--- a/Cython/Compiler/UFuncs.py
+++ b/Cython/Compiler/UFuncs.py
@@ -138,6 +138,9 @@
 
         ufunc_cname = self.global_scope.next_id(self.node.entry.name + "_ufunc_def")
 
+        will_be_called_without_gil = not (any(t.is_pyobject for t in arg_types) or
+            any(t.is_pyobject for t in out_types))
+
         context = dict(
             func_cname=ufunc_cname,
             in_types=arg_types,
@@ -145,6 +148,7 @@
             inline_func_call=self.node.entry.cname,
             inline_func_declaration=inline_func_decl,
             nogil=self.node.entry.type.nogil,
+            will_be_called_without_gil=will_be_called_without_gil,
         )
 
         code = CythonUtilityCode.load(

{"golden_diff": "diff --git a/Cython/Compiler/UFuncs.py b/Cython/Compiler/UFuncs.py\n--- a/Cython/Compiler/UFuncs.py\n+++ b/Cython/Compiler/UFuncs.py\n@@ -138,6 +138,9 @@\n \n         ufunc_cname = self.global_scope.next_id(self.node.entry.name + \"_ufunc_def\")\n \n+        will_be_called_without_gil = not (any(t.is_pyobject for t in arg_types) or\n+            any(t.is_pyobject for t in out_types))\n+\n         context = dict(\n             func_cname=ufunc_cname,\n             in_types=arg_types,\n@@ -145,6 +148,7 @@\n             inline_func_call=self.node.entry.cname,\n             inline_func_declaration=inline_func_decl,\n             nogil=self.node.entry.type.nogil,\n+            will_be_called_without_gil=will_be_called_without_gil,\n         )\n \n         code = CythonUtilityCode.load(\n", "issue": "Fatal Python error: PyEval_SaveThread when creating ufunc with nogil [BUG] \n### Describe the bug\n\nI'm generating ufuncs for my fortran library by the new version 3.0.0b1 of cython, using the @cython.ufunc function decorator.\r\nI declare the function as nogil. and call it in python script. It works fine when the input array length is less than 500. When the input array length is greater than 500, it stops and throws a thread error.\r\n```\r\nFatal Python error: PyEval_SaveThread: the function must be called with the GIL held, but the GIL is released (the current Python thread state is NULL)\r\nPython runtime state: initialized\r\n```\r\nI'm using the mingw-w64 compiler provided by msys2 on windows, but that doesn't seem to be the cause of the error. I transferred the program to an ubuntu machine and found the same error.\n\n### Code to reproduce the behaviour:\n\n```cython\r\n# test_ufunc.pyx\r\ncimport cython\r\n\r\n@cython.ufunc\r\ncdef double add_one(double x) nogil:\r\n    return x+1\r\n```\r\n\r\n```python\r\nimport test_ufunc\r\nimport numpy as np\r\n\r\nx=np.linspace(-1,1,501)\r\nx_add=test_ufunc.add_one(x)\r\nprint(x_add)\r\n```\n\n### Expected behaviour\n\nNo errors, result returns successfully.\n\n### OS\n\nUbuntu and Windows\n\n### Python version\n\n3.10.6 (Ubuntu), 3.11.2 (Windows)\n\n### Cython version\n\n3.0.0b1\n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "from . import (\n    Nodes,\n    ExprNodes,\n    FusedNode,\n    TreeFragment,\n    Pipeline,\n    ParseTreeTransforms,\n    Naming,\n    UtilNodes,\n)\nfrom .Errors import error\nfrom . import PyrexTypes\nfrom .UtilityCode import CythonUtilityCode\nfrom .Code import TempitaUtilityCode, UtilityCode\nfrom .Visitor import PrintTree, TreeVisitor, VisitorTransform\n\nnumpy_int_types = [\n    \"NPY_BYTE\",\n    \"NPY_INT8\",\n    \"NPY_SHORT\",\n    \"NPY_INT16\",\n    \"NPY_INT\",\n    \"NPY_INT32\",\n    \"NPY_LONG\",\n    \"NPY_LONGLONG\",\n    \"NPY_INT64\",\n]\nnumpy_uint_types = [tp.replace(\"NPY_\", \"NPY_U\") for tp in numpy_int_types]\n# note: half float type is deliberately omitted\nnumpy_numeric_types = (\n    numpy_int_types\n    + numpy_uint_types\n    + [\n        \"NPY_FLOAT\",\n        \"NPY_FLOAT32\",\n        \"NPY_DOUBLE\",\n        \"NPY_FLOAT64\",\n        \"NPY_LONGDOUBLE\",\n    ]\n)\n\n\ndef _get_type_constant(pos, type_):\n    if type_.is_complex:\n        # 'is' checks don't seem to work for complex types\n        if type_ == PyrexTypes.c_float_complex_type:\n            return \"NPY_CFLOAT\"\n        elif type_ == PyrexTypes.c_double_complex_type:\n            return \"NPY_CDOUBLE\"\n        elif type_ == PyrexTypes.c_longdouble_complex_type:\n            return \"NPY_CLONGDOUBLE\"\n    elif type_.is_numeric:\n        postfix = type_.empty_declaration_code().upper().replace(\" \", \"\")\n        typename = \"NPY_%s\" % postfix\n        if typename in numpy_numeric_types:\n            return typename\n    elif type_.is_pyobject:\n        return \"NPY_OBJECT\"\n    # TODO possible NPY_BOOL to bint but it needs a cast?\n    # TODO NPY_DATETIME, NPY_TIMEDELTA, NPY_STRING, NPY_UNICODE and maybe NPY_VOID might be handleable\n    error(pos, \"Type '%s' cannot be used as a ufunc argument\" % type_)\n\n\nclass _FindCFuncDefNode(TreeVisitor):\n    \"\"\"\n    Finds the CFuncDefNode in the tree\n\n    The assumption is that there's only one CFuncDefNode\n    \"\"\"\n\n    found_node = None\n\n    def visit_Node(self, node):\n        if self.found_node:\n            return\n        else:\n            self.visitchildren(node)\n\n    def visit_CFuncDefNode(self, node):\n        self.found_node = node\n\n    def __call__(self, tree):\n        self.visit(tree)\n        return self.found_node\n\n\ndef get_cfunc_from_tree(tree):\n    return _FindCFuncDefNode()(tree)\n\n\nclass _ArgumentInfo(object):\n    \"\"\"\n    Everything related to defining an input/output argument for a ufunc\n\n    type  - PyrexType\n    type_constant  - str such as \"NPY_INT8\" representing numpy dtype constants\n    \"\"\"\n\n    def __init__(self, type, type_constant):\n        self.type = type\n        self.type_constant = type_constant\n\n\nclass UFuncConversion(object):\n    def __init__(self, node):\n        self.node = node\n        self.global_scope = node.local_scope.global_scope()\n\n        self.in_definitions = self.get_in_type_info()\n        self.out_definitions = self.get_out_type_info()\n\n    def get_in_type_info(self):\n        definitions = []\n        for n, arg in enumerate(self.node.args):\n            type_const = _get_type_constant(self.node.pos, arg.type)\n            definitions.append(_ArgumentInfo(arg.type, type_const))\n        return definitions\n\n    def get_out_type_info(self):\n        if self.node.return_type.is_ctuple:\n            components = self.node.return_type.components\n        else:\n            components = [self.node.return_type]\n        definitions = []\n        for n, type in enumerate(components):\n            definitions.append(\n                _ArgumentInfo(type, _get_type_constant(self.node.pos, type))\n            )\n        return definitions\n\n    def generate_cy_utility_code(self):\n        arg_types = [a.type for a in self.in_definitions]\n        out_types = [a.type for a in self.out_definitions]\n        inline_func_decl = self.node.entry.type.declaration_code(\n            self.node.entry.cname, pyrex=True\n        )\n        self.node.entry.used = True\n\n        ufunc_cname = self.global_scope.next_id(self.node.entry.name + \"_ufunc_def\")\n\n        context = dict(\n            func_cname=ufunc_cname,\n            in_types=arg_types,\n            out_types=out_types,\n            inline_func_call=self.node.entry.cname,\n            inline_func_declaration=inline_func_decl,\n            nogil=self.node.entry.type.nogil,\n        )\n\n        code = CythonUtilityCode.load(\n            \"UFuncDefinition\",\n            \"UFuncs.pyx\",\n            context=context,\n            outer_module_scope=self.global_scope,\n        )\n\n        tree = code.get_tree(entries_only=True)\n        return tree\n\n    def use_generic_utility_code(self):\n        # use the invariant C utility code\n        self.global_scope.use_utility_code(\n            UtilityCode.load_cached(\"UFuncsInit\", \"UFuncs_C.c\")\n        )\n        self.global_scope.use_utility_code(\n            UtilityCode.load_cached(\"NumpyImportUFunc\", \"NumpyImportArray.c\")\n        )\n\n\ndef convert_to_ufunc(node):\n    if isinstance(node, Nodes.CFuncDefNode):\n        if node.local_scope.parent_scope.is_c_class_scope:\n            error(node.pos, \"Methods cannot currently be converted to a ufunc\")\n            return node\n        converters = [UFuncConversion(node)]\n        original_node = node\n    elif isinstance(node, FusedNode.FusedCFuncDefNode) and isinstance(\n        node.node, Nodes.CFuncDefNode\n    ):\n        if node.node.local_scope.parent_scope.is_c_class_scope:\n            error(node.pos, \"Methods cannot currently be converted to a ufunc\")\n            return node\n        converters = [UFuncConversion(n) for n in node.nodes]\n        original_node = node.node\n    else:\n        error(node.pos, \"Only C functions can be converted to a ufunc\")\n        return node\n\n    if not converters:\n        return  # this path probably shouldn't happen\n\n    del converters[0].global_scope.entries[original_node.entry.name]\n    # the generic utility code is generic, so there's no reason to do it multiple times\n    converters[0].use_generic_utility_code()\n    return [node] + _generate_stats_from_converters(converters, original_node)\n\n\ndef generate_ufunc_initialization(converters, cfunc_nodes, original_node):\n    global_scope = converters[0].global_scope\n    ufunc_funcs_name = global_scope.next_id(Naming.pyrex_prefix + \"funcs\")\n    ufunc_types_name = global_scope.next_id(Naming.pyrex_prefix + \"types\")\n    ufunc_data_name = global_scope.next_id(Naming.pyrex_prefix + \"data\")\n    type_constants = []\n    narg_in = None\n    narg_out = None\n    for c in converters:\n        in_const = [d.type_constant for d in c.in_definitions]\n        if narg_in is not None:\n            assert narg_in == len(in_const)\n        else:\n            narg_in = len(in_const)\n        type_constants.extend(in_const)\n        out_const = [d.type_constant for d in c.out_definitions]\n        if narg_out is not None:\n            assert narg_out == len(out_const)\n        else:\n            narg_out = len(out_const)\n        type_constants.extend(out_const)\n\n    func_cnames = [cfnode.entry.cname for cfnode in cfunc_nodes]\n\n    context = dict(\n        ufunc_funcs_name=ufunc_funcs_name,\n        func_cnames=func_cnames,\n        ufunc_types_name=ufunc_types_name,\n        type_constants=type_constants,\n        ufunc_data_name=ufunc_data_name,\n    )\n    global_scope.use_utility_code(\n        TempitaUtilityCode.load(\"UFuncConsts\", \"UFuncs_C.c\", context=context)\n    )\n\n    pos = original_node.pos\n    func_name = original_node.entry.name\n    docstr = original_node.doc\n    args_to_func = '%s(), %s, %s(), %s, %s, %s, PyUFunc_None, \"%s\", %s, 0' % (\n        ufunc_funcs_name,\n        ufunc_data_name,\n        ufunc_types_name,\n        len(func_cnames),\n        narg_in,\n        narg_out,\n        func_name,\n        docstr.as_c_string_literal() if docstr else \"NULL\",\n    )\n\n    call_node = ExprNodes.PythonCapiCallNode(\n        pos,\n        function_name=\"PyUFunc_FromFuncAndData\",\n        # use a dummy type because it's honestly too fiddly\n        func_type=PyrexTypes.CFuncType(\n            PyrexTypes.py_object_type,\n            [PyrexTypes.CFuncTypeArg(\"dummy\", PyrexTypes.c_void_ptr_type, None)],\n        ),\n        args=[\n            ExprNodes.ConstNode(\n                pos, type=PyrexTypes.c_void_ptr_type, value=args_to_func\n            )\n        ],\n    )\n    lhs_entry = global_scope.declare_var(func_name, PyrexTypes.py_object_type, pos)\n    assgn_node = Nodes.SingleAssignmentNode(\n        pos,\n        lhs=ExprNodes.NameNode(\n            pos, name=func_name, type=PyrexTypes.py_object_type, entry=lhs_entry\n        ),\n        rhs=call_node,\n    )\n    return assgn_node\n\n\ndef _generate_stats_from_converters(converters, node):\n    stats = []\n    for converter in converters:\n        tree = converter.generate_cy_utility_code()\n        ufunc_node = get_cfunc_from_tree(tree)\n        # merge in any utility code\n        converter.global_scope.utility_code_list.extend(tree.scope.utility_code_list)\n        stats.append(ufunc_node)\n\n    stats.append(generate_ufunc_initialization(converters, stats, node))\n    return stats\n", "path": "Cython/Compiler/UFuncs.py"}], "after_files": [{"content": "from . import (\n    Nodes,\n    ExprNodes,\n    FusedNode,\n    TreeFragment,\n    Pipeline,\n    ParseTreeTransforms,\n    Naming,\n    UtilNodes,\n)\nfrom .Errors import error\nfrom . import PyrexTypes\nfrom .UtilityCode import CythonUtilityCode\nfrom .Code import TempitaUtilityCode, UtilityCode\nfrom .Visitor import PrintTree, TreeVisitor, VisitorTransform\n\nnumpy_int_types = [\n    \"NPY_BYTE\",\n    \"NPY_INT8\",\n    \"NPY_SHORT\",\n    \"NPY_INT16\",\n    \"NPY_INT\",\n    \"NPY_INT32\",\n    \"NPY_LONG\",\n    \"NPY_LONGLONG\",\n    \"NPY_INT64\",\n]\nnumpy_uint_types = [tp.replace(\"NPY_\", \"NPY_U\") for tp in numpy_int_types]\n# note: half float type is deliberately omitted\nnumpy_numeric_types = (\n    numpy_int_types\n    + numpy_uint_types\n    + [\n        \"NPY_FLOAT\",\n        \"NPY_FLOAT32\",\n        \"NPY_DOUBLE\",\n        \"NPY_FLOAT64\",\n        \"NPY_LONGDOUBLE\",\n    ]\n)\n\n\ndef _get_type_constant(pos, type_):\n    if type_.is_complex:\n        # 'is' checks don't seem to work for complex types\n        if type_ == PyrexTypes.c_float_complex_type:\n            return \"NPY_CFLOAT\"\n        elif type_ == PyrexTypes.c_double_complex_type:\n            return \"NPY_CDOUBLE\"\n        elif type_ == PyrexTypes.c_longdouble_complex_type:\n            return \"NPY_CLONGDOUBLE\"\n    elif type_.is_numeric:\n        postfix = type_.empty_declaration_code().upper().replace(\" \", \"\")\n        typename = \"NPY_%s\" % postfix\n        if typename in numpy_numeric_types:\n            return typename\n    elif type_.is_pyobject:\n        return \"NPY_OBJECT\"\n    # TODO possible NPY_BOOL to bint but it needs a cast?\n    # TODO NPY_DATETIME, NPY_TIMEDELTA, NPY_STRING, NPY_UNICODE and maybe NPY_VOID might be handleable\n    error(pos, \"Type '%s' cannot be used as a ufunc argument\" % type_)\n\n\nclass _FindCFuncDefNode(TreeVisitor):\n    \"\"\"\n    Finds the CFuncDefNode in the tree\n\n    The assumption is that there's only one CFuncDefNode\n    \"\"\"\n\n    found_node = None\n\n    def visit_Node(self, node):\n        if self.found_node:\n            return\n        else:\n            self.visitchildren(node)\n\n    def visit_CFuncDefNode(self, node):\n        self.found_node = node\n\n    def __call__(self, tree):\n        self.visit(tree)\n        return self.found_node\n\n\ndef get_cfunc_from_tree(tree):\n    return _FindCFuncDefNode()(tree)\n\n\nclass _ArgumentInfo(object):\n    \"\"\"\n    Everything related to defining an input/output argument for a ufunc\n\n    type  - PyrexType\n    type_constant  - str such as \"NPY_INT8\" representing numpy dtype constants\n    \"\"\"\n\n    def __init__(self, type, type_constant):\n        self.type = type\n        self.type_constant = type_constant\n\n\nclass UFuncConversion(object):\n    def __init__(self, node):\n        self.node = node\n        self.global_scope = node.local_scope.global_scope()\n\n        self.in_definitions = self.get_in_type_info()\n        self.out_definitions = self.get_out_type_info()\n\n    def get_in_type_info(self):\n        definitions = []\n        for n, arg in enumerate(self.node.args):\n            type_const = _get_type_constant(self.node.pos, arg.type)\n            definitions.append(_ArgumentInfo(arg.type, type_const))\n        return definitions\n\n    def get_out_type_info(self):\n        if self.node.return_type.is_ctuple:\n            components = self.node.return_type.components\n        else:\n            components = [self.node.return_type]\n        definitions = []\n        for n, type in enumerate(components):\n            definitions.append(\n                _ArgumentInfo(type, _get_type_constant(self.node.pos, type))\n            )\n        return definitions\n\n    def generate_cy_utility_code(self):\n        arg_types = [a.type for a in self.in_definitions]\n        out_types = [a.type for a in self.out_definitions]\n        inline_func_decl = self.node.entry.type.declaration_code(\n            self.node.entry.cname, pyrex=True\n        )\n        self.node.entry.used = True\n\n        ufunc_cname = self.global_scope.next_id(self.node.entry.name + \"_ufunc_def\")\n\n        will_be_called_without_gil = not (any(t.is_pyobject for t in arg_types) or\n            any(t.is_pyobject for t in out_types))\n\n        context = dict(\n            func_cname=ufunc_cname,\n            in_types=arg_types,\n            out_types=out_types,\n            inline_func_call=self.node.entry.cname,\n            inline_func_declaration=inline_func_decl,\n            nogil=self.node.entry.type.nogil,\n            will_be_called_without_gil=will_be_called_without_gil,\n        )\n\n        code = CythonUtilityCode.load(\n            \"UFuncDefinition\",\n            \"UFuncs.pyx\",\n            context=context,\n            outer_module_scope=self.global_scope,\n        )\n\n        tree = code.get_tree(entries_only=True)\n        return tree\n\n    def use_generic_utility_code(self):\n        # use the invariant C utility code\n        self.global_scope.use_utility_code(\n            UtilityCode.load_cached(\"UFuncsInit\", \"UFuncs_C.c\")\n        )\n        self.global_scope.use_utility_code(\n            UtilityCode.load_cached(\"NumpyImportUFunc\", \"NumpyImportArray.c\")\n        )\n\n\ndef convert_to_ufunc(node):\n    if isinstance(node, Nodes.CFuncDefNode):\n        if node.local_scope.parent_scope.is_c_class_scope:\n            error(node.pos, \"Methods cannot currently be converted to a ufunc\")\n            return node\n        converters = [UFuncConversion(node)]\n        original_node = node\n    elif isinstance(node, FusedNode.FusedCFuncDefNode) and isinstance(\n        node.node, Nodes.CFuncDefNode\n    ):\n        if node.node.local_scope.parent_scope.is_c_class_scope:\n            error(node.pos, \"Methods cannot currently be converted to a ufunc\")\n            return node\n        converters = [UFuncConversion(n) for n in node.nodes]\n        original_node = node.node\n    else:\n        error(node.pos, \"Only C functions can be converted to a ufunc\")\n        return node\n\n    if not converters:\n        return  # this path probably shouldn't happen\n\n    del converters[0].global_scope.entries[original_node.entry.name]\n    # the generic utility code is generic, so there's no reason to do it multiple times\n    converters[0].use_generic_utility_code()\n    return [node] + _generate_stats_from_converters(converters, original_node)\n\n\ndef generate_ufunc_initialization(converters, cfunc_nodes, original_node):\n    global_scope = converters[0].global_scope\n    ufunc_funcs_name = global_scope.next_id(Naming.pyrex_prefix + \"funcs\")\n    ufunc_types_name = global_scope.next_id(Naming.pyrex_prefix + \"types\")\n    ufunc_data_name = global_scope.next_id(Naming.pyrex_prefix + \"data\")\n    type_constants = []\n    narg_in = None\n    narg_out = None\n    for c in converters:\n        in_const = [d.type_constant for d in c.in_definitions]\n        if narg_in is not None:\n            assert narg_in == len(in_const)\n        else:\n            narg_in = len(in_const)\n        type_constants.extend(in_const)\n        out_const = [d.type_constant for d in c.out_definitions]\n        if narg_out is not None:\n            assert narg_out == len(out_const)\n        else:\n            narg_out = len(out_const)\n        type_constants.extend(out_const)\n\n    func_cnames = [cfnode.entry.cname for cfnode in cfunc_nodes]\n\n    context = dict(\n        ufunc_funcs_name=ufunc_funcs_name,\n        func_cnames=func_cnames,\n        ufunc_types_name=ufunc_types_name,\n        type_constants=type_constants,\n        ufunc_data_name=ufunc_data_name,\n    )\n    global_scope.use_utility_code(\n        TempitaUtilityCode.load(\"UFuncConsts\", \"UFuncs_C.c\", context=context)\n    )\n\n    pos = original_node.pos\n    func_name = original_node.entry.name\n    docstr = original_node.doc\n    args_to_func = '%s(), %s, %s(), %s, %s, %s, PyUFunc_None, \"%s\", %s, 0' % (\n        ufunc_funcs_name,\n        ufunc_data_name,\n        ufunc_types_name,\n        len(func_cnames),\n        narg_in,\n        narg_out,\n        func_name,\n        docstr.as_c_string_literal() if docstr else \"NULL\",\n    )\n\n    call_node = ExprNodes.PythonCapiCallNode(\n        pos,\n        function_name=\"PyUFunc_FromFuncAndData\",\n        # use a dummy type because it's honestly too fiddly\n        func_type=PyrexTypes.CFuncType(\n            PyrexTypes.py_object_type,\n            [PyrexTypes.CFuncTypeArg(\"dummy\", PyrexTypes.c_void_ptr_type, None)],\n        ),\n        args=[\n            ExprNodes.ConstNode(\n                pos, type=PyrexTypes.c_void_ptr_type, value=args_to_func\n            )\n        ],\n    )\n    lhs_entry = global_scope.declare_var(func_name, PyrexTypes.py_object_type, pos)\n    assgn_node = Nodes.SingleAssignmentNode(\n        pos,\n        lhs=ExprNodes.NameNode(\n            pos, name=func_name, type=PyrexTypes.py_object_type, entry=lhs_entry\n        ),\n        rhs=call_node,\n    )\n    return assgn_node\n\n\ndef _generate_stats_from_converters(converters, node):\n    stats = []\n    for converter in converters:\n        tree = converter.generate_cy_utility_code()\n        ufunc_node = get_cfunc_from_tree(tree)\n        # merge in any utility code\n        converter.global_scope.utility_code_list.extend(tree.scope.utility_code_list)\n        stats.append(ufunc_node)\n\n    stats.append(generate_ufunc_initialization(converters, stats, node))\n    return stats\n", "path": "Cython/Compiler/UFuncs.py"}]}