Commit d9709d1d authored by Kevin Modzelewski's avatar Kevin Modzelewski

Optimize isinstance

isinstance(obj, cls) needs to do a bunch of dynamic checking: it needs to
check cls's class to see if it defines __instancecheck__, and it needs
to fetch __class__ on obj.  Most of those time those aren't overridden,
so __instancecheck__ gets skipped and __class__ returns the type of the object.

So use the same "type slot" machinery to cache whether an __instancecheck__
or custom __class__ attribute have gotten added.  These are a bit different
than the other slots since they are not "wrappers", they are simply bools
that say whether or not the attribute exists.  This makes the slot handling
code a bit messier / more divergent from CPython, but I think it still makes
sense to put this here since we get the hooking-on-attribute-updating and
update-all-subclasses-as-well automatically.
parent 0251f5fa
......@@ -36,7 +36,8 @@ struct wrapperbase {
/* Flags for above struct */
#define PyWrapperFlag_KEYWORDS 1 /* wrapper function takes keyword args */
#define PyWrapperFlag_PYSTON 2 /* wrapper function is a Pyston function*/
#define PyWrapperFlag_PYSTON 2 /* wrapper function is a Pyston function */
#define PyWrapperFlag_BOOL 4 /* not really a wrapper, just set a bool field */
/* Various kinds of descriptor objects */
......
......@@ -457,7 +457,7 @@ struct _typeobject {
char _ics[32];
void* _gcvisit_func;
int _attrs_offset;
bool _flags[4];
bool _flags[6];
void* _tpp_descr_get;
void* _tpp_hasnext;
void* _tpp_call;
......
......@@ -404,10 +404,14 @@ static int recursive_isinstance(PyObject* inst, PyObject* cls) noexcept {
} else if (PyType_Check(cls)) {
retval = PyObject_TypeCheck(inst, (PyTypeObject*)cls);
if (retval == 0) {
PyObject* c = PyObject_GetAttr(inst, __class__);
if (c == NULL) {
PyErr_Clear();
} else {
PyObject* c = NULL;
if (inst->cls->has___class__ || inst->cls->tp_getattr != object_cls->tp_getattr
|| inst->cls->tp_getattro != object_cls->tp_getattro) {
c = PyObject_GetAttr(inst, __class__);
if (!c)
PyErr_Clear();
}
if (c) {
if (c != (PyObject*)(inst->cls) && PyType_Check(c))
retval = PyType_IsSubtype((PyTypeObject*)c, (PyTypeObject*)cls);
Py_DECREF(c);
......@@ -435,6 +439,8 @@ extern "C" int _PyObject_RealIsInstance(PyObject* inst, PyObject* cls) noexcept
}
extern "C" int PyObject_IsInstance(PyObject* inst, PyObject* cls) noexcept {
STAT_TIMER(t0, "us_timer_pyobject_isinstance", 20);
static PyObject* name = NULL;
/* Quick test for an exact match */
......@@ -461,8 +467,15 @@ extern "C" int PyObject_IsInstance(PyObject* inst, PyObject* cls) noexcept {
}
if (!(PyClass_Check(cls) || PyInstance_Check(cls))) {
PyObject* checker;
checker = _PyObject_LookupSpecial(cls, "__instancecheck__", &name);
PyObject* checker = NULL;
if (cls->cls->has_instancecheck) {
checker = _PyObject_LookupSpecial(cls, "__instancecheck__", &name);
if (!checker && PyErr_Occurred())
return -1;
assert(checker);
}
if (checker != NULL) {
PyObject* res;
int ok = -1;
......@@ -478,8 +491,7 @@ extern "C" int PyObject_IsInstance(PyObject* inst, PyObject* cls) noexcept {
Py_DECREF(res);
}
return ok;
} else if (PyErr_Occurred())
return -1;
}
}
return recursive_isinstance(inst, cls);
}
......
......@@ -1488,6 +1488,8 @@ static slotdef slotdefs[]
PyWrapperFlag_KEYWORDS),
TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""),
TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""),
FLSLOT("__class__", has___class__, NULL, NULL, "", PyWrapperFlag_BOOL),
FLSLOT("__instancecheck__", has_instancecheck, NULL, NULL, "", PyWrapperFlag_BOOL),
TPPSLOT("__hasnext__", tpp_hasnext, slotTppHasnext, wrapInquirypred, "hasnext"),
BINSLOT("__add__", nb_add, slot_nb_add, "+"), // [force clang-format to line break]
......@@ -1668,6 +1670,22 @@ static const slotdef* update_one_slot(BoxedClass* type, const slotdef* p) noexce
do {
descr = typeLookup(type, p->name_strobj, NULL);
if (p->flags & PyWrapperFlag_BOOL) {
// We are supposed to iterate over each slotdef; for now just assert that
// there was only one:
assert((p + 1)->offset > p->offset);
static BoxedString* class_str = internStringImmortal("__class__");
if (p->name_strobj == class_str) {
if (descr == object_cls->getattr(class_str))
descr = NULL;
}
*(bool*)ptr = (bool)descr;
return p + 1;
}
if (descr == NULL) {
if (ptr == (void**)&type->tp_iternext) {
specific = (void*)_PyObject_NextNotImplemented;
......
......@@ -361,11 +361,9 @@ Box* sorted(Box* obj, Box* cmp, Box* key, Box** args) {
}
Box* isinstance_func(Box* obj, Box* cls) {
STAT_TIMER(t0, "us_timer_isinstance_func", 10);
int rtn = PyObject_IsInstance(obj, cls);
if (rtn < 0)
checkAndThrowCAPIException();
throwCAPIException();
return boxBool(rtn);
}
......
......@@ -409,7 +409,9 @@ BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset
attrs_offset(attrs_offset),
is_constant(false),
is_user_defined(is_user_defined),
is_pyston_class(true) {
is_pyston_class(true),
has___class__(false),
has_instancecheck(false) {
// Zero out the CPython tp_* slots:
memset(&tp_name, 0, (char*)(&tp_version_tag + 1) - (char*)(&tp_name));
......
......@@ -224,6 +224,9 @@ public:
// that we can't rely on for extension classes.
bool is_pyston_class;
bool has___class__; // Has a custom __class__ attribute (ie different from object's __class__ descriptor)
bool has_instancecheck;
typedef bool (*pyston_inquiry)(Box*);
// tpp_descr_get is currently just a cache only for the use of tp_descr_get, and shouldn't
......
class M(type):
pass
class C(object):
__metaclass__ = M
class D(C):
pass
c = C()
d = D()
i = 0
print "Testing base case:"
print isinstance(i, C), isinstance(c, C), isinstance(d, C), isinstance(c, int)
print
print "Testing custom metaclass instancecheck:"
def m_instancecheck(self, obj):
print "m_instancecheck", type(self), type(obj)
return False
M.__instancecheck__ = m_instancecheck
print isinstance(i, C), isinstance(c, C), isinstance(d, C), isinstance(c, int)
print
print "Testing custom class instancecheck:"
def c_instancecheck(obj):
print "c_instancecheck", type(obj)
return False
C.__instancecheck__ = c_instancecheck
print isinstance(i, C), isinstance(c, C), isinstance(d, C), isinstance(c, int)
print
del M.__instancecheck__
del C.__instancecheck__
print "Testing custom class getattribute:"
def c_getattribute(self, attr):
print "c_getattribute", type(self), attr
C.__getattribute__ = c_getattribute
print isinstance(i, C), isinstance(c, C), isinstance(d, C), isinstance(c, int)
print
print "Testing custom metaclass getattribute:"
def m_getattribute(self, attr):
print "m_getattribute", type(self), attr
M.__getattribute__ = m_getattribute
print isinstance(i, C), isinstance(c, C), isinstance(d, C), isinstance(c, int)
print
del C.__getattribute__
del M.__getattribute__
print "Testing custom instance __class__"
c2 = C()
c2.__dict__['__class__'] = int
print type(c2), c2.__class__ # should be the same; __class__ is a data decriptor
print isinstance(c2, int), isinstance(c, int)
print
print "Testing custom class __class__:"
class E(object):
__class__ = int
e = E()
print type(e), e.__class__ # should be different!
print isinstance(e, int), isinstance(e, float), isinstance(e, E), isinstance(E, object)
print
print "Testing custom instance __class__ with custom class __class__:"
# Unlike the non-custom-class version, a custom instance __class__ will now
# take effect since the class __class__ is no longer a data descriptor.
e.__dict__['__class__'] = float
print type(e), e.__class__ # __class__ is now float!
print isinstance(e, int), isinstance(e, float), isinstance(e, E), isinstance(E, object)
print
class M(type):
pass
class M2(M):
pass
class C(object):
__metaclass__ = M2
class D(object):
pass
# Setting instancecheck on a superclass better update the subclasses:
print "checking superclass instancecheck:"
print isinstance(1, C)
M.__instancecheck__ = m_instancecheck
print isinstance(1, C)
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