Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita41ed97

Browse files
adphrostAndrew Frostitamaro
authored
GH-91049: Introduce set vectorcall field API for PyFunctionObject (GH-92257)
Co-authored-by: Andrew Frost <adfrost@fb.com>Co-authored-by: Itamar Ostricher <itamarost@gmail.com>
1 parente37ac5f commita41ed97

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::voidPyFunction_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_tfunc_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
returnself
581581

582582

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

585588
deftest_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+
deftest_setvectorcall(self):
767+
from_testcapiimportfunction_setvectorcall
768+
deff(num):returnnum+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_inrange(10*QUICKENING_WARMUP_DELAY):
775+
assert_equal("overridden",f(num))
776+
777+
deftest_setvectorcall_load_attr_specialization_skip(self):
778+
from_testcapiimportfunction_setvectorcall
779+
780+
classX:
781+
def__getattribute__(self,attr):
782+
returnattr
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_inrange(QUICKENING_WARMUP_DELAY):
791+
assert_equal("overridden",x.a)
792+
793+
deftest_setvectorcall_load_attr_specialization_deopt(self):
794+
from_testcapiimportfunction_setvectorcall
795+
796+
classX:
797+
def__getattribute__(self,attr):
798+
returnattr
799+
800+
defget_a(x):
801+
returnx.a
802+
803+
assert_equal=self.assertEqual
804+
x=X()
805+
# trigger LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization
806+
for_inrange(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_inrange(QUICKENING_WARMUP_DELAY):
812+
assert_equal("overridden",get_a(x))
813+
763814
@requires_limited_api
764815
deftest_vectorcall_limited(self):
765816
from_testcapiimportpyobject_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
returnPyObject_Vectorcall(func,stack,nargs,kwnames);
103103
}
104104

105+
staticPyObject*
106+
override_vectorcall(PyObject*callable,PyObject*const*args,size_tnargsf,
107+
PyObject*kwnames)
108+
{
109+
returnPyUnicode_FromString("overridden");
110+
}
111+
112+
staticPyObject*
113+
function_setvectorcall(PyObject*self,PyObject*func)
114+
{
115+
if (!PyFunction_Check(func)) {
116+
PyErr_SetString(PyExc_TypeError,"'func' must be a function");
117+
returnNULL;
118+
}
119+
PyFunction_SetVectorcall((PyFunctionObject*)func, (vectorcallfunc)override_vectorcall);
120+
Py_RETURN_NONE;
121+
}
122+
105123
staticPyObject*
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
returnfunc->func_version;
136136
}
137+
if (func->vectorcall!=_PyFunction_Vectorcall) {
138+
return0;
139+
}
137140
if (next_func_version==0) {
138141
return0;
139142
}
@@ -209,6 +212,14 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
209212
return0;
210213
}
211214

215+
void
216+
PyFunction_SetVectorcall(PyFunctionObject*func,vectorcallfuncvectorcall)
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_tfunc_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
intpositional_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
intcode_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
gotofail;
839839
}
840+
uint32_tversion=function_get_version(descr,LOAD_ATTR);
841+
if (version==0) {
842+
gotofail;
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)

[8]ページ先頭

©2009-2025 Movatter.jp