diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index c949bab91fa..ee02b274c03 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -3566,6 +3566,8 @@ def generate_pymoduledef_struct(self, env, code): else: cleanup_func = 'NULL' + freethreading_compatible = env.directives['freethreading_compatible'] + code.putln("") code.putln("#if CYTHON_PEP489_MULTI_PHASE_INIT") exec_func_cname = self.module_init_func_cname() @@ -3576,6 +3578,12 @@ def generate_pymoduledef_struct(self, env, code): code.putln("static PyModuleDef_Slot %s[] = {" % Naming.pymoduledef_slots_cname) code.putln("{Py_mod_create, (void*)%s}," % Naming.pymodule_create_func_cname) code.putln("{Py_mod_exec, (void*)%s}," % exec_func_cname) + code.putln("#if CYTHON_COMPILING_IN_CPYTHON_FREETHREADING") + if freethreading_compatible: + code.putln("{Py_mod_gil, Py_MOD_GIL_NOT_USED},") + else: + code.putln("{Py_mod_gil, Py_MOD_GIL_USED},") + code.putln("#endif") code.putln("{0, NULL}") code.putln("};") if not env.module_name.isascii(): @@ -3630,6 +3638,8 @@ def generate_module_creation_code(self, env, code): else: doc = "0" + freethreading_compatible = env.directives['freethreading_compatible'] + code.putln("#if CYTHON_PEP489_MULTI_PHASE_INIT") code.putln("%s = %s;" % ( env.module_cname, @@ -3664,6 +3674,12 @@ def generate_module_creation_code(self, env, code): env.module_cname, Naming.pymoduledef_cname)) code.putln(code.error_goto_if_null(env.module_cname, self.pos)) + code.putln("#endif") # CYTHON_USE_MODULE_STATE + code.putln("#if CYTHON_COMPILING_IN_CPYTHON_FREETHREADING") + if freethreading_compatible: + code.putln("PyUnstable_Module_SetGIL(%s, Py_MOD_GIL_NOT_USED);" % env.module_cname) + else: + code.putln("PyUnstable_Module_SetGIL(%s, Py_MOD_GIL_USED);" % env.module_cname) code.putln("#endif") code.putln("#endif") # CYTHON_PEP489_MULTI_PHASE_INIT code.putln("CYTHON_UNUSED_VAR(%s);" % module_temp) # only used in limited API diff --git a/Cython/Compiler/Options.py b/Cython/Compiler/Options.py index 465191e0ac5..b15c32d2137 100644 --- a/Cython/Compiler/Options.py +++ b/Cython/Compiler/Options.py @@ -181,6 +181,7 @@ def copy_inherited_directives(outer_directives, **new_directives): 'boundscheck' : True, 'nonecheck' : False, 'initializedcheck' : True, + 'freethreading_compatible': False, 'embedsignature': False, 'embedsignature.format': 'c', 'auto_cpdef': False, @@ -350,6 +351,7 @@ class DEFER_ANALYSIS_OF_ARGUMENTS: 'dataclasses.dataclass': DEFER_ANALYSIS_OF_ARGUMENTS, 'dataclasses.field': DEFER_ANALYSIS_OF_ARGUMENTS, 'embedsignature.format': one_of('c', 'clinic', 'python'), + 'freethreading_compatible': bool, } for key, val in _directive_defaults.items(): diff --git a/tests/run/freethreading_compatible.srctree b/tests/run/freethreading_compatible.srctree new file mode 100644 index 00000000000..eaab17024ab --- /dev/null +++ b/tests/run/freethreading_compatible.srctree @@ -0,0 +1,40 @@ +PYTHON setup.py build_ext --inplace +PYTHON -c "import default; default.test()" +PYTHON -c "import compatible; compatible.test()" +PYTHON -c "import incompatible; incompatible.test()" + +######## setup.py ######## + +from Cython.Build.Dependencies import cythonize +from distutils.core import setup + +ext_modules = [] + +# Test language_level specified in the cythonize() call +ext_modules += cythonize("default.py") +ext_modules += cythonize("compatible.py") +ext_modules += cythonize("incompatible.py") + +setup(ext_modules=ext_modules) + +######## default.py ######## + +def test(): + import sys + assert sys._is_gil_enabled() + +######## compatible.py ######## + +# cython: freethreading_compatible=True + +def test(): + import sys + assert not sys._is_gil_enabled() + +######## incompatible.py ######## + +# cython: freethreading_compatible=False + +def test(): + import sys + assert sys._is_gil_enabled()