Skip to content

Commit 3467656

Browse files
[3.14] gh-132775: Add _PyFunction_GetXIData() (gh-133955)
(cherry picked from commit 8cf4947, AKA gh-133481) Co-authored-by: Eric Snow <[email protected]>
1 parent c1aa5f8 commit 3467656

File tree

5 files changed

+103
-0
lines changed

5 files changed

+103
-0
lines changed

Include/internal/pycore_crossinterp.h

+7
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ PyAPI_FUNC(int) _PyCode_GetPureScriptXIData(
200200
PyObject *,
201201
_PyXIData_t *);
202202

203+
// _PyObject_GetXIData() for functions
204+
PyAPI_FUNC(PyObject *) _PyFunction_FromXIData(_PyXIData_t *);
205+
PyAPI_FUNC(int) _PyFunction_GetXIData(
206+
PyThreadState *,
207+
PyObject *,
208+
_PyXIData_t *);
209+
203210

204211
/* using cross-interpreter data */
205212

Lib/test/test_crossinterp.py

+34
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,40 @@ def test_other_objects(self):
758758
])
759759

760760

761+
class ShareableFuncTests(_GetXIDataTests):
762+
763+
MODE = 'func'
764+
765+
def test_stateless(self):
766+
self.assert_roundtrip_not_equal([
767+
*defs.STATELESS_FUNCTIONS,
768+
# Generators can be stateless too.
769+
*defs.FUNCTION_LIKE,
770+
])
771+
772+
def test_not_stateless(self):
773+
self.assert_not_shareable([
774+
*(f for f in defs.FUNCTIONS
775+
if f not in defs.STATELESS_FUNCTIONS),
776+
])
777+
778+
def test_other_objects(self):
779+
self.assert_not_shareable([
780+
None,
781+
True,
782+
False,
783+
Ellipsis,
784+
NotImplemented,
785+
9999,
786+
'spam',
787+
b'spam',
788+
(),
789+
[],
790+
{},
791+
object(),
792+
])
793+
794+
761795
class PureShareableScriptTests(_GetXIDataTests):
762796

763797
MODE = 'script-pure'

Modules/_testinternalcapi.c

+5
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,11 @@ get_crossinterp_data(PyObject *self, PyObject *args, PyObject *kwargs)
19891989
goto error;
19901990
}
19911991
}
1992+
else if (strcmp(mode, "func") == 0) {
1993+
if (_PyFunction_GetXIData(tstate, obj, xidata) != 0) {
1994+
goto error;
1995+
}
1996+
}
19921997
else if (strcmp(mode, "script") == 0) {
19931998
if (_PyCode_GetScriptXIData(tstate, obj, xidata) != 0) {
19941999
goto error;

Python/crossinterp.c

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "pycore_initconfig.h" // _PyStatus_OK()
1111
#include "pycore_namespace.h" // _PyNamespace_New()
1212
#include "pycore_pythonrun.h" // _Py_SourceAsString()
13+
#include "pycore_setobject.h" // _PySet_NextEntry()
1314
#include "pycore_typeobject.h" // _PyStaticType_InitBuiltin()
1415

1516

Python/crossinterp_data_lookup.h

+56
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,60 @@ _PyCode_GetXIData(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
677677
return 0;
678678
}
679679

680+
// function
681+
682+
PyObject *
683+
_PyFunction_FromXIData(_PyXIData_t *xidata)
684+
{
685+
// For now "stateless" functions are the only ones we must accommodate.
686+
687+
PyObject *code = _PyMarshal_ReadObjectFromXIData(xidata);
688+
if (code == NULL) {
689+
return NULL;
690+
}
691+
// Create a new function.
692+
assert(PyCode_Check(code));
693+
PyObject *globals = PyDict_New();
694+
if (globals == NULL) {
695+
Py_DECREF(code);
696+
return NULL;
697+
}
698+
PyObject *func = PyFunction_New(code, globals);
699+
Py_DECREF(code);
700+
Py_DECREF(globals);
701+
return func;
702+
}
703+
704+
int
705+
_PyFunction_GetXIData(PyThreadState *tstate, PyObject *func,
706+
_PyXIData_t *xidata)
707+
{
708+
if (!PyFunction_Check(func)) {
709+
const char *msg = "expected a function, got %R";
710+
format_notshareableerror(tstate, NULL, 0, msg, func);
711+
return -1;
712+
}
713+
if (_PyFunction_VerifyStateless(tstate, func) < 0) {
714+
PyObject *cause = _PyErr_GetRaisedException(tstate);
715+
assert(cause != NULL);
716+
const char *msg = "only stateless functions are shareable";
717+
set_notshareableerror(tstate, cause, 0, msg);
718+
Py_DECREF(cause);
719+
return -1;
720+
}
721+
PyObject *code = PyFunction_GET_CODE(func);
722+
723+
// Ideally code objects would be immortal and directly shareable.
724+
// In the meantime, we use marshal.
725+
if (_PyMarshal_GetXIData(tstate, code, xidata) < 0) {
726+
return -1;
727+
}
728+
// Replace _PyMarshal_ReadObjectFromXIData.
729+
// (_PyFunction_FromXIData() will call it.)
730+
_PyXIData_SET_NEW_OBJECT(xidata, _PyFunction_FromXIData);
731+
return 0;
732+
}
733+
680734

681735
// registration
682736

@@ -717,4 +771,6 @@ _register_builtins_for_crossinterpreter_data(dlregistry_t *xidregistry)
717771
if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) {
718772
Py_FatalError("could not register tuple for cross-interpreter sharing");
719773
}
774+
775+
// For now, we do not register PyCode_Type or PyFunction_Type.
720776
}

0 commit comments

Comments
 (0)