diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index 4254110889fc6c..20115df1a18283 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -273,79 +273,3 @@ PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(
PyInterpreterState *interp,
_PyFrameEvalFunction eval_frame);
-
-
-/* cross-interpreter data */
-
-// _PyCrossInterpreterData is similar to Py_buffer as an effectively
-// opaque struct that holds data outside the object machinery. This
-// is necessary to pass safely between interpreters in the same process.
-typedef struct _xid _PyCrossInterpreterData;
-
-typedef PyObject *(*xid_newobjectfunc)(_PyCrossInterpreterData *);
-typedef void (*xid_freefunc)(void *);
-
-struct _xid {
- // data is the cross-interpreter-safe derivation of a Python object
- // (see _PyObject_GetCrossInterpreterData). It will be NULL if the
- // new_object func (below) encodes the data.
- void *data;
- // obj is the Python object from which the data was derived. This
- // is non-NULL only if the data remains bound to the object in some
- // way, such that the object must be "released" (via a decref) when
- // the data is released. In that case the code that sets the field,
- // likely a registered "crossinterpdatafunc", is responsible for
- // ensuring it owns the reference (i.e. incref).
- PyObject *obj;
- // interp is the ID of the owning interpreter of the original
- // object. It corresponds to the active interpreter when
- // _PyObject_GetCrossInterpreterData() was called. This should only
- // be set by the cross-interpreter machinery.
- //
- // We use the ID rather than the PyInterpreterState to avoid issues
- // with deleted interpreters. Note that IDs are never re-used, so
- // each one will always correspond to a specific interpreter
- // (whether still alive or not).
- int64_t interp;
- // new_object is a function that returns a new object in the current
- // interpreter given the data. The resulting object (a new
- // reference) will be equivalent to the original object. This field
- // is required.
- xid_newobjectfunc new_object;
- // free is called when the data is released. If it is NULL then
- // nothing will be done to free the data. For some types this is
- // okay (e.g. bytes) and for those types this field should be set
- // to NULL. However, for most the data was allocated just for
- // cross-interpreter use, so it must be freed when
- // _PyCrossInterpreterData_Release is called or the memory will
- // leak. In that case, at the very least this field should be set
- // to PyMem_RawFree (the default if not explicitly set to NULL).
- // The call will happen with the original interpreter activated.
- xid_freefunc free;
-};
-
-PyAPI_FUNC(void) _PyCrossInterpreterData_Init(
- _PyCrossInterpreterData *data,
- PyInterpreterState *interp, void *shared, PyObject *obj,
- xid_newobjectfunc new_object);
-PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize(
- _PyCrossInterpreterData *,
- PyInterpreterState *interp, const size_t, PyObject *,
- xid_newobjectfunc);
-PyAPI_FUNC(void) _PyCrossInterpreterData_Clear(
- PyInterpreterState *, _PyCrossInterpreterData *);
-
-PyAPI_FUNC(int) _PyObject_GetCrossInterpreterData(PyObject *, _PyCrossInterpreterData *);
-PyAPI_FUNC(PyObject *) _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *);
-PyAPI_FUNC(int) _PyCrossInterpreterData_Release(_PyCrossInterpreterData *);
-
-PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *);
-
-/* cross-interpreter data registry */
-
-typedef int (*crossinterpdatafunc)(PyThreadState *tstate, PyObject *,
- _PyCrossInterpreterData *);
-
-PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
-PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
-PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
diff --git a/Include/internal/pycore_cross_interp.h b/Include/internal/pycore_cross_interp.h
new file mode 100644
index 00000000000000..47fc55f0ade4a2
--- /dev/null
+++ b/Include/internal/pycore_cross_interp.h
@@ -0,0 +1,85 @@
+/* Cross-interpreter data */
+
+#ifndef Py_INTERNAL_CROSS_INTERPRETER_DATA_H
+#define Py_INTERNAL_CROSS_INTERPRETER_DATA_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef Py_BUILD_CORE
+# error "this header requires Py_BUILD_CORE define"
+#endif
+
+// _PyCrossInterpreterData is similar to Py_buffer as an effectively
+// opaque struct that holds data outside the object machinery. This
+// is necessary to pass safely between interpreters in the same process.
+typedef struct _xid _PyCrossInterpreterData;
+
+typedef PyObject *(*xid_newobjectfunc)(_PyCrossInterpreterData *);
+typedef void (*xid_freefunc)(void *);
+
+struct _xid {
+ // data is the cross-interpreter-safe derivation of a Python object
+ // (see _PyObject_GetCrossInterpreterData). It will be NULL if the
+ // new_object func (below) encodes the data.
+ void *data;
+ // obj is the Python object from which the data was derived. This
+ // is non-NULL only if the data remains bound to the object in some
+ // way, such that the object must be "released" (via a decref) when
+ // the data is released. In that case the code that sets the field,
+ // likely a registered "crossinterpdatafunc", is responsible for
+ // ensuring it owns the reference (i.e. incref).
+ PyObject *obj;
+ // interp is the ID of the owning interpreter of the original
+ // object. It corresponds to the active interpreter when
+ // _PyObject_GetCrossInterpreterData() was called. This should only
+ // be set by the cross-interpreter machinery.
+ //
+ // We use the ID rather than the PyInterpreterState to avoid issues
+ // with deleted interpreters. Note that IDs are never re-used, so
+ // each one will always correspond to a specific interpreter
+ // (whether still alive or not).
+ int64_t interp;
+ // new_object is a function that returns a new object in the current
+ // interpreter given the data. The resulting object (a new
+ // reference) will be equivalent to the original object. This field
+ // is required.
+ xid_newobjectfunc new_object;
+ // free is called when the data is released. If it is NULL then
+ // nothing will be done to free the data. For some types this is
+ // okay (e.g. bytes) and for those types this field should be set
+ // to NULL. However, for most the data was allocated just for
+ // cross-interpreter use, so it must be freed when
+ // _PyCrossInterpreterData_Release is called or the memory will
+ // leak. In that case, at the very least this field should be set
+ // to PyMem_RawFree (the default if not explicitly set to NULL).
+ // The call will happen with the original interpreter activated.
+ xid_freefunc free;
+};
+
+PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize(
+ _PyCrossInterpreterData *,
+ PyInterpreterState *interp, const size_t, PyObject *,
+ xid_newobjectfunc);
+PyAPI_FUNC(void) _PyCrossInterpreterData_Clear(
+ PyInterpreterState *, _PyCrossInterpreterData *);
+
+PyAPI_FUNC(int) _PyObject_GetCrossInterpreterData(PyObject *, _PyCrossInterpreterData *);
+PyAPI_FUNC(PyObject*) _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *);
+PyAPI_FUNC(int) _PyCrossInterpreterData_Release(_PyCrossInterpreterData *);
+
+PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *);
+
+/* cross-interpreter data registry */
+
+typedef int (*crossinterpdatafunc)(PyThreadState *tstate, PyObject *,
+ _PyCrossInterpreterData *);
+
+// Export for shared _xxinterpchannels extension
+PyAPI_FUNC(int) _PyCrossInterpreterData_RegisterClass(PyTypeObject *, crossinterpdatafunc);
+PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // !Py_INTERNAL_CROSS_INTERPRETER_DATA_H
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index bb37cafe6286a9..0ed242dfd4e8d3 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -16,6 +16,7 @@ extern "C" {
#include "pycore_ceval_state.h" // struct _ceval_state
#include "pycore_code.h" // struct callable_cache
#include "pycore_context.h" // struct _Py_context_state
+#include "pycore_cross_interp.h" // crossinterpdatafunc
#include "pycore_dict_state.h" // struct _Py_dict_state
#include "pycore_dtoa.h" // struct _dtoa_state
#include "pycore_exceptions.h" // struct _Py_exc_state
diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py
index ac2280eb7090e9..f547e6b4dfe955 100644
--- a/Lib/test/test__xxsubinterpreters.py
+++ b/Lib/test/test__xxsubinterpreters.py
@@ -7,13 +7,13 @@
import threading
import unittest
-import _testcapi
from test import support
from test.support import import_helper
from test.support import script_helper
interpreters = import_helper.import_module('_xxsubinterpreters')
+_testinternalcapi = import_helper.import_module('_testinternalcapi')
##################################
@@ -146,8 +146,8 @@ class ShareableTypeTests(unittest.TestCase):
def _assert_values(self, values):
for obj in values:
with self.subTest(obj):
- xid = _testcapi.get_crossinterp_data(obj)
- got = _testcapi.restore_crossinterp_data(xid)
+ xid = _testinternalcapi.get_crossinterp_data(obj)
+ got = _testinternalcapi.restore_crossinterp_data(xid)
self.assertEqual(got, obj)
self.assertIs(type(got), type(obj))
@@ -155,8 +155,8 @@ def _assert_values(self, values):
def test_singletons(self):
for obj in [None]:
with self.subTest(obj):
- xid = _testcapi.get_crossinterp_data(obj)
- got = _testcapi.restore_crossinterp_data(xid)
+ xid = _testinternalcapi.get_crossinterp_data(obj)
+ got = _testinternalcapi.restore_crossinterp_data(xid)
# XXX What about between interpreters?
self.assertIs(got, obj)
@@ -187,7 +187,7 @@ def test_non_shareable_int(self):
for i in ints:
with self.subTest(i):
with self.assertRaises(OverflowError):
- _testcapi.get_crossinterp_data(i)
+ _testinternalcapi.get_crossinterp_data(i)
class ModuleTests(TestBase):
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 5f1988b0d17213..6689f3b3fc5e2c 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1750,9 +1750,10 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_complexobject.h \
$(srcdir)/Include/internal/pycore_condvar.h \
$(srcdir)/Include/internal/pycore_context.h \
+ $(srcdir)/Include/internal/pycore_cross_interp.h \
+ $(srcdir)/Include/internal/pycore_descrobject.h \
$(srcdir)/Include/internal/pycore_dict.h \
$(srcdir)/Include/internal/pycore_dict_state.h \
- $(srcdir)/Include/internal/pycore_descrobject.h \
$(srcdir)/Include/internal/pycore_dtoa.h \
$(srcdir)/Include/internal/pycore_exceptions.h \
$(srcdir)/Include/internal/pycore_faulthandler.h \
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 065a7fb733d432..5c721abfe11bbd 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -1541,58 +1541,6 @@ run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
return PyLong_FromLong(r);
}
-static void
-_xid_capsule_destructor(PyObject *capsule)
-{
- _PyCrossInterpreterData *data = \
- (_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
- if (data != NULL) {
- assert(_PyCrossInterpreterData_Release(data) == 0);
- PyMem_Free(data);
- }
-}
-
-static PyObject *
-get_crossinterp_data(PyObject *self, PyObject *args)
-{
- PyObject *obj = NULL;
- if (!PyArg_ParseTuple(args, "O:get_crossinterp_data", &obj)) {
- return NULL;
- }
-
- _PyCrossInterpreterData *data = PyMem_NEW(_PyCrossInterpreterData, 1);
- if (data == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
- PyMem_Free(data);
- return NULL;
- }
- PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
- if (capsule == NULL) {
- assert(_PyCrossInterpreterData_Release(data) == 0);
- PyMem_Free(data);
- }
- return capsule;
-}
-
-static PyObject *
-restore_crossinterp_data(PyObject *self, PyObject *args)
-{
- PyObject *capsule = NULL;
- if (!PyArg_ParseTuple(args, "O:restore_crossinterp_data", &capsule)) {
- return NULL;
- }
-
- _PyCrossInterpreterData *data = \
- (_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
- if (data == NULL) {
- return NULL;
- }
- return _PyCrossInterpreterData_NewObject(data);
-}
-
static PyMethodDef ml;
static PyObject *
@@ -3707,8 +3655,6 @@ static PyMethodDef TestMethods[] = {
{"run_in_subinterp_with_config",
_PyCFunction_CAST(run_in_subinterp_with_config),
METH_VARARGS | METH_KEYWORDS},
- {"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
- {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
{"create_cfunction", create_cfunction, METH_NOARGS},
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
PyDoc_STR("set_error_class(error_class) -> None")},
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index ecc2721a4988f9..4b541d49e0c20e 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -16,6 +16,7 @@
#include "pycore_bytesobject.h" // _PyBytes_Find()
#include "pycore_ceval.h" // _PyEval_AddPendingCall
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble, _PyCompile_CleanDoc
+#include "pycore_cross_interp.h" // _PyCrossInterpreterData
#include "pycore_fileutils.h" // _Py_normpath
#include "pycore_frame.h" // _PyInterpreterFrame
#include "pycore_gc.h" // PyGC_Head
@@ -1446,6 +1447,61 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
}
+/* Cross-interpreter data (xid) */
+
+static void
+_xid_capsule_destructor(PyObject *capsule)
+{
+ _PyCrossInterpreterData *data = \
+ (_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
+ if (data != NULL) {
+ assert(_PyCrossInterpreterData_Release(data) == 0);
+ PyMem_Free(data);
+ }
+}
+
+static PyObject *
+get_crossinterp_data(PyObject *self, PyObject *args)
+{
+ PyObject *obj = NULL;
+ if (!PyArg_ParseTuple(args, "O:get_crossinterp_data", &obj)) {
+ return NULL;
+ }
+
+ _PyCrossInterpreterData *data = PyMem_NEW(_PyCrossInterpreterData, 1);
+ if (data == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ if (_PyObject_GetCrossInterpreterData(obj, data) != 0) {
+ PyMem_Free(data);
+ return NULL;
+ }
+ PyObject *capsule = PyCapsule_New(data, NULL, _xid_capsule_destructor);
+ if (capsule == NULL) {
+ assert(_PyCrossInterpreterData_Release(data) == 0);
+ PyMem_Free(data);
+ }
+ return capsule;
+}
+
+static PyObject *
+restore_crossinterp_data(PyObject *self, PyObject *args)
+{
+ PyObject *capsule = NULL;
+ if (!PyArg_ParseTuple(args, "O:restore_crossinterp_data", &capsule)) {
+ return NULL;
+ }
+
+ _PyCrossInterpreterData *data = \
+ (_PyCrossInterpreterData *)PyCapsule_GetPointer(capsule, NULL);
+ if (data == NULL) {
+ return NULL;
+ }
+ return _PyCrossInterpreterData_NewObject(data);
+}
+
+
static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
@@ -1502,6 +1558,8 @@ static PyMethodDef module_functions[] = {
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"test_atexit", test_atexit, METH_NOARGS},
+ {"get_crossinterp_data", get_crossinterp_data, METH_VARARGS},
+ {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c
index bdffdba52aa02e..bb96eb339272b1 100644
--- a/Modules/_xxinterpchannelsmodule.c
+++ b/Modules/_xxinterpchannelsmodule.c
@@ -7,6 +7,7 @@
#include "Python.h"
#include "pycore_atexit.h" // _Py_AtExit()
+#include "pycore_cross_interp.h" // crossinterpdatafunc
#include "pycore_interp_id.h" // _PyInterpreterState_GetIDObject()
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 5ccc8958330650..d9c23c6ad1225f 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -214,6 +214,7 @@
+
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index 54a77f81a9a1ab..a4bd8d97e2a3cf 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -546,6 +546,9 @@
Include\internal
+
+ Include\internal
+
Include\internal
diff --git a/Python/pystate.c b/Python/pystate.c
index a9b404bd5c93e3..b6ae99b2eaeec3 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -39,6 +39,8 @@ to avoid the expense of doing their own locking).
extern "C" {
#endif
+// Forward declaration
+static crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
/****************************************/
/* helpers for the current thread state */
@@ -2338,7 +2340,7 @@ _xidata_clear(_PyCrossInterpreterData *data)
Py_CLEAR(data->obj);
}
-void
+static void
_PyCrossInterpreterData_Init(_PyCrossInterpreterData *data,
PyInterpreterState *interp,
void *shared, PyObject *obj,
@@ -2411,8 +2413,6 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data)
return 0;
}
-crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
-
/* This is a separate func from _PyCrossInterpreterData_Lookup in order
to keep the registry code separate. */
static crossinterpdatafunc
@@ -2653,7 +2653,7 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
We can reassess this policy when we move from a global registry to a
tp_* slot. */
-crossinterpdatafunc
+static crossinterpdatafunc
_PyCrossInterpreterData_Lookup(PyObject *obj)
{
struct _xidregistry *xidregistry = &_PyRuntime.xidregistry ;