From 9cf96ec740fb59155d8842a2bc91f52c04beb22d Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Tue, 3 May 2022 12:53:10 -0600 Subject: [PATCH 01/21] Enable setting vectorcall field on PyFunctionObjects --- Include/cpython/funcobject.h | 1 + Lib/test/test_call.py | 8 ++++++++ Modules/_testcapimodule.c | 21 +++++++++++++++++++++ Objects/funcobject.c | 11 +++++++++++ 4 files changed, 41 insertions(+) diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h index 99ac6008f8b611..32f53bebd54fa0 100644 --- a/Include/cpython/funcobject.h +++ b/Include/cpython/funcobject.h @@ -69,6 +69,7 @@ PyAPI_FUNC(PyObject *) PyFunction_GetGlobals(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetModule(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetDefaults(PyObject *); PyAPI_FUNC(int) PyFunction_SetDefaults(PyObject *, PyObject *); +PyAPI_FUNC(int) PyFunction_SetVectorcall(PyFunctionObject *, vectorcallfunc); PyAPI_FUNC(PyObject *) PyFunction_GetKwDefaults(PyObject *); PyAPI_FUNC(int) PyFunction_SetKwDefaults(PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetClosure(PyObject *); diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 6936f093e3db19..1af8bbaf129cb2 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -665,6 +665,14 @@ def __call__(self, *args): self.assertEqual(expected, meth(*args1, **kwargs)) self.assertEqual(expected, wrapped(*args, **kwargs)) + def test_setvectorcall(self): + from _testcapi import pyobject_setvectorcall + def f(num): return num + 1 + arg = (10,) + + assert f(*arg) is 11 + pyobject_setvectorcall(f) + assert f(*arg) is None class A: def method_two_args(self, x, y): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 9073f33e226bd3..4c923688b84c92 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5285,6 +5285,26 @@ test_pyobject_vectorcall(PyObject *self, PyObject *args) } +static PyObject * +override_vectorcall( + PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames) { + Py_RETURN_NONE; +} + +static PyObject * +test_pyobject_setvectorcall(PyObject *self, PyObject *args) +{ + PyFunctionObject *func = NULL; + if (!PyArg_ParseTuple(args, "O", &func)) { + return NULL; + } + if(PyFunction_SetVectorcall(func, (vectorcallfunc)override_vectorcall) != 0) { + PyErr_SetString(PyExc_TypeError, "Something went wrong setting vectorcall field"); + return NULL; + } + Py_RETURN_NONE; +} + static PyObject * test_pyvectorcall_call(PyObject *self, PyObject *args) { @@ -6186,6 +6206,7 @@ static PyMethodDef TestMethods[] = { {"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS}, {"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS}, {"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS}, + {"pyobject_setvectorcall", test_pyobject_setvectorcall, METH_VARARGS}, {"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS}, {"stack_pointer", stack_pointer, METH_NOARGS}, #ifdef W_STOPCODE diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 1e0cfb7efb479a..484b8dec4802c9 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -133,6 +133,9 @@ uint32_t _PyFunction_GetVersionForCurrentState(PyFunctionObject *func) if (func->func_version != 0) { return func->func_version; } + if (func->vectorcall != _PyFunction_Vectorcall) { + return 0; + } if (next_func_version == 0) { return 0; } @@ -208,6 +211,14 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults) return 0; } +int +PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall) +{ + func->func_version = 0; + func->vectorcall = vectorcall; + return 0; +} + PyObject * PyFunction_GetKwDefaults(PyObject *op) { From 282e3dcaccaa43d63f1605f6b97046ad7e54a6f8 Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Tue, 3 May 2022 13:28:51 -0600 Subject: [PATCH 02/21] check if vectorcall is nondefault before inlining call --- Lib/test/test_call.py | 9 +++++---- Python/ceval.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 1af8bbaf129cb2..72863a28a16f1d 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -668,11 +668,12 @@ def __call__(self, *args): def test_setvectorcall(self): from _testcapi import pyobject_setvectorcall def f(num): return num + 1 - arg = (10,) - - assert f(*arg) is 11 + num = 10 + assert f(num) is 11 pyobject_setvectorcall(f) - assert f(*arg) is None + # make sure specializer is triggered by running > 50 times + for _ in range(51): + assert f(num) is None class A: def method_two_args(self, x, y): diff --git a/Python/ceval.c b/Python/ceval.c index f3329b5d9d454d..dacdc2cfbeab56 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4792,7 +4792,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int PyObject *function = PEEK(total_args + 1); int positional_args = total_args - KWNAMES_LEN(); // Check if the call can be inlined or not - if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL) { + if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL && ((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall) { int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags; PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function); STACK_SHRINK(total_args); From edcdcf1ae2496b0d0733c40da1bc02b2b8e24682 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 3 May 2022 19:35:37 +0000 Subject: [PATCH 03/21] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst diff --git a/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst new file mode 100644 index 00000000000000..f3ea2572f03d49 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst @@ -0,0 +1,6 @@ +CPython extensions providing optimized execution of Python bytecode (like Cinder JIT and Pyjion) +can benefit from being able to modify the vectorcall field on instances of PyFunctionObject to allow calling the optimized path (e.g. JIT-compiled) directly. + +This PR introduces an API call where vectorcall field can be modified like so: + +`void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall);` From debf5a90ede8b0a706f96940067e22c721774b20 Mon Sep 17 00:00:00 2001 From: adphrost <104581013+adphrost@users.noreply.github.com> Date: Tue, 3 May 2022 15:00:11 -0600 Subject: [PATCH 04/21] Apply suggestions from code review Change how assertion is done Co-authored-by: Itamar Ostricher --- Lib/test/test_call.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 72863a28a16f1d..20b533524e0970 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -669,11 +669,11 @@ def test_setvectorcall(self): from _testcapi import pyobject_setvectorcall def f(num): return num + 1 num = 10 - assert f(num) is 11 + self.assertEqual(11, f(num)) pyobject_setvectorcall(f) # make sure specializer is triggered by running > 50 times for _ in range(51): - assert f(num) is None + self.assertIsNone(f(num)) class A: def method_two_args(self, x, y): From 473d18d75f8c3285f520d30e53cfb0dabb455df2 Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Tue, 3 May 2022 15:36:03 -0600 Subject: [PATCH 05/21] addressed comments --- Include/cpython/funcobject.h | 2 +- Lib/test/test_call.py | 4 ++-- Modules/_testcapimodule.c | 13 +++++-------- Objects/funcobject.c | 3 +-- Python/ceval.c | 4 +++- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h index 32f53bebd54fa0..1a635f5f054961 100644 --- a/Include/cpython/funcobject.h +++ b/Include/cpython/funcobject.h @@ -69,7 +69,7 @@ PyAPI_FUNC(PyObject *) PyFunction_GetGlobals(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetModule(PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetDefaults(PyObject *); PyAPI_FUNC(int) PyFunction_SetDefaults(PyObject *, PyObject *); -PyAPI_FUNC(int) PyFunction_SetVectorcall(PyFunctionObject *, vectorcallfunc); +PyAPI_FUNC(void) PyFunction_SetVectorcall(PyFunctionObject *, vectorcallfunc); PyAPI_FUNC(PyObject *) PyFunction_GetKwDefaults(PyObject *); PyAPI_FUNC(int) PyFunction_SetKwDefaults(PyObject *, PyObject *); PyAPI_FUNC(PyObject *) PyFunction_GetClosure(PyObject *); diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 20b533524e0970..39e8a46e1cb60e 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -666,11 +666,11 @@ def __call__(self, *args): self.assertEqual(expected, wrapped(*args, **kwargs)) def test_setvectorcall(self): - from _testcapi import pyobject_setvectorcall + from _testcapi import function_setvectorcall def f(num): return num + 1 num = 10 self.assertEqual(11, f(num)) - pyobject_setvectorcall(f) + function_setvectorcall(f) # make sure specializer is triggered by running > 50 times for _ in range(51): self.assertIsNone(f(num)) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4c923688b84c92..6d29f63dd03ca9 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5292,16 +5292,13 @@ override_vectorcall( } static PyObject * -test_pyobject_setvectorcall(PyObject *self, PyObject *args) +function_setvectorcall(PyObject *self, PyObject *func) { - PyFunctionObject *func = NULL; - if (!PyArg_ParseTuple(args, "O", &func)) { - return NULL; - } - if(PyFunction_SetVectorcall(func, (vectorcallfunc)override_vectorcall) != 0) { - PyErr_SetString(PyExc_TypeError, "Something went wrong setting vectorcall field"); + if (!PyFunction_Check(func)) { + PyErr_BadInternalCall(); return NULL; } + PyFunction_SetVectorcall((PyFunctionObject *)func, (vectorcallfunc)override_vectorcall); Py_RETURN_NONE; } @@ -6206,7 +6203,7 @@ static PyMethodDef TestMethods[] = { {"pyobject_fastcall", test_pyobject_fastcall, METH_VARARGS}, {"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS}, {"pyobject_vectorcall", test_pyobject_vectorcall, METH_VARARGS}, - {"pyobject_setvectorcall", test_pyobject_setvectorcall, METH_VARARGS}, + {"function_setvectorcall", function_setvectorcall, METH_O}, {"pyvectorcall_call", test_pyvectorcall_call, METH_VARARGS}, {"stack_pointer", stack_pointer, METH_NOARGS}, #ifdef W_STOPCODE diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 484b8dec4802c9..592d861ad4ca00 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -211,12 +211,11 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults) return 0; } -int +void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall) { func->func_version = 0; func->vectorcall = vectorcall; - return 0; } PyObject * diff --git a/Python/ceval.c b/Python/ceval.c index dacdc2cfbeab56..dfabcf3fe47004 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4792,7 +4792,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int PyObject *function = PEEK(total_args + 1); int positional_args = total_args - KWNAMES_LEN(); // Check if the call can be inlined or not - if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL && ((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall) { + if (Py_TYPE(function) == &PyFunction_Type && + tstate->interp->eval_frame == NULL && + ((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall) { int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags; PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function); STACK_SHRINK(total_args); From 76e8c9d224e0120b0ddf00389aabcb8e475638d8 Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Tue, 3 May 2022 16:01:29 -0600 Subject: [PATCH 06/21] added to docs --- Doc/c-api/function.rst | 5 +++++ Doc/whatsnew/3.11.rst | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 56c18396d3221d..5a8504abd8ed97 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -83,6 +83,11 @@ There are a few functions specific to Python functions. Raises :exc:`SystemError` and returns ``-1`` on failure. +.. c:function:: void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall) + + Set the vectorcall field of a given function object *func* + + .. c:function:: PyObject* PyFunction_GetClosure(PyObject *op) Return the closure associated with the function object *op*. This can be ``NULL`` diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 0a8ba1e8843e06..8d644a2c745b13 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -172,6 +172,8 @@ Irit Katriel in :issue:`45607`.) .. _new-feat-related-type-hints-311: + + New Features Related to Type Hints ================================== @@ -1469,6 +1471,11 @@ New Features (Contributed by Irit Katriel in :issue:`46343`.) +* Add new function :c:func:`PyFunction_SetVectorcall` to the C API + which sets the vectorcall field of a given PyFunctionObject + (Contributed by Andrew Frost in :issue:`91049`.) + + Porting to Python 3.11 ---------------------- From 1543f445ef4ddecc2f9795f10a88cbcb6c39e91a Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Wed, 15 Jun 2022 18:09:29 -0700 Subject: [PATCH 07/21] updated doc with fix by itamaro --- Doc/whatsnew/3.12.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 59eb4348cdca47..7602b196fc3d44 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -236,6 +236,11 @@ New Features an additional metaclass argument. (Contributed by Wenzel Jakob in :gh:`93012`.) + +* Add new function :c:func:`PyFunction_SetVectorcall` to the C API + which sets the vectorcall field of a given PyFunctionObject + (Contributed by Andrew Frost in :gh:`92257`.) + Porting to Python 3.12 ---------------------- From 08082bd6f20d663a7eadccdc4fab9d2203843da4 Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Wed, 15 Jun 2022 18:11:24 -0700 Subject: [PATCH 08/21] formatting --- Doc/whatsnew/3.12.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 7602b196fc3d44..8cec57cf4dfbd9 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -236,7 +236,6 @@ New Features an additional metaclass argument. (Contributed by Wenzel Jakob in :gh:`93012`.) - * Add new function :c:func:`PyFunction_SetVectorcall` to the C API which sets the vectorcall field of a given PyFunctionObject (Contributed by Andrew Frost in :gh:`92257`.) From ed9332736cdedced71038130cdd2747728d82245 Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Wed, 15 Jun 2022 18:18:37 -0700 Subject: [PATCH 09/21] removed doc from 3.11 --- Doc/whatsnew/3.11.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 9653ac18c96ecd..b56176061dba61 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1622,11 +1622,6 @@ New Features * Added the :c:member:`PyConfig.safe_path` member. (Contributed by Victor Stinner in :gh:`57684`.) -* Add new function :c:func:`PyFunction_SetVectorcall` to the C API - which sets the vectorcall field of a given PyFunctionObject - (Contributed by Andrew Frost in :issue:`91049`.) - - Porting to Python 3.11 ---------------------- From 24ffd302948940df399abb024f49c24575e887cf Mon Sep 17 00:00:00 2001 From: Andrew Frost Date: Wed, 15 Jun 2022 19:48:19 -0700 Subject: [PATCH 10/21] remove lines from 3.11 --- Doc/whatsnew/3.11.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index b56176061dba61..8075a726f61f34 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -178,8 +178,6 @@ Irit Katriel in :issue:`45607`.) .. _new-feat-related-type-hints-311: - - New Features Related to Type Hints ================================== From 89cb4b25271be5a0e5dd7b18e464e3dc72d4195a Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 21 Jun 2022 13:58:22 -0700 Subject: [PATCH 11/21] Apply review feedback from vstinner and markshannon --- Doc/c-api/function.rst | 1 + Doc/whatsnew/3.12.rst | 4 +++- Lib/test/test_call.py | 2 +- .../C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst | 9 ++++----- Modules/_testcapimodule.c | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 5a8504abd8ed97..5c4e87f8b887dc 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -87,6 +87,7 @@ There are a few functions specific to Python functions. Set the vectorcall field of a given function object *func* + .. versionadded:: 3.12 .. c:function:: PyObject* PyFunction_GetClosure(PyObject *op) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 8cec57cf4dfbd9..b2dfc80284d115 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -237,7 +237,9 @@ New Features (Contributed by Wenzel Jakob in :gh:`93012`.) * Add new function :c:func:`PyFunction_SetVectorcall` to the C API - which sets the vectorcall field of a given PyFunctionObject + which sets the vectorcall field of a given :c:type:`PyFunctionObject` + Warning: extensions using this API must preserve the behavior + of the unaltered function! (Contributed by Andrew Frost in :gh:`92257`.) Porting to Python 3.12 diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 39e8a46e1cb60e..73b72c5d7a2c23 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -673,7 +673,7 @@ def f(num): return num + 1 function_setvectorcall(f) # make sure specializer is triggered by running > 50 times for _ in range(51): - self.assertIsNone(f(num)) + self.assertEqual("overridden", f(num)) class A: def method_two_args(self, x, y): diff --git a/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst index f3ea2572f03d49..e87c7ede7c4c57 100644 --- a/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst +++ b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst @@ -1,6 +1,5 @@ -CPython extensions providing optimized execution of Python bytecode (like Cinder JIT and Pyjion) -can benefit from being able to modify the vectorcall field on instances of PyFunctionObject to allow calling the optimized path (e.g. JIT-compiled) directly. +Add new function :c:func:`PyFunction_SetVectorcall` to the C API +which sets the vectorcall field of a given :c:type:`PyFunctionObject` -This PR introduces an API call where vectorcall field can be modified like so: - -`void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall);` +Warning: extensions using this API must preserve the behavior +of the unaltered function! diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 8cac713789fde2..585b3775b60cc0 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5359,7 +5359,7 @@ test_pyobject_vectorcall(PyObject *self, PyObject *args) static PyObject * override_vectorcall( PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames) { - Py_RETURN_NONE; + return PyUnicode_FromString("overridden"); } static PyObject * From 99e4085e7e40afb733fdfad0f55e4df29cb82ec6 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Mon, 5 Sep 2022 18:48:03 -0700 Subject: [PATCH 12/21] Add deopt on func version in LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN --- Include/cpython/funcobject.h | 3 ++- Python/ceval.c | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h index df5cb9bdc3d51b..dd8f20b2c20b39 100644 --- a/Include/cpython/funcobject.h +++ b/Include/cpython/funcobject.h @@ -48,7 +48,8 @@ typedef struct { * defaults * kwdefaults (only if the object changes, not the contents of the dict) * code - * annotations */ + * annotations + * vectorcall function pointer */ uint32_t func_version; /* Invariant: diff --git a/Python/ceval.c b/Python/ceval.c index 7d0fff8cb3a344..79d2b9d2d7a7dc 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3135,8 +3135,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int PyObject *getattribute = read_obj(cache->descr); assert(Py_IS_TYPE(getattribute, &PyFunction_Type)); PyFunctionObject *f = (PyFunctionObject *)getattribute; + uint32_t func_version = read_u32(cache->keys_version); + DEOPT_IF(f->func_version != func_version, LOAD_ATTR); PyCodeObject *code = (PyCodeObject *)f->func_code; - DEOPT_IF(((PyCodeObject *)f->func_code)->co_argcount != 2, LOAD_ATTR); + DEOPT_IF(code->co_argcount != 2, LOAD_ATTR); DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL); STAT_INC(LOAD_ATTR, hit); From 24353c8c5dbfda9541974eb227476f2568ded904 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 6 Sep 2022 11:36:42 -0700 Subject: [PATCH 13/21] write func version to cache keys version when specializing LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN --- Python/ceval.c | 1 + Python/specialize.c | 1 + 2 files changed, 2 insertions(+) diff --git a/Python/ceval.c b/Python/ceval.c index 79d2b9d2d7a7dc..fd9544838861d4 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3136,6 +3136,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int assert(Py_IS_TYPE(getattribute, &PyFunction_Type)); PyFunctionObject *f = (PyFunctionObject *)getattribute; uint32_t func_version = read_u32(cache->keys_version); + assert(func_version != 0); DEOPT_IF(f->func_version != func_version, LOAD_ATTR); PyCodeObject *code = (PyCodeObject *)f->func_code; DEOPT_IF(code->co_argcount != 2, LOAD_ATTR); diff --git a/Python/specialize.c b/Python/specialize.c index e8c3f468feaa47..f06abcb2f27b91 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -836,6 +836,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name) if (func_version == 0) { goto fail; } + write_u32(lm_cache->keys_version, func_version); /* borrowed */ write_obj(lm_cache->descr, descr); write_u32(lm_cache->type_version, type->tp_version_tag); From 60f7769332bb3f1c35f772788b2bbe11c378db59 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 6 Sep 2022 12:58:03 -0700 Subject: [PATCH 14/21] remove redundant argcount check in LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN (replace with assert) - func version check is sufficient --- Python/ceval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index fd9544838861d4..6ee49d74d45a3c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3139,7 +3139,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int assert(func_version != 0); DEOPT_IF(f->func_version != func_version, LOAD_ATTR); PyCodeObject *code = (PyCodeObject *)f->func_code; - DEOPT_IF(code->co_argcount != 2, LOAD_ATTR); + assert(code->co_argcount == 2); DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL); STAT_INC(LOAD_ATTR, hit); From 376ee75144358feba05d6a99f41aa0333ad3fa0e Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 6 Sep 2022 12:59:38 -0700 Subject: [PATCH 15/21] Add missing periods in docs --- Doc/c-api/function.rst | 2 +- .../2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index 5c4e87f8b887dc..aba3bb90989cda 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -85,7 +85,7 @@ There are a few functions specific to Python functions. .. c:function:: void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall) - Set the vectorcall field of a given function object *func* + Set the vectorcall field of a given function object *func*. .. versionadded:: 3.12 diff --git a/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst index e87c7ede7c4c57..e0755bb0199491 100644 --- a/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst +++ b/Misc/NEWS.d/next/C API/2022-05-03-19-35-37.gh-issue-92193.61VoFL.rst @@ -1,5 +1,5 @@ -Add new function :c:func:`PyFunction_SetVectorcall` to the C API -which sets the vectorcall field of a given :c:type:`PyFunctionObject` - -Warning: extensions using this API must preserve the behavior -of the unaltered function! +Add new function :c:func:`PyFunction_SetVectorcall` to the C API +which sets the vectorcall field of a given :c:type:`PyFunctionObject`. + +Warning: extensions using this API must preserve the behavior +of the unaltered function! From 56ffc708b0ad29bdbd16be7694cd58203978bf81 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 6 Sep 2022 13:11:21 -0700 Subject: [PATCH 16/21] move warning comment to the function C API docs --- Doc/c-api/function.rst | 3 +++ Doc/whatsnew/3.12.rst | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst index aba3bb90989cda..df88e85e518829 100644 --- a/Doc/c-api/function.rst +++ b/Doc/c-api/function.rst @@ -87,6 +87,9 @@ There are a few functions specific to Python functions. Set the vectorcall field of a given function object *func*. + Warning: extensions using this API must preserve the behavior + of the unaltered (default) vectorcall function! + .. versionadded:: 3.12 .. c:function:: PyObject* PyFunction_GetClosure(PyObject *op) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 83c4e88a263876..5a1d85f9a479f9 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -472,8 +472,6 @@ New Features * Added new function :c:func:`PyFunction_SetVectorcall` to the C API which sets the vectorcall field of a given :c:type:`PyFunctionObject`. - Warning: extensions using this API must preserve the behavior - of the unaltered function! (Contributed by Andrew Frost in :gh:`92257`.) Porting to Python 3.12 From a5e9d13f49474e73f3f06b009efb551e156724fc Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Tue, 6 Sep 2022 13:20:23 -0700 Subject: [PATCH 17/21] fix race with GH-96519 (removed func_version in LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization) --- Python/specialize.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Python/specialize.c b/Python/specialize.c index a5021c635c804d..20841da3996449 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -836,7 +836,11 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name) if (!function_check_args(descr, 2, LOAD_ATTR)) { goto fail; } - write_u32(lm_cache->keys_version, func_version); + uint32_t version = function_get_version(descr, LOAD_ATTR); + if (version == 0) { + goto fail; + } + write_u32(lm_cache->keys_version, version); /* borrowed */ write_obj(lm_cache->descr, descr); write_u32(lm_cache->type_version, type->tp_version_tag); From 12faf525dd33480352da5543ffcd60e5ff0974d0 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Wed, 7 Sep 2022 09:27:55 -0700 Subject: [PATCH 18/21] PEP-7 formatting --- Python/ceval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/ceval.c b/Python/ceval.c index 7474680837e42e..5785022724f520 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4143,7 +4143,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int // Check if the call can be inlined or not if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL && - ((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall) { + ((PyFunctionObject *)function)->vectorcall == _PyFunction_Vectorcall) + { int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags; PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(function)); STACK_SHRINK(total_args); From 6623470968eed5863e6d78c18c4138670704ec56 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Wed, 7 Sep 2022 13:26:28 -0700 Subject: [PATCH 19/21] Address review feedback - PEP-7 braces - add assert - raise TypeError --- Modules/_testcapi/vectorcall.c | 7 ++++--- Objects/funcobject.c | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Modules/_testcapi/vectorcall.c b/Modules/_testcapi/vectorcall.c index a84f76ec9ec28a..e9c863a7570458 100644 --- a/Modules/_testcapi/vectorcall.c +++ b/Modules/_testcapi/vectorcall.c @@ -103,8 +103,9 @@ test_pyobject_vectorcall(PyObject *self, PyObject *args) } static PyObject * -override_vectorcall( - PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames) { +override_vectorcall(PyObject *callable, PyObject *const *args, size_t nargsf, + PyObject *kwnames) +{ return PyUnicode_FromString("overridden"); } @@ -112,7 +113,7 @@ static PyObject * function_setvectorcall(PyObject *self, PyObject *func) { if (!PyFunction_Check(func)) { - PyErr_BadInternalCall(); + PyErr_SetString(PyExc_TypeError, "'func' must be a function"); return NULL; } PyFunction_SetVectorcall((PyFunctionObject *)func, (vectorcallfunc)override_vectorcall); diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 06cdc4f7dfb957..7f257a9986987b 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -215,6 +215,7 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults) void PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall) { + assert(func != NULL); func->func_version = 0; func->vectorcall = vectorcall; } From 9149f14fb81274d8d7dae8f3db5a90a0089f7486 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Thu, 8 Sep 2022 00:25:54 -0700 Subject: [PATCH 20/21] Add test for LOAD_ATTR specialization when overriding vectorcall of __getattribute__ --- Lib/test/test_call.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 918fedad3ae682..86eef1d3913755 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -770,6 +770,22 @@ def f(num): return num + 1 for _ in range(51): self.assertEqual("overridden", f(num)) + def test_setvectorcall_load_attr_specialization(self): + from _testcapi import function_setvectorcall + + class X: + def __getattribute__(self, attr): + return attr + + x = X() + # trigger LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization + for _ in range(8): + self.assertEqual("a", x.a) + function_setvectorcall(X.__getattribute__) + # make sure specialization doesn't override the override + for _ in range(8): + self.assertEqual("overridden", x.a) + @requires_limited_api def test_vectorcall_limited(self): from _testcapi import pyobject_vectorcall From e732d7e6e3a6c1174537835f831e801bab34329a Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Thu, 8 Sep 2022 09:30:35 -0700 Subject: [PATCH 21/21] Improve setvectorcall + specialization testing 1. test specialization doesn't trigger when vectorcall is already overridden 2. test specialization gets deopted when vectorcall is overridden after specialization triggered --- Lib/test/test_call.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 86eef1d3913755..c1a386228ff0d0 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -580,6 +580,9 @@ def testfunction_kw(self, *, kw): return self +QUICKENING_WARMUP_DELAY = 8 + + class TestPEP590(unittest.TestCase): def test_method_descriptor_flag(self): @@ -763,28 +766,50 @@ def __call__(self, *args): def test_setvectorcall(self): from _testcapi import function_setvectorcall def f(num): return num + 1 + assert_equal = self.assertEqual num = 10 - self.assertEqual(11, f(num)) + assert_equal(11, f(num)) function_setvectorcall(f) # make sure specializer is triggered by running > 50 times - for _ in range(51): - self.assertEqual("overridden", f(num)) + for _ in range(10 * QUICKENING_WARMUP_DELAY): + assert_equal("overridden", f(num)) + + def test_setvectorcall_load_attr_specialization_skip(self): + from _testcapi import function_setvectorcall + + class X: + def __getattribute__(self, attr): + return attr + + assert_equal = self.assertEqual + x = X() + assert_equal("a", x.a) + function_setvectorcall(X.__getattribute__) + # make sure specialization doesn't trigger + # when vectorcall is overridden + for _ in range(QUICKENING_WARMUP_DELAY): + assert_equal("overridden", x.a) - def test_setvectorcall_load_attr_specialization(self): + def test_setvectorcall_load_attr_specialization_deopt(self): from _testcapi import function_setvectorcall class X: def __getattribute__(self, attr): return attr + def get_a(x): + return x.a + + assert_equal = self.assertEqual x = X() # trigger LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN specialization - for _ in range(8): - self.assertEqual("a", x.a) + for _ in range(QUICKENING_WARMUP_DELAY): + assert_equal("a", get_a(x)) function_setvectorcall(X.__getattribute__) - # make sure specialization doesn't override the override - for _ in range(8): - self.assertEqual("overridden", x.a) + # make sure specialized LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN + # gets deopted due to overridden vectorcall + for _ in range(QUICKENING_WARMUP_DELAY): + assert_equal("overridden", get_a(x)) @requires_limited_api def test_vectorcall_limited(self):