Skip to content

gh-93911: Specialize LOAD_ATTR for custom __getattr__ and __getattribute__ #93988

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ struct callable_cache {
PyObject *isinstance;
PyObject *len;
PyObject *list_append;
PyObject *object__getattribute__;
};

/* "Locals plus" for a code object is the set of locals + cell vars +
Expand Down
24 changes: 12 additions & 12 deletions Include/internal/pycore_opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ extern void _PyStaticType_ClearWeakRefs(PyTypeObject *type);
extern void _PyStaticType_Dealloc(PyTypeObject *type);


PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);

#ifdef __cplusplus
}
#endif
Expand Down
59 changes: 30 additions & 29 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ def pseudo_op(name, op, real_ops):
"LOAD_ATTR_ADAPTIVE",
# These potentially push [NULL, bound method] onto the stack.
"LOAD_ATTR_CLASS",
"LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN",
"LOAD_ATTR_INSTANCE_VALUE",
"LOAD_ATTR_MODULE",
"LOAD_ATTR_PROPERTY",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Specialize ``LOAD_ATTR`` for objects with custom ``__getattr__`` and ``__getattribute__``.
26 changes: 13 additions & 13 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8053,17 +8053,17 @@ slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds)

/* There are two slot dispatch functions for tp_getattro.

- slot_tp_getattro() is used when __getattribute__ is overridden
- _Py_slot_tp_getattro() is used when __getattribute__ is overridden
but no __getattr__ hook is present;

- slot_tp_getattr_hook() is used when a __getattr__ hook is present.
- _Py_slot_tp_getattr_hook() is used when a __getattr__ hook is present.

The code in update_one_slot() always installs slot_tp_getattr_hook(); this
detects the absence of __getattr__ and then installs the simpler slot if
necessary. */
The code in update_one_slot() always installs _Py_slot_tp_getattr_hook();
this detects the absence of __getattr__ and then installs the simpler
slot if necessary. */

static PyObject *
slot_tp_getattro(PyObject *self, PyObject *name)
PyObject *
_Py_slot_tp_getattro(PyObject *self, PyObject *name)
{
PyObject *stack[2] = {self, name};
return vectorcall_method(&_Py_ID(__getattribute__), stack, 2);
Expand Down Expand Up @@ -8094,8 +8094,8 @@ call_attribute(PyObject *self, PyObject *attr, PyObject *name)
return res;
}

static PyObject *
slot_tp_getattr_hook(PyObject *self, PyObject *name)
PyObject *
_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(self);
PyObject *getattr, *getattribute, *res;
Expand All @@ -8108,8 +8108,8 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name)
getattr = _PyType_Lookup(tp, &_Py_ID(__getattr__));
if (getattr == NULL) {
/* No __getattr__ hook: use a simpler dispatcher */
tp->tp_getattro = slot_tp_getattro;
return slot_tp_getattro(self, name);
tp->tp_getattro = _Py_slot_tp_getattro;
return _Py_slot_tp_getattro(self, name);
}
Py_INCREF(getattr);
/* speed hack: we could use lookup_maybe, but that would resolve the
Expand Down Expand Up @@ -8469,10 +8469,10 @@ static slotdef slotdefs[] = {
PyWrapperFlag_KEYWORDS),
TPSLOT("__str__", tp_str, slot_tp_str, wrap_unaryfunc,
"__str__($self, /)\n--\n\nReturn str(self)."),
TPSLOT("__getattribute__", tp_getattro, slot_tp_getattr_hook,
TPSLOT("__getattribute__", tp_getattro, _Py_slot_tp_getattr_hook,
wrap_binaryfunc,
"__getattribute__($self, name, /)\n--\n\nReturn getattr(self, name)."),
TPSLOT("__getattr__", tp_getattro, slot_tp_getattr_hook, NULL, ""),
TPSLOT("__getattr__", tp_getattro, _Py_slot_tp_getattr_hook, NULL, ""),
TPSLOT("__setattr__", tp_setattro, slot_tp_setattro, wrap_setattr,
"__setattr__($self, name, value, /)\n--\n\nImplement setattr(self, name, value)."),
TPSLOT("__delattr__", tp_setattro, slot_tp_setattro, wrap_delattr,
Expand Down
43 changes: 41 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3698,6 +3698,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
DEOPT_IF(cls->tp_version_tag != type_version, LOAD_ATTR);
assert(type_version != 0);
PyObject *fget = read_obj(cache->descr);
assert(Py_IS_TYPE(fget, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)fget;
uint32_t func_version = read_u32(cache->keys_version);
assert(func_version != 0);
Expand All @@ -3709,8 +3710,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
Py_INCREF(fget);
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
SET_TOP(NULL);
int push_null = !(oparg & 1);
STACK_SHRINK(push_null);
int shrink_stack = !(oparg & 1);
STACK_SHRINK(shrink_stack);
new_frame->localsplus[0] = owner;
for (int i = 1; i < code->co_nlocalsplus; i++) {
new_frame->localsplus[i] = NULL;
Expand All @@ -3724,6 +3725,44 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
goto start_frame;
}

TARGET(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN) {
assert(cframe.use_tracing == 0);
DEOPT_IF(tstate->interp->eval_frame, LOAD_ATTR);
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)next_instr;
PyObject *owner = TOP();
PyTypeObject *cls = Py_TYPE(owner);
uint32_t type_version = read_u32(cache->type_version);
DEOPT_IF(cls->tp_version_tag != type_version, LOAD_ATTR);
assert(type_version != 0);
PyObject *getattribute = read_obj(cache->descr);
assert(Py_IS_TYPE(getattribute, &PyFunction_Type));
PyFunctionObject *f = (PyFunctionObject *)getattribute;
PyCodeObject *code = (PyCodeObject *)f->func_code;
DEOPT_IF(((PyCodeObject *)f->func_code)->co_argcount != 2, LOAD_ATTR);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
STAT_INC(LOAD_ATTR, hit);

PyObject *name = GETITEM(names, oparg >> 1);
Py_INCREF(f);
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
SET_TOP(NULL);
int shrink_stack = !(oparg & 1);
STACK_SHRINK(shrink_stack);
Py_INCREF(name);
new_frame->localsplus[0] = owner;
new_frame->localsplus[1] = name;
for (int i = 2; i < code->co_nlocalsplus; i++) {
new_frame->localsplus[i] = NULL;
}
_PyFrame_SetStackPointer(frame, stack_pointer);
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR);
frame->prev_instr = next_instr - 1;
new_frame->previous = frame;
frame = cframe.current_frame = new_frame;
CALL_STAT_INC(inlined_py_calls);
goto start_frame;
}

TARGET(STORE_ATTR_ADAPTIVE) {
assert(cframe.use_tracing == 0);
_PyAttrCache *cache = (_PyAttrCache *)next_instr;
Expand Down
Loading