mirror of
https://github.com/python/cpython.git
synced 2025-10-28 01:00:34 +00:00
GH-93911: Specialize LOAD_ATTR for custom __getattribute__ (GH-93988)
This commit is contained in:
parent
36517101dd
commit
7276ca25f5
11 changed files with 219 additions and 86 deletions
|
|
@ -3698,6 +3698,7 @@ handle_eval_breaker:
|
|||
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);
|
||||
|
|
@ -3709,8 +3710,8 @@ handle_eval_breaker:
|
|||
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;
|
||||
|
|
@ -3724,6 +3725,44 @@ handle_eval_breaker:
|
|||
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;
|
||||
|
|
|
|||
22
Python/opcode_targets.h
generated
22
Python/opcode_targets.h
generated
|
|
@ -72,20 +72,20 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_PRINT_EXPR,
|
||||
&&TARGET_LOAD_BUILD_CLASS,
|
||||
&&TARGET_LOAD_ATTR_CLASS,
|
||||
&&TARGET_LOAD_ATTR_INSTANCE_VALUE,
|
||||
&&TARGET_LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN,
|
||||
&&TARGET_LOAD_ASSERTION_ERROR,
|
||||
&&TARGET_RETURN_GENERATOR,
|
||||
&&TARGET_LOAD_ATTR_INSTANCE_VALUE,
|
||||
&&TARGET_LOAD_ATTR_MODULE,
|
||||
&&TARGET_LOAD_ATTR_PROPERTY,
|
||||
&&TARGET_LOAD_ATTR_SLOT,
|
||||
&&TARGET_LOAD_ATTR_WITH_HINT,
|
||||
&&TARGET_LOAD_ATTR_METHOD_LAZY_DICT,
|
||||
&&TARGET_LOAD_ATTR_METHOD_NO_DICT,
|
||||
&&TARGET_LIST_TO_TUPLE,
|
||||
&&TARGET_RETURN_VALUE,
|
||||
&&TARGET_IMPORT_STAR,
|
||||
&&TARGET_SETUP_ANNOTATIONS,
|
||||
&&TARGET_LOAD_ATTR_METHOD_WITH_DICT,
|
||||
&&TARGET_LOAD_ATTR_METHOD_NO_DICT,
|
||||
&&TARGET_ASYNC_GEN_WRAP,
|
||||
&&TARGET_PREP_RERAISE_STAR,
|
||||
&&TARGET_POP_EXCEPT,
|
||||
|
|
@ -112,7 +112,7 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_JUMP_FORWARD,
|
||||
&&TARGET_JUMP_IF_FALSE_OR_POP,
|
||||
&&TARGET_JUMP_IF_TRUE_OR_POP,
|
||||
&&TARGET_LOAD_ATTR_METHOD_WITH_VALUES,
|
||||
&&TARGET_LOAD_ATTR_METHOD_WITH_DICT,
|
||||
&&TARGET_POP_JUMP_FORWARD_IF_FALSE,
|
||||
&&TARGET_POP_JUMP_FORWARD_IF_TRUE,
|
||||
&&TARGET_LOAD_GLOBAL,
|
||||
|
|
@ -120,7 +120,7 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_CONTAINS_OP,
|
||||
&&TARGET_RERAISE,
|
||||
&&TARGET_COPY,
|
||||
&&TARGET_LOAD_CONST__LOAD_FAST,
|
||||
&&TARGET_LOAD_ATTR_METHOD_WITH_VALUES,
|
||||
&&TARGET_BINARY_OP,
|
||||
&&TARGET_SEND,
|
||||
&&TARGET_LOAD_FAST,
|
||||
|
|
@ -140,9 +140,9 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_STORE_DEREF,
|
||||
&&TARGET_DELETE_DEREF,
|
||||
&&TARGET_JUMP_BACKWARD,
|
||||
&&TARGET_LOAD_FAST__LOAD_CONST,
|
||||
&&TARGET_LOAD_CONST__LOAD_FAST,
|
||||
&&TARGET_CALL_FUNCTION_EX,
|
||||
&&TARGET_LOAD_FAST__LOAD_FAST,
|
||||
&&TARGET_LOAD_FAST__LOAD_CONST,
|
||||
&&TARGET_EXTENDED_ARG,
|
||||
&&TARGET_LIST_APPEND,
|
||||
&&TARGET_SET_ADD,
|
||||
|
|
@ -152,30 +152,31 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_YIELD_VALUE,
|
||||
&&TARGET_RESUME,
|
||||
&&TARGET_MATCH_CLASS,
|
||||
&&TARGET_LOAD_FAST__LOAD_FAST,
|
||||
&&TARGET_LOAD_GLOBAL_ADAPTIVE,
|
||||
&&TARGET_LOAD_GLOBAL_BUILTIN,
|
||||
&&TARGET_FORMAT_VALUE,
|
||||
&&TARGET_BUILD_CONST_KEY_MAP,
|
||||
&&TARGET_BUILD_STRING,
|
||||
&&TARGET_LOAD_GLOBAL_BUILTIN,
|
||||
&&TARGET_LOAD_GLOBAL_MODULE,
|
||||
&&TARGET_RESUME_QUICK,
|
||||
&&TARGET_STORE_ATTR_ADAPTIVE,
|
||||
&&TARGET_STORE_ATTR_INSTANCE_VALUE,
|
||||
&&TARGET_LIST_EXTEND,
|
||||
&&TARGET_SET_UPDATE,
|
||||
&&TARGET_DICT_MERGE,
|
||||
&&TARGET_DICT_UPDATE,
|
||||
&&TARGET_STORE_ATTR_INSTANCE_VALUE,
|
||||
&&TARGET_STORE_ATTR_SLOT,
|
||||
&&TARGET_STORE_ATTR_WITH_HINT,
|
||||
&&TARGET_STORE_FAST__LOAD_FAST,
|
||||
&&TARGET_STORE_FAST__STORE_FAST,
|
||||
&&TARGET_STORE_SUBSCR_ADAPTIVE,
|
||||
&&TARGET_CALL,
|
||||
&&TARGET_KW_NAMES,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_NOT_NONE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_NONE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_FALSE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_TRUE,
|
||||
&&TARGET_STORE_SUBSCR_ADAPTIVE,
|
||||
&&TARGET_STORE_SUBSCR_DICT,
|
||||
&&TARGET_STORE_SUBSCR_LIST_INT,
|
||||
&&TARGET_UNPACK_SEQUENCE_ADAPTIVE,
|
||||
|
|
@ -253,6 +254,5 @@ static void *opcode_targets[256] = {
|
|||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&TARGET_DO_TRACING
|
||||
};
|
||||
|
|
|
|||
|
|
@ -787,6 +787,9 @@ pycore_init_builtins(PyThreadState *tstate)
|
|||
PyObject *list_append = _PyType_Lookup(&PyList_Type, &_Py_ID(append));
|
||||
assert(list_append);
|
||||
interp->callable_cache.list_append = list_append;
|
||||
PyObject *object__getattribute__ = _PyType_Lookup(&PyBaseObject_Type, &_Py_ID(__getattribute__));
|
||||
assert(object__getattribute__);
|
||||
interp->callable_cache.object__getattribute__ = object__getattribute__;
|
||||
|
||||
if (_PyBuiltins_AddExceptions(bimod) < 0) {
|
||||
return _PyStatus_ERR("failed to add exceptions to builtins");
|
||||
|
|
|
|||
|
|
@ -362,6 +362,8 @@ miss_counter_start(void) {
|
|||
#define SPEC_FAIL_OUT_OF_RANGE 4
|
||||
#define SPEC_FAIL_EXPECTED_ERROR 5
|
||||
#define SPEC_FAIL_WRONG_NUMBER_ARGUMENTS 6
|
||||
#define SPEC_FAIL_NOT_PY_FUNCTION 7
|
||||
|
||||
|
||||
#define SPEC_FAIL_LOAD_GLOBAL_NON_STRING_OR_SPLIT 18
|
||||
|
||||
|
|
@ -387,6 +389,7 @@ miss_counter_start(void) {
|
|||
#define SPEC_FAIL_ATTR_HAS_MANAGED_DICT 25
|
||||
#define SPEC_FAIL_ATTR_INSTANCE_ATTRIBUTE 26
|
||||
#define SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE 27
|
||||
#define SPEC_FAIL_ATTR_PROPERTY_NOT_PY_FUNCTION 28
|
||||
|
||||
/* Binary subscr and store subscr */
|
||||
|
||||
|
|
@ -498,6 +501,9 @@ miss_counter_start(void) {
|
|||
#define SPEC_FAIL_UNPACK_SEQUENCE_ITERATOR 8
|
||||
#define SPEC_FAIL_UNPACK_SEQUENCE_SEQUENCE 9
|
||||
|
||||
static int function_kind(PyCodeObject *code);
|
||||
static bool function_check_args(PyObject *o, int expected_argcount, int opcode);
|
||||
static uint32_t function_get_version(PyObject *o, int opcode);
|
||||
|
||||
static int
|
||||
specialize_module_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
|
||||
|
|
@ -557,13 +563,15 @@ typedef enum {
|
|||
MUTABLE, /* Instance of a mutable class; might, or might not, be a descriptor */
|
||||
ABSENT, /* Attribute is not present on the class */
|
||||
DUNDER_CLASS, /* __class__ attribute */
|
||||
GETSET_OVERRIDDEN /* __getattribute__ or __setattr__ has been overridden */
|
||||
GETSET_OVERRIDDEN, /* __getattribute__ or __setattr__ has been overridden */
|
||||
GETATTRIBUTE_IS_PYTHON_FUNCTION /* Descriptor requires calling a Python __getattribute__ */
|
||||
} DescriptorClassification;
|
||||
|
||||
|
||||
static DescriptorClassification
|
||||
analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int store)
|
||||
{
|
||||
bool has_getattr = false;
|
||||
if (store) {
|
||||
if (type->tp_setattro != PyObject_GenericSetAttr) {
|
||||
*descr = NULL;
|
||||
|
|
@ -571,7 +579,42 @@ analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int sto
|
|||
}
|
||||
}
|
||||
else {
|
||||
if (type->tp_getattro != PyObject_GenericGetAttr) {
|
||||
getattrofunc getattro_slot = type->tp_getattro;
|
||||
if (getattro_slot == PyObject_GenericGetAttr) {
|
||||
/* Normal attribute lookup; */
|
||||
has_getattr = false;
|
||||
}
|
||||
else if (getattro_slot == _Py_slot_tp_getattr_hook ||
|
||||
getattro_slot == _Py_slot_tp_getattro) {
|
||||
/* One or both of __getattribute__ or __getattr__ may have been
|
||||
overridden See typeobject.c for why these functions are special. */
|
||||
PyObject *getattribute = _PyType_Lookup(type,
|
||||
&_Py_ID(__getattribute__));
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
bool has_custom_getattribute = getattribute != NULL &&
|
||||
getattribute != interp->callable_cache.object__getattribute__;
|
||||
has_getattr = _PyType_Lookup(type, &_Py_ID(__getattr__)) != NULL;
|
||||
if (has_custom_getattribute) {
|
||||
if (getattro_slot == _Py_slot_tp_getattro &&
|
||||
!has_getattr &&
|
||||
Py_IS_TYPE(getattribute, &PyFunction_Type)) {
|
||||
*descr = getattribute;
|
||||
return GETATTRIBUTE_IS_PYTHON_FUNCTION;
|
||||
}
|
||||
/* Potentially both __getattr__ and __getattribute__ are set.
|
||||
Too complicated */
|
||||
*descr = NULL;
|
||||
return GETSET_OVERRIDDEN;
|
||||
}
|
||||
/* Potentially has __getattr__ but no custom __getattribute__.
|
||||
Fall through to usual descriptor analysis.
|
||||
Usual attribute lookup should only be allowed at runtime
|
||||
if we can guarantee that there is no way an exception can be
|
||||
raised. This means some specializations, e.g. specializing
|
||||
for property() isn't safe.
|
||||
*/
|
||||
}
|
||||
else {
|
||||
*descr = NULL;
|
||||
return GETSET_OVERRIDDEN;
|
||||
}
|
||||
|
|
@ -595,7 +638,10 @@ analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int sto
|
|||
return OTHER_SLOT;
|
||||
}
|
||||
if (desc_cls == &PyProperty_Type) {
|
||||
return PROPERTY;
|
||||
/* We can't detect at runtime whether an attribute exists
|
||||
with property. So that means we may have to call
|
||||
__getattr__. */
|
||||
return has_getattr ? GETSET_OVERRIDDEN : PROPERTY;
|
||||
}
|
||||
if (PyUnicode_CompareWithASCIIString(name, "__class__") == 0) {
|
||||
if (descriptor == _PyType_Lookup(&PyBaseObject_Type, name)) {
|
||||
|
|
@ -674,7 +720,6 @@ specialize_dict_access(
|
|||
static int specialize_attr_loadmethod(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name,
|
||||
PyObject* descr, DescriptorClassification kind);
|
||||
static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name);
|
||||
static int function_kind(PyCodeObject *code);
|
||||
|
||||
int
|
||||
_Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
|
||||
|
|
@ -729,24 +774,13 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
|
|||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
|
||||
goto fail;
|
||||
}
|
||||
if (Py_TYPE(fget) != &PyFunction_Type) {
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_PROPERTY);
|
||||
if (!Py_IS_TYPE(fget, &PyFunction_Type)) {
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_PROPERTY_NOT_PY_FUNCTION);
|
||||
goto fail;
|
||||
}
|
||||
PyFunctionObject *func = (PyFunctionObject *)fget;
|
||||
PyCodeObject *fcode = (PyCodeObject *)func->func_code;
|
||||
int kind = function_kind(fcode);
|
||||
if (kind != SIMPLE_FUNCTION) {
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, kind);
|
||||
goto fail;
|
||||
}
|
||||
if (fcode->co_argcount != 1) {
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_WRONG_NUMBER_ARGUMENTS);
|
||||
goto fail;
|
||||
}
|
||||
int version = _PyFunction_GetVersionForCurrentState(func);
|
||||
uint32_t version = function_check_args(fget, 1, LOAD_ATTR) &&
|
||||
function_get_version(fget, LOAD_ATTR);
|
||||
if (version == 0) {
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
|
||||
goto fail;
|
||||
}
|
||||
write_u32(lm_cache->keys_version, version);
|
||||
|
|
@ -795,6 +829,22 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
|
|||
case GETSET_OVERRIDDEN:
|
||||
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OVERRIDDEN);
|
||||
goto fail;
|
||||
case GETATTRIBUTE_IS_PYTHON_FUNCTION:
|
||||
{
|
||||
assert(type->tp_getattro == _Py_slot_tp_getattro);
|
||||
assert(Py_IS_TYPE(descr, &PyFunction_Type));
|
||||
_PyLoadMethodCache *lm_cache = (_PyLoadMethodCache *)(instr + 1);
|
||||
uint32_t func_version = function_check_args(descr, 2, LOAD_ATTR) &&
|
||||
function_get_version(descr, LOAD_ATTR);
|
||||
if (func_version == 0) {
|
||||
goto fail;
|
||||
}
|
||||
/* borrowed */
|
||||
write_obj(lm_cache->descr, descr);
|
||||
write_u32(lm_cache->type_version, type->tp_version_tag);
|
||||
_Py_SET_OPCODE(*instr, LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN);
|
||||
goto success;
|
||||
}
|
||||
case BUILTIN_CLASSMETHOD:
|
||||
case PYTHON_CLASSMETHOD:
|
||||
case NON_OVERRIDING:
|
||||
|
|
@ -873,6 +923,7 @@ _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name)
|
|||
case MUTABLE:
|
||||
SPECIALIZATION_FAIL(STORE_ATTR, SPEC_FAIL_ATTR_MUTABLE_CLASS);
|
||||
goto fail;
|
||||
case GETATTRIBUTE_IS_PYTHON_FUNCTION:
|
||||
case GETSET_OVERRIDDEN:
|
||||
SPECIALIZATION_FAIL(STORE_ATTR, SPEC_FAIL_OVERRIDDEN);
|
||||
goto fail;
|
||||
|
|
@ -1215,6 +1266,39 @@ function_kind(PyCodeObject *code) {
|
|||
return SIMPLE_FUNCTION;
|
||||
}
|
||||
|
||||
/* Returning false indicates a failure. */
|
||||
static bool
|
||||
function_check_args(PyObject *o, int expected_argcount, int opcode)
|
||||
{
|
||||
assert(Py_IS_TYPE(o, &PyFunction_Type));
|
||||
PyFunctionObject *func = (PyFunctionObject *)o;
|
||||
PyCodeObject *fcode = (PyCodeObject *)func->func_code;
|
||||
int kind = function_kind(fcode);
|
||||
if (kind != SIMPLE_FUNCTION) {
|
||||
SPECIALIZATION_FAIL(opcode, kind);
|
||||
return false;
|
||||
}
|
||||
if (fcode->co_argcount != expected_argcount) {
|
||||
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_WRONG_NUMBER_ARGUMENTS);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returning 0 indicates a failure. */
|
||||
static uint32_t
|
||||
function_get_version(PyObject *o, int opcode)
|
||||
{
|
||||
assert(Py_IS_TYPE(o, &PyFunction_Type));
|
||||
PyFunctionObject *func = (PyFunctionObject *)o;
|
||||
uint32_t version = _PyFunction_GetVersionForCurrentState(func);
|
||||
if (version == 0) {
|
||||
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OUT_OF_VERSIONS);
|
||||
return 0;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
int
|
||||
_Py_Specialize_BinarySubscr(
|
||||
PyObject *container, PyObject *sub, _Py_CODEUNIT *instr)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue