]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-132775: Add _PyFunction_GetXIData() (gh-133481)
authorEric Snow <ericsnowcurrently@gmail.com>
Mon, 12 May 2025 22:10:56 +0000 (16:10 -0600)
committerGitHub <noreply@github.com>
Mon, 12 May 2025 22:10:56 +0000 (22:10 +0000)
Include/internal/pycore_crossinterp.h
Lib/test/test_crossinterp.py
Modules/_testinternalcapi.c
Python/crossinterp.c
Python/crossinterp_data_lookup.h

index 9c9b2c2f9c599d49a6bf61871348d278be548211..19c55dd65983d7d4349adb28dfae72a40948e96e 100644 (file)
@@ -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 */
 
index b366a29645e9f23f7408818b23a94f2655321e04..cddacbc997005272e332080255fca7fff026aa01 100644 (file)
@@ -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'
index 3030f45d72cefacc42478c28b72364387005cb53..92f744c5a5fc707e766c561c831d3fe5aa522c61 100644 (file)
@@ -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;
index 7d7e6551c3f2d29273336e3b4aea79415842a5d9..725d6009f84014a36d4f54e9e25de400d01fc381 100644 (file)
@@ -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()
 
 
index 231537c66d78f6d1a58eb8e856a1073141e7fcfc..d69927dbcd387f9632db844a48cd203d5e420520 100644 (file)
@@ -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.
 }