diff options
Diffstat (limited to 'Python/specialize.c')
-rw-r--r-- | Python/specialize.c | 122 |
1 files changed, 103 insertions, 19 deletions
diff --git a/Python/specialize.c b/Python/specialize.c index d5877a1..2dc7495 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -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) |