GH-93911: Specialize LOAD_ATTR for custom __getattribute__ (GH-93988)

This commit is contained in:
Ken Jin 2022-08-17 19:37:07 +08:00 committed by GitHub
parent 36517101dd
commit 7276ca25f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 219 additions and 86 deletions

View file

@ -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)