Commit b842b0c5 authored by Stefan Behnel's avatar Stefan Behnel

Optimise attribute access on extension types with "__getattr__" but without...

Optimise attribute access on extension types with "__getattr__" but without instance dict through a streamlined copy of "PyObject_GenericGetAttr".
parent 1f345873
......@@ -1880,16 +1880,19 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# If that raises an AttributeError, call the __getattr__ if defined.
#
# In both cases, defined can be in this class, or any base class.
def lookup_here_or_base(n, type=None):
def lookup_here_or_base(n, tp=None, extern_return=None):
# Recursive lookup
if type is None:
type = scope.parent_type
r = type.scope.lookup_here(n)
if r is None and \
type.base_type is not None:
return lookup_here_or_base(n, type.base_type)
else:
if tp is None:
tp = scope.parent_type
r = tp.scope.lookup_here(n)
if r is None:
if tp.is_external and extern_return is not None:
return extern_return
if tp.base_type is not None:
return lookup_here_or_base(n, tp.base_type)
return r
has_instance_dict = lookup_here_or_base("__dict__", extern_return="extern")
getattr_entry = lookup_here_or_base("__getattr__")
getattribute_entry = lookup_here_or_base("__getattribute__")
code.putln("")
......@@ -1901,8 +1904,20 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
"PyObject *v = %s(o, n);" % (
getattribute_entry.func_cname))
else:
if not has_instance_dict and scope.parent_type.is_final_type:
# Final with no dict => use faster type attribute lookup.
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObject_GenericGetAttrNoDict", "ObjectHandling.c"))
generic_getattr_cfunc = "__Pyx_PyObject_GenericGetAttrNoDict"
elif not has_instance_dict or has_instance_dict == "extern":
# No dict in the known ancestors, but don't know about extern ancestors or subtypes.
code.globalstate.use_utility_code(
UtilityCode.load_cached("PyObject_GenericGetAttr", "ObjectHandling.c"))
generic_getattr_cfunc = "__Pyx_PyObject_GenericGetAttr"
else:
generic_getattr_cfunc = "PyObject_GenericGetAttr"
code.putln(
"PyObject *v = PyObject_GenericGetAttr(o, n);")
"PyObject *v = %s(o, n);" % generic_getattr_cfunc)
if getattr_entry is not None:
code.putln(
"if (!v && PyErr_ExceptionMatches(PyExc_AttributeError)) {")
......
......@@ -1293,7 +1293,119 @@ done:
#endif
////////////// PyObjectGetAttrStr.proto ///////////////
/////////////// PyObjectGetDict.proto ///////////////
// Doesn't actually depend on CYTHON_USE_PYTYPE_LOOKUP, but all usages currently have that constraint.
#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP
static PyObject* __Pyx_PyObject_GetDict(PyTypeObject* tp, PyObject *obj);
#endif
/////////////// PyObjectGetDict ///////////////
#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP
static PyObject* __Pyx_PyObject_GetDict(PyTypeObject* tp, PyObject *obj) {
// Copied from _PyObject_GetDictPtr() in CPython.
Py_ssize_t dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
PyObject **dictptr;
if (unlikely(dictoffset < 0)) {
Py_ssize_t tsize;
size_t size;
tsize = ((PyVarObject *)obj)->ob_size;
if (tsize < 0)
tsize = -tsize;
size = _PyObject_VAR_SIZE(tp, tsize);
assert(size <= PY_SSIZE_T_MAX);
dictoffset += (Py_ssize_t)size;
assert(dictoffset > 0);
// assert(dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
return *dictptr;
}
return NULL;
}
#endif
/////////////// PyObject_GenericGetAttr.proto ///////////////
#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP
static PyObject* __Pyx_PyObject_GenericGetAttr(PyObject* obj, PyObject* attr_name);
#else
// No-args macro to allow function pointer assignment.
#define __Pyx_PyObject_GenericGetAttr PyObject_GenericGetAttr
#endif
/////////////// PyObject_GenericGetAttr ///////////////
//@requires: PyObjectGetDict
//@requires: PyObject_GenericGetAttrNoDict
#if CYTHON_USE_TYPE_SLOTS && CYTHON_USE_PYTYPE_LOOKUP
static PyObject* __Pyx_PyObject_GenericGetAttr(PyObject* obj, PyObject* attr_name) {
// Copied and adapted from _PyObject_GenericGetAttrWithDict() in CPython 2.6/3.7.
// To be used in the "tp_getattro" slot of extension types that have no instance dict can get one by subclassing.
PyObject *descr, *res;
PyTypeObject *tp = Py_TYPE(obj);
descrgetfunc f = NULL;
if (likely(!tp->tp_dictoffset)) {
return __Pyx_PyObject_GenericGetAttrNoDict(obj, attr_name);
}
if (unlikely(!PyString_CheckExact(attr_name))) {
return PyObject_GenericGetAttr(obj, attr_name);
}
descr = _PyType_Lookup(tp, attr_name);
if (descr
#if PY_MAJOR_VERSION < 3
&& likely(PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_HAVE_CLASS))
#endif
) {
f = Py_TYPE(descr)->tp_descr_get;
// Writable properties are less common than dict attributes and methods.
if (f && unlikely(PyDescr_IsData(descr))) {
Py_INCREF(descr);
res = f(descr, obj, (PyObject *)tp);
Py_DECREF(descr);
goto done;
}
}
{
PyObject *dict = __Pyx_PyObject_GetDict(tp, obj);
if (dict) {
Py_INCREF(dict);
res = PyDict_GetItem(dict, attr_name);
if (res) {
Py_INCREF(res);
Py_DECREF(dict);
goto done;
}
Py_DECREF(dict);
}
}
if (likely(descr)) {
Py_INCREF(descr);
res = descr;
if (f) {
res = f(descr, obj, (PyObject*)tp);
Py_DECREF(descr);
}
goto done;
}
return __Pyx_RaiseGenericAttributeError(tp, attr_name);
done:
return res;
}
// CYTHON_USE_PYTYPE_LOOKUP
#endif
/////////////// PyObjectGetAttrStr.proto ///////////////
#if CYTHON_USE_TYPE_SLOTS
static CYTHON_INLINE PyObject* __Pyx_PyObject_GetAttrStr(PyObject* obj, PyObject* attr_name);/*proto*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment