From 2639997fa2afeee66635de366dad31937830634d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 24 Jun 2025 15:31:17 +0200 Subject: [PATCH 1/4] Allow Py_LIMITED_API for (Py_GIL_DISABLED && _Py_OPAQUE_PYOBJECT) --- Include/Python.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Include/Python.h b/Include/Python.h index 64be80145890a3..19417df698c8e7 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -45,19 +45,19 @@ # endif #endif -// gh-111506: The free-threaded build is not compatible with the limited API -// or the stable ABI. -#if defined(Py_LIMITED_API) && defined(Py_GIL_DISABLED) -# error "The limited API is not currently supported in the free-threaded build" -#endif +#if defined(Py_GIL_DISABLED) +# if defined(Py_LIMITED_API) && !defined(_Py_OPAQUE_PYOBJECT) +# error "Py_LIMITED_API is not currently supported in the free-threaded build" +# endif -#if defined(Py_GIL_DISABLED) && defined(_MSC_VER) -# include // __readgsqword() -#endif +# if defined(_MSC_VER) +# include // __readgsqword() +# endif -#if defined(Py_GIL_DISABLED) && defined(__MINGW32__) -# include // __readgsqword() -#endif +# if defined(__MINGW32__) +# include // __readgsqword() +# endif +#endif // Py_GIL_DISABLED // Include Python header files #include "pyport.h" From 7ff56f41d91457ac1baa1ce72312d8d7ce61acb7 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 8 Jul 2025 16:05:50 +0200 Subject: [PATCH 2/4] Hide some API if _Py_OPAQUE_PYOBJECT is defined API that's removed when _Py_OPAQUE_PYOBJECT is defined: - PyObject_HEAD - _PyObject_EXTRA_INIT - PyObject_HEAD_INIT - PyObject_VAR_HEAD - struct _object (i.e. PyObject) (opaque) - struct PyVarObject (opaque) - Py_SIZE - Py_SET_TYPE - Py_SET_SIZE - PyModuleDef_Base (opaque) - PyModuleDef_HEAD_INIT - PyModuleDef (opaque) - _Py_IsImmortal - _Py_IsStaticImmortal Note that the `_Py_IsImmortal` removal means _Py_OPAQUE_PYOBJECT only works with limited API 3.14+ now. --- Include/moduleobject.h | 5 +++++ Include/object.h | 24 ++++++++++++++++++++---- Include/refcount.h | 2 ++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Include/moduleobject.h b/Include/moduleobject.h index 2a17c891dda811..17634a93f8fa6f 100644 --- a/Include/moduleobject.h +++ b/Include/moduleobject.h @@ -36,6 +36,7 @@ PyAPI_FUNC(PyObject *) PyModuleDef_Init(PyModuleDef*); PyAPI_DATA(PyTypeObject) PyModuleDef_Type; #endif +#ifndef _Py_OPAQUE_PYOBJECT typedef struct PyModuleDef_Base { PyObject_HEAD /* The function used to re-initialize the module. @@ -63,6 +64,7 @@ typedef struct PyModuleDef_Base { 0, /* m_index */ \ _Py_NULL, /* m_copy */ \ } +#endif // _Py_OPAQUE_PYOBJECT #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000 /* New in 3.5 */ @@ -104,6 +106,8 @@ struct PyModuleDef_Slot { PyAPI_FUNC(int) PyUnstable_Module_SetGIL(PyObject *module, void *gil); #endif + +#ifndef _Py_OPAQUE_PYOBJECT struct PyModuleDef { PyModuleDef_Base m_base; const char* m_name; @@ -115,6 +119,7 @@ struct PyModuleDef { inquiry m_clear; freefunc m_free; }; +#endif // _Py_OPAQUE_PYOBJECT #ifdef __cplusplus } diff --git a/Include/object.h b/Include/object.h index c75e9db0cbd935..b1bcc9481871b4 100644 --- a/Include/object.h +++ b/Include/object.h @@ -56,6 +56,11 @@ whose size is determined when the object is allocated. # define Py_REF_DEBUG #endif +#if defined(_Py_OPAQUE_PYOBJECT) && !defined(Py_LIMITED_API) +# error "_Py_OPAQUE_PYOBJECT only makes sense with Py_LIMITED_API" +#endif + +#ifndef _Py_OPAQUE_PYOBJECT /* PyObject_HEAD defines the initial segment of every PyObject. */ #define PyObject_HEAD PyObject ob_base; @@ -99,6 +104,8 @@ whose size is determined when the object is allocated. * not necessarily a byte count. */ #define PyObject_VAR_HEAD PyVarObject ob_base; +#endif // !defined(_Py_OPAQUE_PYOBJECT) + #define Py_INVALID_SIZE (Py_ssize_t)-1 /* PyObjects are given a minimum alignment so that the least significant bits @@ -112,7 +119,9 @@ whose size is determined when the object is allocated. * by hand. Similarly every pointer to a variable-size Python object can, * in addition, be cast to PyVarObject*. */ -#ifndef Py_GIL_DISABLED +#ifdef _Py_OPAQUE_PYOBJECT + /* PyObject is opaque */ +#elif !defined(Py_GIL_DISABLED) struct _object { #if (defined(__GNUC__) || defined(__clang__)) \ && !(defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L) @@ -168,15 +177,18 @@ struct _object { Py_ssize_t ob_ref_shared; // shared (atomic) reference count PyTypeObject *ob_type; }; -#endif +#endif // !defined(_Py_OPAQUE_PYOBJECT) /* Cast argument to PyObject* type. */ #define _PyObject_CAST(op) _Py_CAST(PyObject*, (op)) -typedef struct { +#ifndef _Py_OPAQUE_PYOBJECT +struct PyVarObject { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ -} PyVarObject; +}; +#endif +typedef struct PyVarObject PyVarObject; /* Cast argument to PyVarObject* type. */ #define _PyVarObject_CAST(op) _Py_CAST(PyVarObject*, (op)) @@ -286,6 +298,7 @@ PyAPI_FUNC(PyTypeObject*) Py_TYPE(PyObject *ob); PyAPI_DATA(PyTypeObject) PyLong_Type; PyAPI_DATA(PyTypeObject) PyBool_Type; +#ifndef _Py_OPAQUE_PYOBJECT // bpo-39573: The Py_SET_SIZE() function must be used to set an object size. static inline Py_ssize_t Py_SIZE(PyObject *ob) { assert(Py_TYPE(ob) != &PyLong_Type); @@ -295,6 +308,7 @@ static inline Py_ssize_t Py_SIZE(PyObject *ob) { #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_SIZE(ob) Py_SIZE(_PyObject_CAST(ob)) #endif +#endif // !defined(_Py_OPAQUE_PYOBJECT) static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { return Py_TYPE(ob) == type; @@ -304,6 +318,7 @@ static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { #endif +#ifndef _Py_OPAQUE_PYOBJECT static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) { ob->ob_type = type; } @@ -323,6 +338,7 @@ static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) { #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000 # define Py_SET_SIZE(ob, size) Py_SET_SIZE(_PyVarObject_CAST(ob), (size)) #endif +#endif // !defined(_Py_OPAQUE_PYOBJECT) /* diff --git a/Include/refcount.h b/Include/refcount.h index 457972b6dcfd9f..ba34461fefcbb0 100644 --- a/Include/refcount.h +++ b/Include/refcount.h @@ -117,6 +117,7 @@ PyAPI_FUNC(Py_ssize_t) Py_REFCNT(PyObject *ob); #endif #endif +#ifndef _Py_OPAQUE_PYOBJECT static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op) { #if defined(Py_GIL_DISABLED) @@ -140,6 +141,7 @@ static inline Py_ALWAYS_INLINE int _Py_IsStaticImmortal(PyObject *op) #endif } #define _Py_IsStaticImmortal(op) _Py_IsStaticImmortal(_PyObject_CAST(op)) +#endif // !defined(_Py_OPAQUE_PYOBJECT) // Py_SET_REFCNT() implementation for stable ABI PyAPI_FUNC(void) _Py_SetRefcnt(PyObject *ob, Py_ssize_t refcnt); From 33d9ae7cbeea2702baa8566617d48d855e0242f7 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 26 May 2025 13:36:26 +0200 Subject: [PATCH 3/4] Add test for _Py_OPAQUE_PYOBJECT --- Lib/test/test_cext/__init__.py | 21 +++++++++++++----- Lib/test/test_cext/create_moduledef.c | 29 +++++++++++++++++++++++++ Lib/test/test_cext/extension.c | 31 +++++++++++++++++++++++---- Lib/test/test_cext/setup.py | 11 +++++++++- 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 Lib/test/test_cext/create_moduledef.c diff --git a/Lib/test/test_cext/__init__.py b/Lib/test/test_cext/__init__.py index 46fde541494aa3..eee16b8a4daf54 100644 --- a/Lib/test/test_cext/__init__.py +++ b/Lib/test/test_cext/__init__.py @@ -12,7 +12,10 @@ from test import support -SOURCE = os.path.join(os.path.dirname(__file__), 'extension.c') +SOURCES = [ + os.path.join(os.path.dirname(__file__), 'extension.c'), + os.path.join(os.path.dirname(__file__), 'create_moduledef.c'), +] SETUP = os.path.join(os.path.dirname(__file__), 'setup.py') @@ -51,17 +54,23 @@ def test_build_limited(self): def test_build_limited_c11(self): self.check_build('_test_limited_c11_cext', limited=True, std='c11') - def check_build(self, extension_name, std=None, limited=False): + def test_build_opaque(self): + # Test with _Py_OPAQUE_PYOBJECT + self.check_build('_test_limited_opaque_cext', limited=True, opaque=True) + + def check_build(self, extension_name, std=None, limited=False, opaque=False): venv_dir = 'env' with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe: self._check_build(extension_name, python_exe, - std=std, limited=limited) + std=std, limited=limited, opaque=opaque) - def _check_build(self, extension_name, python_exe, std, limited): + def _check_build(self, extension_name, python_exe, std, limited, opaque): pkg_dir = 'pkg' os.mkdir(pkg_dir) shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP))) - shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE))) + for source in SOURCES: + dest = os.path.join(pkg_dir, os.path.basename(source)) + shutil.copy(source, dest) def run_cmd(operation, cmd): env = os.environ.copy() @@ -69,6 +78,8 @@ def run_cmd(operation, cmd): env['CPYTHON_TEST_STD'] = std if limited: env['CPYTHON_TEST_LIMITED'] = '1' + if opaque: + env['CPYTHON_TEST_OPAQUE_PYOBJECT'] = '1' env['CPYTHON_TEST_EXT_NAME'] = extension_name if support.verbose: print('Run:', ' '.join(map(shlex.quote, cmd))) diff --git a/Lib/test/test_cext/create_moduledef.c b/Lib/test/test_cext/create_moduledef.c new file mode 100644 index 00000000000000..249c3163552c03 --- /dev/null +++ b/Lib/test/test_cext/create_moduledef.c @@ -0,0 +1,29 @@ +// Workaround for testing _Py_OPAQUE_PYOBJECT. +// See end of 'extension.c' + + +#undef _Py_OPAQUE_PYOBJECT +#undef Py_LIMITED_API +#include "Python.h" + + +// (repeated definition to avoid creating a header) +extern PyObject *testcext_create_moduledef( + const char *name, const char *doc, + PyMethodDef *methods, PyModuleDef_Slot *slots); + +PyObject *testcext_create_moduledef( + const char *name, const char *doc, + PyMethodDef *methods, PyModuleDef_Slot *slots) { + + static struct PyModuleDef _testcext_module = { + PyModuleDef_HEAD_INIT, + }; + if (!_testcext_module.m_name) { + _testcext_module.m_name = name; + _testcext_module.m_doc = doc; + _testcext_module.m_methods = methods; + _testcext_module.m_slots = slots; + } + return PyModuleDef_Init(&_testcext_module); +} diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index 64629c5a6da8cd..2803e0aa53ef24 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -58,6 +58,9 @@ _testcext_exec( return 0; } +#define _FUNC_NAME(NAME) PyInit_ ## NAME +#define FUNC_NAME(NAME) _FUNC_NAME(NAME) + // Converting from function pointer to void* has undefined behavior, but // works on all known platforms, and CPython's module and type slots currently // need it. @@ -76,9 +79,10 @@ static PyModuleDef_Slot _testcext_slots[] = { _Py_COMP_DIAG_POP - PyDoc_STRVAR(_testcext_doc, "C test extension."); +#ifndef _Py_OPAQUE_PYOBJECT + static struct PyModuleDef _testcext_module = { PyModuleDef_HEAD_INIT, // m_base STR(MODULE_NAME), // m_name @@ -92,11 +96,30 @@ static struct PyModuleDef _testcext_module = { }; -#define _FUNC_NAME(NAME) PyInit_ ## NAME -#define FUNC_NAME(NAME) _FUNC_NAME(NAME) - PyMODINIT_FUNC FUNC_NAME(MODULE_NAME)(void) { return PyModuleDef_Init(&_testcext_module); } + +#else // _Py_OPAQUE_PYOBJECT + +// Opaque PyObject means that PyModuleDef is also opaque and cannot be +// declared statically. See PEP 793. +// So, this part of module creation is split into a separate source file +// which uses non-limited API. + +// (repeated definition to avoid creating a header) +extern PyObject *testcext_create_moduledef( + const char *name, const char *doc, + PyMethodDef *methods, PyModuleDef_Slot *slots); + + +PyMODINIT_FUNC +FUNC_NAME(MODULE_NAME)(void) +{ + return testcext_create_moduledef( + STR(MODULE_NAME), _testcext_doc, _testcext_methods, _testcext_slots); +} + +#endif // _Py_OPAQUE_PYOBJECT diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py index 1275282983f7ff..08ddf6ac2f0dd8 100644 --- a/Lib/test/test_cext/setup.py +++ b/Lib/test/test_cext/setup.py @@ -45,6 +45,9 @@ def main(): std = os.environ.get("CPYTHON_TEST_STD", "") module_name = os.environ["CPYTHON_TEST_EXT_NAME"] limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", "")) + opaque = bool(os.environ.get("CPYTHON_TEST_OPAQUE_PYOBJECT", "")) + + sources = [SOURCE] cflags = list(CFLAGS) cflags.append(f'-DMODULE_NAME={module_name}') @@ -75,6 +78,12 @@ def main(): version = sys.hexversion cflags.append(f'-DPy_LIMITED_API={version:#x}') + # Define _Py_OPAQUE_PYOBJECT macro + if opaque: + version = sys.hexversion + cflags.append(f'-D_Py_OPAQUE_PYOBJECT') + sources.append('create_moduledef.c') + # On Windows, add PCbuild\amd64\ to include and library directories include_dirs = [] library_dirs = [] @@ -99,7 +108,7 @@ def main(): ext = Extension( module_name, - sources=[SOURCE], + sources=sources, extra_compile_args=cflags, include_dirs=include_dirs, library_dirs=library_dirs) From 781e2abecd12aa092f48ca34ea5290fb7764d23f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 11 Jul 2025 21:53:33 +0200 Subject: [PATCH 4/4] Apply suggestions from code review --- Lib/test/test_cext/__init__.py | 16 ++++++++++------ Lib/test/test_cext/setup.py | 5 ++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_cext/__init__.py b/Lib/test/test_cext/__init__.py index eee16b8a4daf54..dc9f9cb397c51b 100644 --- a/Lib/test/test_cext/__init__.py +++ b/Lib/test/test_cext/__init__.py @@ -54,17 +54,21 @@ def test_build_limited(self): def test_build_limited_c11(self): self.check_build('_test_limited_c11_cext', limited=True, std='c11') - def test_build_opaque(self): + def test_build_opaque_pyobject(self): # Test with _Py_OPAQUE_PYOBJECT - self.check_build('_test_limited_opaque_cext', limited=True, opaque=True) + self.check_build('_test_limited_opaque_cext', limited=True, + opaque_pyobject=True) - def check_build(self, extension_name, std=None, limited=False, opaque=False): + def check_build(self, extension_name, std=None, limited=False, + opaque_pyobject=False): venv_dir = 'env' with support.setup_venv_with_pip_setuptools(venv_dir) as python_exe: self._check_build(extension_name, python_exe, - std=std, limited=limited, opaque=opaque) + std=std, limited=limited, + opaque_pyobject=opaque_pyobject) - def _check_build(self, extension_name, python_exe, std, limited, opaque): + def _check_build(self, extension_name, python_exe, std, limited, + opaque_pyobject): pkg_dir = 'pkg' os.mkdir(pkg_dir) shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP))) @@ -78,7 +82,7 @@ def run_cmd(operation, cmd): env['CPYTHON_TEST_STD'] = std if limited: env['CPYTHON_TEST_LIMITED'] = '1' - if opaque: + if opaque_pyobject: env['CPYTHON_TEST_OPAQUE_PYOBJECT'] = '1' env['CPYTHON_TEST_EXT_NAME'] = extension_name if support.verbose: diff --git a/Lib/test/test_cext/setup.py b/Lib/test/test_cext/setup.py index 08ddf6ac2f0dd8..b03bc37231c79c 100644 --- a/Lib/test/test_cext/setup.py +++ b/Lib/test/test_cext/setup.py @@ -45,7 +45,7 @@ def main(): std = os.environ.get("CPYTHON_TEST_STD", "") module_name = os.environ["CPYTHON_TEST_EXT_NAME"] limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", "")) - opaque = bool(os.environ.get("CPYTHON_TEST_OPAQUE_PYOBJECT", "")) + opaque_pyobject = bool(os.environ.get("CPYTHON_TEST_OPAQUE_PYOBJECT", "")) sources = [SOURCE] @@ -79,8 +79,7 @@ def main(): cflags.append(f'-DPy_LIMITED_API={version:#x}') # Define _Py_OPAQUE_PYOBJECT macro - if opaque: - version = sys.hexversion + if opaque_pyobject: cflags.append(f'-D_Py_OPAQUE_PYOBJECT') sources.append('create_moduledef.c') pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy