# 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 <intrin.h> // __readgsqword()
-#endif
+# if defined(_MSC_VER)
+# include <intrin.h> // __readgsqword()
+# endif
-#if defined(Py_GIL_DISABLED) && defined(__MINGW32__)
-# include <intrin.h> // __readgsqword()
-#endif
+# if defined(__MINGW32__)
+# include <intrin.h> // __readgsqword()
+# endif
+#endif // Py_GIL_DISABLED
// Include Python header files
#include "pyport.h"
PyAPI_DATA(PyTypeObject) PyModuleDef_Type;
#endif
+#ifndef _Py_OPAQUE_PYOBJECT
typedef struct PyModuleDef_Base {
PyObject_HEAD
/* The function used to re-initialize the module.
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 */
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;
inquiry m_clear;
freefunc m_free;
};
+#endif // _Py_OPAQUE_PYOBJECT
#ifdef __cplusplus
}
# 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;
* 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
* 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)
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))
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);
#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;
#endif
+#ifndef _Py_OPAQUE_PYOBJECT
static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) {
ob->ob_type = type;
}
#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)
/*
#endif
#endif
+#ifndef _Py_OPAQUE_PYOBJECT
static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
{
#if defined(Py_GIL_DISABLED)
#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 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')
def test_build(self):
self.check_build('_test_cext')
- def check_build(self, extension_name, std=None, limited=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)
+ std=std, limited=limited,
+ opaque_pyobject=opaque_pyobject)
- def _check_build(self, extension_name, python_exe, std, limited):
+ 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)))
- 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()
env['CPYTHON_TEST_STD'] = std
if limited:
env['CPYTHON_TEST_LIMITED'] = '1'
+ if opaque_pyobject:
+ env['CPYTHON_TEST_OPAQUE_PYOBJECT'] = '1'
env['CPYTHON_TEST_EXT_NAME'] = extension_name
env['TEST_INTERNAL_C_API'] = str(int(self.TEST_INTERNAL_C_API))
if support.verbose:
def test_build_c11(self):
self.check_build('_test_c11_cext', std='c11')
+ def test_build_opaque_pyobject(self):
+ # Test with _Py_OPAQUE_PYOBJECT
+ self.check_build('_test_limited_opaque_cext', limited=True,
+ opaque_pyobject=True)
+
@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
def test_build_c99(self):
# In public docs, we say C API is compatible with C11. However,
--- /dev/null
+// 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);
+}
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.
_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
};
-#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
std = os.environ.get("CPYTHON_TEST_STD", "")
module_name = os.environ["CPYTHON_TEST_EXT_NAME"]
limited = bool(os.environ.get("CPYTHON_TEST_LIMITED", ""))
+ opaque_pyobject = bool(os.environ.get("CPYTHON_TEST_OPAQUE_PYOBJECT", ""))
internal = bool(int(os.environ.get("TEST_INTERNAL_C_API", "0")))
+ sources = [SOURCE]
+
if not internal:
cflags = list(PUBLIC_CFLAGS)
else:
version = sys.hexversion
cflags.append(f'-DPy_LIMITED_API={version:#x}')
+ # Define _Py_OPAQUE_PYOBJECT macro
+ if opaque_pyobject:
+ cflags.append(f'-D_Py_OPAQUE_PYOBJECT')
+ sources.append('create_moduledef.c')
+
if internal:
cflags.append('-DTEST_INTERNAL_C_API=1')
ext = Extension(
module_name,
- sources=[SOURCE],
+ sources=sources,
extra_compile_args=cflags,
include_dirs=include_dirs,
library_dirs=library_dirs)