diff options
author | Eric Snow <ericsnowcurrently@gmail.com> | 2025-05-12 16:10:56 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-12 22:10:56 +0000 |
commit | 8cf4947b0f2d37f7ffeca136ac4f99cb4cb70e5c (patch) | |
tree | 556a2a34633f18e7271467d73854ad344831f33d | |
parent | 121ed71f4e395948d313249b2ad33e1e21581f8a (diff) | |
download | cpython-8cf4947b0f2d37f7ffeca136ac4f99cb4cb70e5c.tar.gz cpython-8cf4947b0f2d37f7ffeca136ac4f99cb4cb70e5c.zip |
-rw-r--r-- | Include/internal/pycore_crossinterp.h | 7 | ||||
-rw-r--r-- | Lib/test/test_crossinterp.py | 34 | ||||
-rw-r--r-- | Modules/_testinternalcapi.c | 5 | ||||
-rw-r--r-- | Python/crossinterp.c | 1 | ||||
-rw-r--r-- | Python/crossinterp_data_lookup.h | 56 |
5 files changed, 103 insertions, 0 deletions
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 9c9b2c2f9c5..19c55dd6598 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -200,6 +200,13 @@ PyAPI_FUNC(int) _PyCode_GetPureScriptXIData( PyObject *, _PyXIData_t *); +// _PyObject_GetXIData() for functions +PyAPI_FUNC(PyObject *) _PyFunction_FromXIData(_PyXIData_t *); +PyAPI_FUNC(int) _PyFunction_GetXIData( + PyThreadState *, + PyObject *, + _PyXIData_t *); + /* using cross-interpreter data */ diff --git a/Lib/test/test_crossinterp.py b/Lib/test/test_crossinterp.py index b366a29645e..cddacbc9970 100644 --- a/Lib/test/test_crossinterp.py +++ b/Lib/test/test_crossinterp.py @@ -758,6 +758,40 @@ class CodeTests(_GetXIDataTests): ]) +class ShareableFuncTests(_GetXIDataTests): + + MODE = 'func' + + def test_stateless(self): + self.assert_roundtrip_not_equal([ + *defs.STATELESS_FUNCTIONS, + # Generators can be stateless too. + *defs.FUNCTION_LIKE, + ]) + + def test_not_stateless(self): + self.assert_not_shareable([ + *(f for f in defs.FUNCTIONS + if f not in defs.STATELESS_FUNCTIONS), + ]) + + def test_other_objects(self): + self.assert_not_shareable([ + None, + True, + False, + Ellipsis, + NotImplemented, + 9999, + 'spam', + b'spam', + (), + [], + {}, + object(), + ]) + + class PureShareableScriptTests(_GetXIDataTests): MODE = 'script-pure' diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 3030f45d72c..92f744c5a5f 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -1989,6 +1989,11 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs) goto error; } } + else if (strcmp(mode, "func") == 0) { + if (_PyFunction_GetXIData(tstate, obj, xidata) != 0) { + goto error; + } + } else if (strcmp(mode, "script") == 0) { if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) { goto error; diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 7d7e6551c3f..725d6009f84 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -10,6 +10,7 @@ #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_namespace.h" // _PyNamespace_New() #include "pycore_pythonrun.h" // _Py_SourceAsString() +#include "pycore_setobject.h" // _PySet_NextEntry() #include "pycore_typeobject.h" // _PyStaticType_InitBuiltin() diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h index 231537c66d7..d69927dbcd3 100644 --- a/Python/crossinterp_data_lookup.h +++ b/Python/crossinterp_data_lookup.h @@ -677,6 +677,60 @@ _PyCode_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata) return 0; } +// function + +PyObject * +_PyFunction_FromXIData(_PyXIData_t *xidata) +{ + // For now "stateless" functions are the only ones we must accommodate. + + PyObject *code = _PyMarshal_ReadObjectFromXIData(xidata); + if (code == NULL) { + return NULL; + } + // Create a new function. + assert(PyCode_Check(code)); + PyObject *globals = PyDict_New(); + if (globals == NULL) { + Py_DECREF(code); + return NULL; + } + PyObject *func = PyFunction_New(code, globals); + Py_DECREF(code); + Py_DECREF(globals); + return func; +} + +int +_PyFunction_GetXIData(PyThreadState *tstate, PyObject *func, + _PyXIData_t *xidata) +{ + if (!PyFunction_Check(func)) { + const char *msg = "expected a function, got %R"; + format_notshareableerror(tstate, NULL, 0, msg, func); + return -1; + } + if (_PyFunction_VerifyStateless(tstate, func) < 0) { + PyObject *cause = _PyErr_GetRaisedException(tstate); + assert(cause != NULL); + const char *msg = "only stateless functions are shareable"; + set_notshareableerror(tstate, cause, 0, msg); + Py_DECREF(cause); + return -1; + } + PyObject *code = PyFunction_GET_CODE(func); + + // Ideally code objects would be immortal and directly shareable. + // In the meantime, we use marshal. + if (_PyMarshal_GetXIData(tstate, code, xidata) < 0) { + return -1; + } + // Replace _PyMarshal_ReadObjectFromXIData. + // (_PyFunction_FromXIData() will call it.) + _PyXIData_SET_NEW_OBJECT(xidata, _PyFunction_FromXIData); + return 0; +} + // registration @@ -717,4 +771,6 @@ _register_builtins_for_crossinterpreter_data(dlregistry_t *xidregistry) if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) { Py_FatalError("could not register tuple for cross-interpreter sharing"); } + + // For now, we do not register PyCode_Type or PyFunction_Type. } |