summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2025-05-12 22:37:55 (GMT)
committerGitHub <noreply@github.com>2025-05-12 22:37:55 (GMT)
commit3467656b18d188511df33d05be918f6d1f6ceae5 (patch)
treebc41ff2308a7d8bc17a6799bf16da2bb0ca57115
parentc1aa5f82d9442f5a15676e962aba3501951c542e (diff)
downloadcpython-3467656b18d188511df33d05be918f6d1f6ceae5.zip
cpython-3467656b18d188511df33d05be918f6d1f6ceae5.tar.gz
cpython-3467656b18d188511df33d05be918f6d1f6ceae5.tar.bz2
[3.14] gh-132775: Add _PyFunction_GetXIData() (gh-133955)
(cherry picked from commit 8cf4947b0f, AKA gh-133481) Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
-rw-r--r--Include/internal/pycore_crossinterp.h7
-rw-r--r--Lib/test/test_crossinterp.py34
-rw-r--r--Modules/_testinternalcapi.c5
-rw-r--r--Python/crossinterp.c1
-rw-r--r--Python/crossinterp_data_lookup.h56
5 files changed, 103 insertions, 0 deletions
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 9c9b2c2..19c55dd 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 b366a29..cddacbc 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 3030f45..92f744c 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 7d7e655..725d600 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 231537c..d69927d 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.
}