From 7d388eb0f89b92a51dc0d5a562920279879126a9 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Wed, 14 May 2025 12:12:12 +0530 Subject: [PATCH 1/7] add _PyObject_GetMethodStackRef --- Include/internal/pycore_object.h | 3 + Objects/call.c | 10 +-- Objects/object.c | 112 +++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index b7e162c8abcabf..51aa10806c129e 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -897,6 +897,9 @@ extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *, extern unsigned int _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out); +extern int _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, + PyObject *name, _PyStackRef *method); + // Cache the provided init method in the specialization cache of type if the // provided type version matches the current version of the type. // diff --git a/Objects/call.c b/Objects/call.c index b1610dababd466..551bcb71ecba46 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -834,12 +834,14 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, assert(PyVectorcall_NARGS(nargsf) >= 1); PyThreadState *tstate = _PyThreadState_GET(); - PyObject *callable = NULL; + _PyCStackRef method; + _PyThreadState_PushCStackRef(tstate, &method); /* Use args[0] as "self" argument */ - int unbound = _PyObject_GetMethod(args[0], name, &callable); - if (callable == NULL) { + int unbound = _PyObject_GetMethodStackRef(tstate, args[0], name, &method.ref); + if (PyStackRef_IsNull(method.ref)) { return NULL; } + PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref); if (unbound) { /* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since @@ -855,7 +857,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable); PyObject *result = _PyObject_VectorcallTstate(tstate, callable, args, nargsf, kwnames); - Py_DECREF(callable); + _PyThreadState_PopCStackRef(tstate, &method); return result; } diff --git a/Objects/object.c b/Objects/object.c index 723b0427e69251..96d5bbd13aef4b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1664,6 +1664,118 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) return 0; } +int +_PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, + PyObject *name, _PyStackRef *method) +{ + int meth_found = 0; + + int ret = 0; + *method = PyStackRef_NULL; + + PyTypeObject *tp = Py_TYPE(obj); + if (!_PyType_IsReady(tp)) { + if (PyType_Ready(tp) < 0) { + return 0; + } + } + + if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) { + PyObject *res = PyObject_GetAttr(obj, name); + if (res != NULL) { + *method = PyStackRef_FromPyObjectSteal(res); + } + return 0; + } + + _PyType_LookupStackRefAndVersion(tp, name, method); + PyObject *descr = PyStackRef_AsPyObjectBorrow(*method); + descrgetfunc f = NULL; + if (descr != NULL) { + if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) { + meth_found = 1; + } + else { + f = Py_TYPE(descr)->tp_descr_get; + if (f != NULL && PyDescr_IsData(descr)) { + PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj)); + PyStackRef_CLEAR(*method); + if (value != NULL) { + *method = PyStackRef_FromPyObjectSteal(value); + } + goto exit; + } + } + } + PyObject *dict, *attr; + if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && + _PyObject_TryGetInstanceAttribute(obj, name, &attr)) { + if (attr != NULL) { + PyStackRef_CLEAR(*method); + *method = PyStackRef_FromPyObjectSteal(attr); + goto exit; + } + dict = NULL; + } + else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) { + dict = (PyObject *)_PyObject_GetManagedDict(obj); + } + else { + PyObject **dictptr = _PyObject_ComputedDictPointer(obj); + if (dictptr != NULL) { + dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr); + } + else { + dict = NULL; + } + } + if (dict != NULL) { + Py_INCREF(dict); + PyObject *value; + if (PyDict_GetItemRef(dict, name, &value) != 0) { + // found or error + Py_DECREF(dict); + PyStackRef_CLEAR(*method); + if (value != NULL) { + *method = PyStackRef_FromPyObjectSteal(value); + } + goto exit; + } + // not found + Py_DECREF(dict); + } + + if (meth_found) { + ret = 1; + assert(!PyStackRef_IsNull(*method)); + goto exit; + } + + if (f != NULL) { + PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj)); + PyStackRef_CLEAR(*method); + if (value) { + *method = PyStackRef_FromPyObjectSteal(value); + } + goto exit; + } + + if (descr != NULL) { + assert(!PyStackRef_IsNull(*method)); + goto exit; + } + + PyErr_Format(PyExc_AttributeError, + "'%.100s' object has no attribute '%U'", + tp->tp_name, name); + + _PyObject_SetAttributeErrorContext(obj, name); + PyStackRef_CLEAR(*method); +exit: + return ret; +} + + /* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */ PyObject * From b09f23f4af9f5177436fc4010346695088d43908 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 15 May 2025 16:52:30 +0530 Subject: [PATCH 2/7] fix assert --- Objects/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 96d5bbd13aef4b..443bb0af947c57 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1671,7 +1671,7 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, int meth_found = 0; int ret = 0; - *method = PyStackRef_NULL; + assert(PyStackRef_IsNull(*method)); PyTypeObject *tp = Py_TYPE(obj); if (!_PyType_IsReady(tp)) { From 40da794baac3c8cace67c215ea285308074da59e Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 15 May 2025 17:01:13 +0530 Subject: [PATCH 3/7] add todo --- Objects/object.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/object.c b/Objects/object.c index 443bb0af947c57..be062d2b6b7ea2 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1730,6 +1730,7 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, } } if (dict != NULL) { + // TODO: use _Py_dict_lookup_threadsafe_stackref Py_INCREF(dict); PyObject *value; if (PyDict_GetItemRef(dict, name, &value) != 0) { From de82e900a8aee093967168c67b332a68dec9ab6e Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 15 May 2025 17:35:58 +0530 Subject: [PATCH 4/7] pop stackref when error --- Objects/call.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/call.c b/Objects/call.c index 551bcb71ecba46..0f0dd0a309f87a 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -839,6 +839,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args, /* Use args[0] as "self" argument */ int unbound = _PyObject_GetMethodStackRef(tstate, args[0], name, &method.ref); if (PyStackRef_IsNull(method.ref)) { + _PyThreadState_PopCStackRef(tstate, &method); return NULL; } PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref); From f9d2f2842c3e68de4feb7d94eb723cf1f667f532 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 23 May 2025 16:29:09 +0530 Subject: [PATCH 5/7] cleanup --- Objects/call.c | 24 +++++++++++++++--------- Objects/object.c | 19 ++++++++----------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Objects/call.c b/Objects/call.c index 0f0dd0a309f87a..c9a18bcc3da60b 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -871,11 +871,14 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...) return null_error(tstate); } - PyObject *callable = NULL; - int is_method = _PyObject_GetMethod(obj, name, &callable); - if (callable == NULL) { + _PyCStackRef method; + _PyThreadState_PushCStackRef(tstate, &method); + int is_method = _PyObject_GetMethodStackRef(tstate, obj, name, &method.ref); + if (PyStackRef_IsNull(method.ref)) { + _PyThreadState_PopCStackRef(tstate, &method); return NULL; } + PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref); obj = is_method ? obj : NULL; va_list vargs; @@ -883,7 +886,7 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...) PyObject *result = object_vacall(tstate, obj, callable, vargs); va_end(vargs); - Py_DECREF(callable); + _PyThreadState_PopCStackRef(tstate, &method); return result; } @@ -900,12 +903,15 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...) if (!oname) { return NULL; } - - PyObject *callable = NULL; - int is_method = _PyObject_GetMethod(obj, oname, &callable); - if (callable == NULL) { + _PyCStackRef method; + _PyThreadState_PushCStackRef(tstate, &method); + int is_method = _PyObject_GetMethodStackRef(tstate, obj, oname, &method.ref); + if (PyStackRef_IsNull(method.ref)) { + _PyThreadState_PopCStackRef(tstate, &method); return NULL; } + PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref); + obj = is_method ? obj : NULL; va_list vargs; @@ -913,7 +919,7 @@ _PyObject_CallMethodIdObjArgs(PyObject *obj, _Py_Identifier *name, ...) PyObject *result = object_vacall(tstate, obj, callable, vargs); va_end(vargs); - Py_DECREF(callable); + _PyThreadState_PopCStackRef(tstate, &method); return result; } diff --git a/Objects/object.c b/Objects/object.c index 342c83986aa0d9..68c8bfeae33e33 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1670,7 +1670,6 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, { int meth_found = 0; - int ret = 0; assert(PyStackRef_IsNull(*method)); PyTypeObject *tp = Py_TYPE(obj); @@ -1703,7 +1702,7 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, if (value != NULL) { *method = PyStackRef_FromPyObjectSteal(value); } - goto exit; + return 0; } } } @@ -1713,7 +1712,7 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, if (attr != NULL) { PyStackRef_CLEAR(*method); *method = PyStackRef_FromPyObjectSteal(attr); - goto exit; + return 0; } dict = NULL; } @@ -1740,16 +1739,15 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, if (value != NULL) { *method = PyStackRef_FromPyObjectSteal(value); } - goto exit; + return 0; } // not found Py_DECREF(dict); } if (meth_found) { - ret = 1; assert(!PyStackRef_IsNull(*method)); - goto exit; + return 1; } if (f != NULL) { @@ -1758,12 +1756,12 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, if (value) { *method = PyStackRef_FromPyObjectSteal(value); } - goto exit; + return 0; } if (descr != NULL) { assert(!PyStackRef_IsNull(*method)); - goto exit; + return 0; } PyErr_Format(PyExc_AttributeError, @@ -1771,9 +1769,8 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, tp->tp_name, name); _PyObject_SetAttributeErrorContext(obj, name); - PyStackRef_CLEAR(*method); -exit: - return ret; + assert(PyStackRef_IsNull(*method)); + return 0; } From 1d0a0b904837e5d30cb651ed357062a9f068561d Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 23 May 2025 17:05:27 +0530 Subject: [PATCH 6/7] add method_caller benchmark to ftscalingbench.py --- Tools/ftscalingbench/ftscalingbench.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tools/ftscalingbench/ftscalingbench.py b/Tools/ftscalingbench/ftscalingbench.py index 926bc66b944c6f..1a59e25189d5dd 100644 --- a/Tools/ftscalingbench/ftscalingbench.py +++ b/Tools/ftscalingbench/ftscalingbench.py @@ -27,6 +27,7 @@ import sys import threading import time +from operator import methodcaller # The iterations in individual benchmarks are scaled by this factor. WORK_SCALE = 100 @@ -188,6 +189,18 @@ def thread_local_read(): _ = tmp.x _ = tmp.x +class MyClass: + __slots__ = () + + def func(self): + pass + +@register_benchmark +def method_caller(): + mc = methodcaller("func") + obj = MyClass() + for i in range(1000 * WORK_SCALE): + mc(obj) def bench_one_thread(func): t0 = time.perf_counter_ns() From 2b3c56524421ea95ab006a71af61850c59541c02 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 23 May 2025 17:06:37 +0530 Subject: [PATCH 7/7] fmt --- Include/internal/pycore_object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 51aa10806c129e..3aaee7d008155a 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -898,7 +898,7 @@ extern unsigned int _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out); extern int _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, - PyObject *name, _PyStackRef *method); + PyObject *name, _PyStackRef *method); // Cache the provided init method in the specialization cache of type if the // provided type version matches the current version of the type.