Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a41ed97

Browse files
adphrostAndrew Frostitamaro
authored
GH-91049: Introduce set vectorcall field API for PyFunctionObject (GH-92257)
Co-authored-by: Andrew Frost <[email protected]> Co-authored-by: Itamar Ostricher <[email protected]>
1 parent e37ac5f commit a41ed97

File tree

9 files changed

+115
-3
lines changed

9 files changed

+115
-3
lines changed

Doc/c-api/function.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ There are a few functions specific to Python functions.
8383
Raises :exc:`SystemError` and returns ``-1`` on failure.
8484
8585
86+
.. c:function:: void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall)
87+
88+
Set the vectorcall field of a given function object *func*.
89+
90+
Warning: extensions using this API must preserve the behavior
91+
of the unaltered (default) vectorcall function!
92+
93+
.. versionadded:: 3.12
94+
8695
.. c:function:: PyObject* PyFunction_GetClosure(PyObject *op)
8796
8897
Return the closure associated with the function object *op*. This can be ``NULL``

Doc/whatsnew/3.12.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ New Features
493493
functions in all running threads in addition to the calling one. (Contributed
494494
by Pablo Galindo in :gh:`93503`.)
495495

496+
* Added new function :c:func:`PyFunction_SetVectorcall` to the C API
497+
which sets the vectorcall field of a given :c:type:`PyFunctionObject`.
498+
(Contributed by Andrew Frost in :gh:`92257`.)
499+
496500
Porting to Python 3.12
497501
----------------------
498502

