| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -83,6 +83,15 @@ There are a few functions specific to Python functions. | |||
| 83 | 83 | Raises :exc:`SystemError` and returns ``-1`` on failure. | |
| 84 | 84 | ||
| 85 | 85 | ||
| 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 | + | ||
| 86 | 95 | .. c:function:: PyObject* PyFunction_GetClosure(PyObject *op) | |
| 87 | 96 | ||
| 88 | 97 | Return the closure associated with the function object *op*. This can be ``NULL`` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -493,6 +493,10 @@ New Features | |||
| 493 | 493 | functions in all running threads in addition to the calling one. (Contributed | |
| 494 | 494 | by Pablo Galindo in :gh:`93503`.) | |
| 495 | 495 | ||
| 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 | + | ||
| 496 | 500 | Porting to Python 3.12 | |
| 497 | 501 | ---------------------- | |
| 498 | 502 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,7 +48,8 @@ typedef struct { | |||
| 48 | 48 | * defaults | |
| 49 | 49 | * kwdefaults (only if the object changes, not the contents of the dict) | |
| 50 | 50 | * code | |
| 51 | - * annotations */ | ||
| 51 | + * annotations | ||
| 52 | + * vectorcall function pointer */ | ||
| 52 | 53 | uint32_t func_version; | |
| 53 | 54 | ||
| 54 | 55 | /* Invariant: | |
@@ -69,6 +70,7 @@ PyAPI_FUNC(PyObject *) PyFunction_GetGlobals(PyObject *); | |||
| 69 | 70 | PyAPI_FUNC(PyObject *) PyFunction_GetModule(PyObject *); | |
| 70 | 71 | PyAPI_FUNC(PyObject *) PyFunction_GetDefaults(PyObject *); | |
| 71 | 72 | PyAPI_FUNC(int) PyFunction_SetDefaults(PyObject *, PyObject *); | |
| 73 | + PyAPI_FUNC(void) PyFunction_SetVectorcall(PyFunctionObject *, vectorcallfunc); | ||
| 72 | 74 | PyAPI_FUNC(PyObject *) PyFunction_GetKwDefaults(PyObject *); | |
| 73 | 75 | PyAPI_FUNC(int) PyFunction_SetKwDefaults(PyObject *, PyObject *); | |
| 74 | 76 | PyAPI_FUNC(PyObject *) PyFunction_GetClosure(PyObject *); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -580,6 +580,9 @@ def testfunction_kw(self, *, kw): | |||
| 580 | 580 | return self | |
| 581 | 581 | ||
| 582 | 582 | ||
| 583 | + QUICKENING_WARMUP_DELAY = 8 | ||
| 584 | + | ||
| 585 | + | ||
| 583 | 586 | class TestPEP590(unittest.TestCase): | |
| 584 | 587 | ||
| 585 | 588 | def test_method_descriptor_flag(self): | |
@@ -760,6 +763,54 @@ def __call__(self, *args): | |||
| 760 | 763 | self.assertEqual(expected, meth(*args1, **kwargs)) | |
| 761 | 764 | self.assertEqual(expected, wrapped(*args, **kwargs)) | |
| 762 | 765 | ||
| 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 | + | ||
| 763 | 814 | @requires_limited_api | |
| 764 | 815 | def test_vectorcall_limited(self): | |
| 765 | 816 | from _testcapi import pyobject_vectorcall | |
| Original file line number | Diff line number | Diff 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! | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -102,6 +102,24 @@ test_pyobject_vectorcall(PyObject *self, PyObject *args) | |||
| 102 | 102 | return PyObject_Vectorcall(func, stack, nargs, kwnames); | |
| 103 | 103 | } | |
| 104 | 104 | ||
| 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 | + | ||
| 105 | 123 | static PyObject * | |
| 106 | 124 | test_pyvectorcall_call(PyObject *self, PyObject *args) | |
| 107 | 125 | { | |
@@ -244,6 +262,7 @@ static PyMethodDef TestMethods[] = { | |||
| 244 | 262 | {"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS}, | |
| 245 | 263 | {"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS}, | |
| 246 | 264 | {"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS}, | |
| 265 | + {"function_setvectorcall", function_setvectorcall, METH_O}, | ||
| 247 | 266 | {"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS}, | |
| 248 | 267 | _TESTCAPI_MAKE_VECTORCALL_CLASS_METHODDEF | |
| 249 | 268 | _TESTCAPI_HAS_VECTORCALL_FLAG_METHODDEF | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -134,6 +134,9 @@ uint32_t _PyFunction_GetVersionForCurrentState(PyFunctionObject *func) | |||
| 134 | 134 | if (func->func_version != 0) { | |
| 135 | 135 | return func->func_version; | |
| 136 | 136 | } | |
| 137 | + if (func->vectorcall != _PyFunction_Vectorcall) { | ||
| 138 | + return 0; | ||
| 139 | + } | ||
| 137 | 140 | if (next_func_version == 0) { | |
| 138 | 141 | return 0; | |
| 139 | 142 | } | |
@@ -209,6 +212,14 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults) | |||
| 209 | 212 | return 0; | |
| 210 | 213 | } | |
| 211 | 214 | ||
| 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 | + | ||
| 212 | 223 | PyObject * | |
| 213 | 224 | PyFunction_GetKwDefaults(PyObject *op) | |
| 214 | 225 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3126,8 +3126,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |||
| 3126 | 3126 | PyObject *getattribute = read_obj(cache->descr); | |
| 3127 | 3127 | assert(Py_IS_TYPE(getattribute, &PyFunction_Type)); | |
| 3128 | 3128 | 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); | ||
| 3129 | 3132 | PyCodeObject *code = (PyCodeObject *)f->func_code; | |
| 3130 | - DEOPT_IF(((PyCodeObject *)f->func_code)->co_argcount != 2, LOAD_ATTR); | ||
| 3133 | + assert(code->co_argcount == 2); | ||
| 3131 | 3134 | DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL); | |
| 3132 | 3135 | STAT_INC(LOAD_ATTR, hit); | |
| 3133 | 3136 | ||
@@ -4133,7 +4136,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |||
| 4133 | 4136 | function = PEEK(total_args + 1); | |
| 4134 | 4137 | int positional_args = total_args - KWNAMES_LEN(); | |
| 4135 | 4138 | // 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 | + { | ||
| 4137 | 4143 | int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags; | |
| 4138 | 4144 | PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(function)); | |
| 4139 | 4145 | STACK_SHRINK(total_args); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -837,6 +837,11 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name) | |||
| 837 | 837 | if (!function_check_args(descr, 2, LOAD_ATTR)) { | |
| 838 | 838 | goto fail; | |
| 839 | 839 | } | |
| 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); | ||
| 840 | 845 | /* borrowed */ | |
| 841 | 846 | write_obj(lm_cache->descr, descr); | |
| 842 | 847 | write_u32(lm_cache->type_version, type->tp_version_tag); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments