Skip to content

Commit 7276ca2

Browse files
GH-93911: Specialize LOAD_ATTR for custom __getattribute__ (GH-93988)
1 parent 3651710 commit 7276ca2

11 files changed

+219
-86
lines changed

Include/internal/pycore_code.h

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct callable_cache {
118118
PyObject *isinstance;
119119
PyObject *len;
120120
PyObject *list_append;
121+
PyObject *object__getattribute__;
121122
};
122123

123124
/* "Locals plus" for a code object is the set of locals + cell vars +

Include/internal/pycore_opcode.h

+12-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_typeobject.h

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ extern void _PyStaticType_ClearWeakRefs(PyTypeObject *type);
7575
extern void _PyStaticType_Dealloc(PyTypeObject *type);
7676

7777

78+
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
79+
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);
80+
7881
#ifdef __cplusplus
7982
}
8083
#endif

Include/opcode.h

+30-29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/opcode.py

+1
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ def pseudo_op(name, op, real_ops):
338338
"LOAD_ATTR_ADAPTIVE",
339339
# These potentially push [NULL, bound method] onto the stack.
340340
"LOAD_ATTR_CLASS",
341+
"LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN",
341342
"LOAD_ATTR_INSTANCE_VALUE",
342343
"LOAD_ATTR_MODULE",
343344
"LOAD_ATTR_PROPERTY",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Specialize ``LOAD_ATTR`` for objects with custom ``__getattr__`` and ``__getattribute__``.

Objects/typeobject.c

+13-13
Original file line numberDiff line numberDiff line change
@@ -8102,17 +8102,17 @@ slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds)
81028102

81038103
/* There are two slot dispatch functions for tp_getattro.
81048104
8105-
- slot_tp_getattro() is used when __getattribute__ is overridden
8105+
- _Py_slot_tp_getattro() is used when __getattribute__ is overridden
81068106
but no __getattr__ hook is present;
81078107
8108-
- slot_tp_getattr_hook() is used when a __getattr__ hook is present.
8108+
- _Py_slot_tp_getattr_hook() is used when a __getattr__ hook is present.
81098109
8110-
The code in update_one_slot() always installs slot_tp_getattr_hook(); this
8111-
detects the absence of __getattr__ and then installs the simpler slot if
8112-
necessary. */
8110+
The code in update_one_slot() always installs _Py_slot_tp_getattr_hook();
8111+
this detects the absence of __getattr__ and then installs the simpler
8112+
slot if necessary. */
81138113

8114-
static PyObject *
8115-
slot_tp_getattro(PyObject *self, PyObject *name)
8114+
PyObject *
8115+
_Py_slot_tp_getattro(PyObject *self, PyObject *name)
81168116
{
81178117
PyObject *stack[2] = {self, name};
81188118
return vectorcall_method(&_Py_ID(__getattribute__), stack, 2);
@@ -8143,8 +8143,8 @@ call_attribute(PyObject *self, PyObject *attr, PyObject *name)
81438143
return res;
81448144
}
81458145

8146-
static PyObject *
8147-
slot_tp_getattr_hook(PyObject *self, PyObject *name)
8146+
PyObject *
8147+
_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name)
81488148
{
81498149
PyTypeObject *tp = Py_TYPE(self);
81508150
PyObject *getattr, *getattribute, *res;
@@ -8157,8 +8157,8 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name)
81578157
getattr = _PyType_Lookup(tp, &_Py_ID(__getattr__));
81588158
if (getattr == NULL) {
81598159
/* No __getattr__ hook: use a simpler dispatcher */
8160-
tp->tp_getattro = slot_tp_getattro;
8161-
return slot_tp_getattro(self, name);
8160+
tp->tp_getattro = _Py_slot_tp_getattro;
8161+
return _Py_slot_tp_getattro(self, name);
81628162
}
81638163
Py_INCREF(getattr);
81648164
/* speed hack: we could use lookup_maybe, but that would resolve the
@@ -8519,10 +8519,10 @@ static slotdef slotdefs[] = {
85198519
PyWrapperFlag_KEYWORDS),
85208520
TPSLOT("__str__", tp_str, slot_tp_str, wrap_unaryfunc,
85218521
"__str__($self, /)\n--\n\nReturn str(self)."),
8522-
TPSLOT("__getattribute__", tp_getattro, slot_tp_getattr_hook,
8522+
TPSLOT("__getattribute__", tp_getattro, _Py_slot_tp_getattr_hook,
85238523
wrap_binaryfunc,
85248524
"__getattribute__($self, name, /)\n--\n\nReturn getattr(self, name)."),
8525-
TPSLOT("__getattr__", tp_getattro, slot_tp_getattr_hook, NULL, ""),
8525+
TPSLOT("__getattr__", tp_getattro, _Py_slot_tp_getattr_hook, NULL, ""),
85268526
TPSLOT("__setattr__", tp_setattro, slot_tp_setattro, wrap_setattr,
85278527
"__setattr__($self, name, value, /)\n--\n\nImplement setattr(self, name, value)."),
85288528
TPSLOT("__delattr__", tp_setattro, slot_tp_setattro, wrap_delattr,

Python/ceval.c

+41-2
Original file line numberDiff line numberDiff line change
@@ -3698,6 +3698,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
36983698
DEOPT_IF(cls->tp_version_tag != type_version, LOAD_ATTR);
36993699
assert(type_version != 0);
37003700
PyObject *fget = read_obj(cache->descr);
3701+
assert(Py_IS_TYPE(fget, &PyFunction_Type));
37013702
PyFunctionObject *f = (PyFunctionObject *)fget;
37023703
uint32_t func_version = read_u32(cache->keys_version);
37033704
assert(func_version != 0);
@@ -3709,8 +3710,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
37093710
Py_INCREF(fget);
37103711
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
37113712
SET_TOP(NULL);
3712-
int push_null = !(oparg & 1);
3713-
STACK_SHRINK(push_null);
3713+
int shrink_stack = !(oparg & 1);
3714+
STACK_SHRINK(shrink_stack);
37143715
new_frame->localsplus[0] = owner;
37153716
for (int i = 1; i < code->co_nlocalsplus; i++) {
37163717
new_frame->localsplus[i] = NULL;
@@ -3724,6 +3725,44 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
37243725
goto start_frame;
37253726
}
37263727

3728+
TARGET(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN) {
3729+
assert(cframe.use_tracing == 0);
3730+
DEOPT_IF(tstate->interp->eval_frame, LOAD_ATTR);
3731+
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)next_instr;
3732+
PyObject *owner = TOP();
3733+
PyTypeObject *cls = Py_TYPE(owner);
3734+
uint32_t type_version = read_u32(cache->type_version);
3735+
DEOPT_IF(cls->tp_version_tag != type_version, LOAD_ATTR);
3736+
assert(type_version != 0);
3737+
PyObject *getattribute = read_obj(cache->descr);
3738+
assert(Py_IS_TYPE(getattribute, &PyFunction_Type));
3739+
PyFunctionObject *f = (PyFunctionObject *)getattribute;
3740+
PyCodeObject *code = (PyCodeObject *)f->func_code;
3741+
DEOPT_IF(((PyCodeObject *)f->func_code)->co_argcount != 2, LOAD_ATTR);
3742+
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
3743+
STAT_INC(LOAD_ATTR, hit);
3744+
3745+
PyObject *name = GETITEM(names, oparg >> 1);
3746+
Py_INCREF(f);
3747+
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
3748+
SET_TOP(NULL);
3749+
int shrink_stack = !(oparg & 1);
3750+
STACK_SHRINK(shrink_stack);
3751+
Py_INCREF(name);
3752+
new_frame->localsplus[0] = owner;
3753+
new_frame->localsplus[1] = name;
3754+
for (int i = 2; i < code->co_nlocalsplus; i++) {
3755+
new_frame->localsplus[i] = NULL;
3756+
}
3757+
_PyFrame_SetStackPointer(frame, stack_pointer);
3758+
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
3759+
frame->prev_instr = next_instr - 1;
3760+
new_frame->previous = frame;
3761+
frame = cframe.current_frame = new_frame;
3762+
CALL_STAT_INC(inlined_py_calls);
3763+
goto start_frame;
3764+
}
3765+
37273766
TARGET(STORE_ATTR_ADAPTIVE) {
37283767
assert(cframe.use_tracing == 0);
37293768
_PyAttrCache *cache = (_PyAttrCache *)next_instr;

0 commit comments

Comments
 (0)