Include/cpython/funcobject.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ typedef struct {
4848
* defaults
4949
* kwdefaults (only if the object changes, not the contents of the dict)
5050
* code
51-
* annotations */
51+
* annotations
52+
* vectorcall function pointer */
5253
uint32_t func_version;
5354

5455
/* Invariant:
@@ -69,6 +70,7 @@ PyAPI_FUNC(PyObject *) PyFunction_GetGlobals(PyObject *);
6970
PyAPI_FUNC(PyObject *) PyFunction_GetModule(PyObject *);
7071
PyAPI_FUNC(PyObject *) PyFunction_GetDefaults(PyObject *);
7172
PyAPI_FUNC(int) PyFunction_SetDefaults(PyObject *, PyObject *);
73+
PyAPI_FUNC(void) PyFunction_SetVectorcall(PyFunctionObject *, vectorcallfunc);
7274
PyAPI_FUNC(PyObject *) PyFunction_GetKwDefaults(PyObject *);
7375
PyAPI_FUNC(int) PyFunction_SetKwDefaults(PyObject *, PyObject *);
7476
PyAPI_FUNC(PyObject *) PyFunction_GetClosure(PyObject *);

Lib/test/test_call.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,9 @@ def testfunction_kw(self, *, kw):
580580
return self
581581

582582

583+
QUICKENING_WARMUP_DELAY = 8
584+
585+
583586
class TestPEP590(unittest.TestCase):
584587

585588
def test_method_descriptor_flag(self):
@@ -760,6 +763,54 @@ def __call__(self, *args):
760763
self.assertEqual(expected, meth(*args1, **kwargs))
761764
self.assertEqual(expected, wrapped(*args, **kwargs))
762765

766+
def test_setvectorcall(self):
767+
from _testcapi import function_setvectorcall
768+
def f(num): return num + 1
769+
assert_equal = self.assertEqual
770+
num = 10
771+
assert_equal(11, f(num))
772+
function_setvectorcall(f)
773+
# make sure specializer is triggered by running > 50 times
774+
for _ in range(10 * QUICKENING_WARMUP_DELAY):
775+
assert_equal("overridden", f(num))
776+
777+
def test_setvectorcall_load_attr_specialization_skip(self):
778+
from _testcapi import function_setvectorcall
779+
780+
class X:
781+
def __getattribute__(self, attr):
782+
return attr
783+
784+
assert_equal = self.assertEqual
785+
x = X()
786+
assert_equal("a", x.a)
787+
function_setvectorcall(X.__getattribute__)
788+
# make sure specialization doesn't trigger
789+
# when vectorcall is overridden
790+
for _ in range(QUICKENING_WARMUP_DELAY):
791+
assert_equal("overridden", x.a)
792+
793+
def test_setvectorcall_load_attr_specialization_deopt(self):
794+
from _testcapi import function_setvectorcall
795+
796+
class X:
797+
def __getattribute__(self, attr):
798+
return attr
799+
800+
def get_a(x):
801+
return x.a
802+
803+
assert_equal = self.assertEqual
804+
x = X()
805+
# trigger LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization
806+
for _ in range(QUICKENING_WARMUP_DELAY):
807+
assert_equal("a", get_a(x))
808+
function_setvectorcall(X.__getattribute__)
809+
# make sure specialized LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN
810+
# gets deopted due to overridden vectorcall
811+
for _ in range(QUICKENING_WARMUP_DELAY):
812+
assert_equal("overridden", get_a(x))
813+
763814
@requires_limited_api
764815
def test_vectorcall_limited(self):
765816
from _testcapi import pyobject_vectorcall
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add new function :c:func:`PyFunction_SetVectorcall` to the C API
2+
which sets the vectorcall field of a given :c:type:`PyFunctionObject`.
3+
4+
Warning: extensions using this API must preserve the behavior
5+
of the unaltered function!

Modules/_testcapi/vectorcall.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ test_pyobject_vectorcall(PyObject *self, PyObject *args)
102102
return PyObject_Vectorcall(func, stack, nargs, kwnames);
103103
}
104104

105+
static PyObject *
106+
override_vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf,
107+
PyObject *kwnames)
108+
{
109+
return PyUnicode_FromString("overridden");
110+
}
111+
112+
static PyObject *
113+
function_setvectorcall(PyObject *self, PyObject *func)
114+
{
115+
if (!PyFunction_Check(func)) {
116+
PyErr_SetString(PyExc_TypeError, "'func' must be a function");
117+
return NULL;
118+
}
119+
PyFunction_SetVectorcall((PyFunctionObject *)func, (vectorcallfunc)override_vectorcall);
120+
Py_RETURN_NONE;
121+
}
122+
105123
static PyObject *
106124
test_pyvectorcall_call(PyObject *self, PyObject *args)
107125
{
@@ -244,6 +262,7 @@ static PyMethodDef TestMethods[] = {
244262
{"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS},
245263
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
246264
{"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS},
265+
{"function_setvectorcall", function_setvectorcall, METH_O},
247266
{"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS},
248267
_TESTCAPI_MAKE_VECTORCALL_CLASS_METHODDEF
249268
_TESTCAPI_HAS_VECTORCALL_FLAG_METHODDEF

Objects/funcobject.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ uint32_t _PyFunction_GetVersionForCurrentState(PyFunctionObject *func)
134134
if (func->func_version != 0) {
135135
return func->func_version;
136136
}
137+
if (func->vectorcall != _PyFunction_Vectorcall) {
138+
return 0;
139+
}
137140
if (next_func_version == 0) {
138141
return 0;
139142
}
@@ -209,6 +212,14 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
209212
return 0;
210213
}
211214

215+
void
216+
PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall)
217+
{
218+
assert(func != NULL);
219+
func->func_version = 0;
220+
func->vectorcall = vectorcall;
221+
}
222+
212223
PyObject *
213224
PyFunction_GetKwDefaults(PyObject *op)
214225
{

Python/ceval.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3126,8 +3126,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
31263126
PyObject *getattribute = read_obj(cache->descr);
31273127
assert(Py_IS_TYPE(getattribute, &PyFunction_Type));
31283128
PyFunctionObject *f = (PyFunctionObject *)getattribute;
3129+
uint32_t func_version = read_u32(cache->keys_version);
3130+
assert(func_version != 0);
3131+
DEOPT_IF(f->func_version != func_version, LOAD_ATTR);
31293132
PyCodeObject *code = (PyCodeObject *)f->func_code;
3130-
DEOPT_IF(((PyCodeObject *)f->func_code)->co_argcount != 2, LOAD_ATTR);
3133+
assert(code->co_argcount == 2);
31313134
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
31323135
STAT_INC(LOAD_ATTR, hit);
31333136

@@ -4133,7 +4136,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
41334136
function = PEEK(total_args + 1);
41344137
int positional_args = total_args - KWNAMES_LEN();
41354138
// Check if the call can be inlined or not
4136-
if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL) {
4139+
if (Py_TYPE(function) == &PyFunction_Type &&
4140+
tstate->interp->eval_frame == NULL &&
4141+
((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall)
4142+
{
41374143
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags;
41384144
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(function));
41394145
STACK_SHRINK(total_args);

Python/specialize.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,11 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
837837
if (!function_check_args(descr, 2, LOAD_ATTR)) {
838838
goto fail;
839839
}
840+
uint32_t version = function_get_version(descr, LOAD_ATTR);
841+
if (version == 0) {
842+
goto fail;
843+
}
844+
write_u32(lm_cache->keys_version, version);
840845
/* borrowed */
841846
write_obj(lm_cache->descr, descr);
842847
write_u32(lm_cache->type_version, type->tp_version_tag);

0 commit comments

Comments
 (0)