py: Combine load_attr and store_attr type methods into one (attr).

This simplifies the API for objects and reduces code size (by around 400
bytes on Thumb2, and around 2k on x86).  Performance impact was measured
with Pystone score, but change was barely noticeable.
This commit is contained in:
Damien George 2015-04-01 14:10:50 +00:00
parent d07ccc5a39
commit b1bbe966c4
13 changed files with 173 additions and 128 deletions

View File

@ -482,13 +482,17 @@ STATIC mp_obj_t uctypes_struct_attr_op(mp_obj_t self_in, qstr attr, mp_obj_t set
return MP_OBJ_NULL;
}
STATIC void uctypes_struct_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_t val = uctypes_struct_attr_op(self_in, attr, MP_OBJ_NULL);
*dest = val;
}
STATIC bool uctypes_struct_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t val) {
return uctypes_struct_attr_op(self_in, attr, val) != MP_OBJ_NULL;
STATIC void uctypes_struct_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] == MP_OBJ_NULL) {
// load attribute
mp_obj_t val = uctypes_struct_attr_op(self_in, attr, MP_OBJ_NULL);
dest[0] = val;
} else {
// delete/store attribute
if (uctypes_struct_attr_op(self_in, attr, dest[1]) != MP_OBJ_NULL) {
dest[0] = MP_OBJ_NULL; // indicate success
}
}
}
STATIC mp_obj_t uctypes_struct_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value) {
@ -589,8 +593,7 @@ STATIC const mp_obj_type_t uctypes_struct_type = {
.name = MP_QSTR_struct,
.print = uctypes_struct_print,
.make_new = uctypes_struct_make_new,
.load_attr = uctypes_struct_load_attr,
.store_attr = uctypes_struct_store_attr,
.attr = uctypes_struct_attr,
.subscr = uctypes_struct_subscr,
};

View File

@ -265,8 +265,7 @@ typedef mp_obj_t (*mp_make_new_fun_t)(mp_obj_t type_in, mp_uint_t n_args, mp_uin
typedef mp_obj_t (*mp_call_fun_t)(mp_obj_t fun, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
typedef mp_obj_t (*mp_unary_op_fun_t)(mp_uint_t op, mp_obj_t);
typedef mp_obj_t (*mp_binary_op_fun_t)(mp_uint_t op, mp_obj_t, mp_obj_t);
typedef void (*mp_load_attr_fun_t)(mp_obj_t self_in, qstr attr, mp_obj_t *dest); // for fail, do nothing; for attr, dest[0] = value; for method, dest[0] = method, dest[1] = self
typedef bool (*mp_store_attr_fun_t)(mp_obj_t self_in, qstr attr, mp_obj_t value); // return true if store succeeded; if value==MP_OBJ_NULL then delete
typedef void (*mp_attr_fun_t)(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
typedef mp_obj_t (*mp_subscr_fun_t)(mp_obj_t self_in, mp_obj_t index, mp_obj_t value);
typedef struct _mp_method_t {
@ -330,8 +329,18 @@ struct _mp_obj_type_t {
mp_unary_op_fun_t unary_op; // can return MP_OBJ_NULL if op not supported
mp_binary_op_fun_t binary_op; // can return MP_OBJ_NULL if op not supported
mp_load_attr_fun_t load_attr;
mp_store_attr_fun_t store_attr; // if value is MP_OBJ_NULL, then delete that attribute
// implements load, store and delete attribute
//
// dest[0] = MP_OBJ_NULL means load
// return: for fail, do nothing
// for attr, dest[0] = value
// for method, dest[0] = method, dest[1] = self
//
// dest[0,1] = {MP_OBJ_SENTINEL, MP_OBJ_NULL} means delete
// dest[0,1] = {MP_OBJ_SENTINEL, object} means store
// return: for fail, do nothing
// for success set dest[0] = MP_OBJ_NULL
mp_attr_fun_t attr;
mp_subscr_fun_t subscr; // implements load, store, delete subscripting
// value=MP_OBJ_NULL means delete, value=MP_OBJ_SENTINEL means load, else store

View File

@ -71,7 +71,11 @@ STATIC mp_obj_t bound_meth_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_
}
#if MICROPY_PY_FUNCTION_ATTRS
STATIC void bound_meth_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void bound_meth_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
if (attr == MP_QSTR___name__) {
mp_obj_bound_meth_t *o = self_in;
dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(o->meth));
@ -87,7 +91,7 @@ STATIC const mp_obj_type_t mp_type_bound_meth = {
#endif
.call = bound_meth_call,
#if MICROPY_PY_FUNCTION_ATTRS
.load_attr = bound_meth_load_attr,
.attr = bound_meth_attr,
#endif
};

View File

@ -141,7 +141,11 @@ STATIC mp_obj_t complex_binary_op(mp_uint_t op, mp_obj_t lhs_in, mp_obj_t rhs_in
return mp_obj_complex_binary_op(op, lhs->real, lhs->imag, rhs_in);
}
STATIC void complex_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void complex_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
mp_obj_complex_t *self = self_in;
if (attr == MP_QSTR_real) {
dest[0] = mp_obj_new_float(self->real);
@ -157,7 +161,7 @@ const mp_obj_type_t mp_type_complex = {
.make_new = complex_make_new,
.unary_op = complex_unary_op,
.binary_op = complex_binary_op,
.load_attr = complex_load_attr,
.attr = complex_attr,
};
mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag) {

View File

@ -140,7 +140,11 @@ mp_obj_t mp_obj_exception_get_value(mp_obj_t self_in) {
}
}
STATIC void exception_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
mp_obj_exception_t *self = self_in;
if (attr == MP_QSTR_args) {
dest[0] = self->args;
@ -168,7 +172,7 @@ const mp_obj_type_t mp_type_BaseException = {
.name = MP_QSTR_BaseException,
.print = mp_obj_exception_print,
.make_new = mp_obj_exception_make_new,
.load_attr = exception_load_attr,
.attr = exception_attr,
.locals_dict = (mp_obj_t)&exc_locals_dict,
};
@ -181,7 +185,7 @@ const mp_obj_type_t mp_type_ ## exc_name = { \
.name = MP_QSTR_ ## exc_name, \
.print = mp_obj_exception_print, \
.make_new = mp_obj_exception_make_new, \
.load_attr = exception_load_attr, \
.attr = exception_attr, \
.bases_tuple = (mp_obj_t)&mp_type_ ## base_name ## _base_tuple, \
};

View File

@ -296,7 +296,11 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw,
}
#if MICROPY_PY_FUNCTION_ATTRS
STATIC void fun_bc_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
if (attr == MP_QSTR___name__) {
dest[0] = MP_OBJ_NEW_QSTR(mp_obj_fun_get_name(self_in));
}
@ -311,7 +315,7 @@ const mp_obj_type_t mp_type_fun_bc = {
#endif
.call = fun_bc_call,
#if MICROPY_PY_FUNCTION_ATTRS
.load_attr = fun_bc_load_attr,
.attr = fun_bc_attr,
#endif
};

View File

@ -51,48 +51,48 @@ STATIC void module_print(void (*print)(void *env, const char *fmt, ...), void *e
print(env, "<module '%s'>", name);
}
STATIC void module_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_module_t *self = self_in;
mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
dest[0] = elem->value;
}
}
STATIC bool module_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
mp_obj_module_t *self = self_in;
mp_obj_dict_t *dict = self->globals;
if (dict->map.is_fixed) {
#if MICROPY_CAN_OVERRIDE_BUILTINS
if (dict == &mp_module_builtins_globals) {
if (MP_STATE_VM(mp_module_builtins_override_dict) == NULL) {
MP_STATE_VM(mp_module_builtins_override_dict) = mp_obj_new_dict(1);
}
dict = MP_STATE_VM(mp_module_builtins_override_dict);
} else
#endif
{
// can't delete or store to fixed map
return false;
if (dest[0] == MP_OBJ_NULL) {
// load attribute
mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
dest[0] = elem->value;
}
}
if (value == MP_OBJ_NULL) {
// delete attribute
mp_obj_dict_delete(dict, MP_OBJ_NEW_QSTR(attr));
} else {
// store attribute
// TODO CPython allows STORE_ATTR to a module, but is this the correct implementation?
mp_obj_dict_store(dict, MP_OBJ_NEW_QSTR(attr), value);
// delete/store attribute
mp_obj_dict_t *dict = self->globals;
if (dict->map.is_fixed) {
#if MICROPY_CAN_OVERRIDE_BUILTINS
if (dict == &mp_module_builtins_globals) {
if (MP_STATE_VM(mp_module_builtins_override_dict) == NULL) {
MP_STATE_VM(mp_module_builtins_override_dict) = mp_obj_new_dict(1);
}
dict = MP_STATE_VM(mp_module_builtins_override_dict);
} else
#endif
{
// can't delete or store to fixed map
return;
}
}
if (dest[1] == MP_OBJ_NULL) {
// delete attribute
mp_obj_dict_delete(dict, MP_OBJ_NEW_QSTR(attr));
} else {
// store attribute
// TODO CPython allows STORE_ATTR to a module, but is this the correct implementation?
mp_obj_dict_store(dict, MP_OBJ_NEW_QSTR(attr), dest[1]);
}
dest[0] = MP_OBJ_NULL; // indicate success
}
return true;
}
const mp_obj_type_t mp_type_module = {
{ &mp_type_type },
.name = MP_QSTR_module,
.print = module_print,
.load_attr = module_load_attr,
.store_attr = module_store_attr,
.attr = module_attr,
};
mp_obj_t mp_obj_new_module(qstr module_name) {

View File

@ -68,20 +68,20 @@ STATIC void namedtuple_print(void (*print)(void *env, const char *fmt, ...), voi
print(env, ")");
}
STATIC void namedtuple_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_namedtuple_t *self = self_in;
int id = namedtuple_find_field((mp_obj_namedtuple_type_t*)self->tuple.base.type, attr);
if (id == -1) {
return;
STATIC void namedtuple_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] == MP_OBJ_NULL) {
// load attribute
mp_obj_namedtuple_t *self = self_in;
int id = namedtuple_find_field((mp_obj_namedtuple_type_t*)self->tuple.base.type, attr);
if (id == -1) {
return;
}
dest[0] = self->tuple.items[id];
} else {
// delete/store attribute
// provide more detailed error message than we'd get by just returning
nlr_raise(mp_obj_new_exception_msg(&mp_type_AttributeError, "can't set attribute"));
}
dest[0] = self->tuple.items[id];
}
STATIC bool namedtuple_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
(void)self_in;
(void)attr;
(void)value;
nlr_raise(mp_obj_new_exception_msg(&mp_type_AttributeError, "can't set attribute"));
}
STATIC mp_obj_t namedtuple_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
@ -154,8 +154,7 @@ STATIC mp_obj_t mp_obj_new_namedtuple_type(qstr name, mp_uint_t n_fields, mp_obj
o->base.make_new = namedtuple_make_new;
o->base.unary_op = mp_obj_tuple_unary_op;
o->base.binary_op = mp_obj_tuple_binary_op;
o->base.load_attr = namedtuple_load_attr;
o->base.store_attr = namedtuple_store_attr;
o->base.attr = namedtuple_attr;
o->base.subscr = mp_obj_tuple_subscr;
o->base.getiter = mp_obj_tuple_getiter;
o->base.bases_tuple = (mp_obj_t)&namedtuple_base_tuple;

View File

@ -168,7 +168,11 @@ STATIC mp_obj_t range_getiter(mp_obj_t o_in) {
#if MICROPY_PY_BUILTINS_RANGE_ATTRS
STATIC void range_load_attr(mp_obj_t o_in, qstr attr, mp_obj_t *dest) {
STATIC void range_attr(mp_obj_t o_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
mp_obj_range_t *o = o_in;
if (attr == MP_QSTR_start) {
dest[0] = mp_obj_new_int(o->start);
@ -189,6 +193,6 @@ const mp_obj_type_t mp_type_range = {
.subscr = range_subscr,
.getiter = range_getiter,
#if MICROPY_PY_BUILTINS_RANGE_ATTRS
.load_attr = range_load_attr,
.attr = range_attr,
#endif
};

View File

@ -440,7 +440,7 @@ STATIC mp_obj_t instance_binary_op(mp_uint_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
}
}
void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
// logic: look in instance members then class locals
assert(is_instance_type(mp_obj_get_type(self_in)));
mp_obj_instance_t *self = self_in;
@ -512,7 +512,7 @@ void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
}
}
bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
STATIC bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
mp_obj_instance_t *self = self_in;
#if MICROPY_PY_BUILTINS_PROPERTY || MICROPY_PY_DESCRIPTORS
@ -602,6 +602,16 @@ bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
}
}
void mp_obj_instance_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] == MP_OBJ_NULL) {
mp_obj_instance_load_attr(self_in, attr, dest);
} else {
if (mp_obj_instance_store_attr(self_in, attr, dest[1])) {
dest[0] = MP_OBJ_NULL; // indicate success
}
}
}
STATIC mp_obj_t instance_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
mp_obj_instance_t *self = self_in;
mp_obj_t member[2] = {MP_OBJ_NULL};
@ -774,52 +784,52 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, co
return o;
}
// for fail, do nothing; for attr, dest[0] = value; for method, dest[0] = method, dest[1] = self
STATIC void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
mp_obj_type_t *self = self_in;
#if MICROPY_CPYTHON_COMPAT
if (attr == MP_QSTR___name__) {
dest[0] = MP_OBJ_NEW_QSTR(self->name);
return;
}
#endif
struct class_lookup_data lookup = {
.obj = self_in,
.attr = attr,
.meth_offset = 0,
.dest = dest,
.is_type = true,
};
mp_obj_class_lookup(&lookup, self);
}
STATIC bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
STATIC void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
mp_obj_type_t *self = self_in;
// TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
if (dest[0] == MP_OBJ_NULL) {
// load attribute
#if MICROPY_CPYTHON_COMPAT
if (attr == MP_QSTR___name__) {
dest[0] = MP_OBJ_NEW_QSTR(self->name);
return;
}
#endif
struct class_lookup_data lookup = {
.obj = self_in,
.attr = attr,
.meth_offset = 0,
.dest = dest,
.is_type = true,
};
mp_obj_class_lookup(&lookup, self);
} else {
// delete/store attribute
if (self->locals_dict != NULL) {
assert(MP_OBJ_IS_TYPE(self->locals_dict, &mp_type_dict)); // Micro Python restriction, for now
mp_map_t *locals_map = mp_obj_dict_get_map(self->locals_dict);
if (value == MP_OBJ_NULL) {
// delete attribute
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
// note that locals_map may be in ROM, so remove will fail in that case
return elem != NULL;
} else {
// store attribute
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
// note that locals_map may be in ROM, so add will fail in that case
if (elem != NULL) {
elem->value = value;
return true;
// TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
if (self->locals_dict != NULL) {
assert(MP_OBJ_IS_TYPE(self->locals_dict, &mp_type_dict)); // Micro Python restriction, for now
mp_map_t *locals_map = mp_obj_dict_get_map(self->locals_dict);
if (dest[1] == MP_OBJ_NULL) {
// delete attribute
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
// note that locals_map may be in ROM, so remove will fail in that case
if (elem != NULL) {
dest[0] = MP_OBJ_NULL; // indicate success
}
} else {
// store attribute
mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
// note that locals_map may be in ROM, so add will fail in that case
if (elem != NULL) {
elem->value = dest[1];
dest[0] = MP_OBJ_NULL; // indicate success
}
}
}
}
return false;
}
const mp_obj_type_t mp_type_type = {
@ -828,8 +838,7 @@ const mp_obj_type_t mp_type_type = {
.print = type_print,
.make_new = type_make_new,
.call = type_call,
.load_attr = type_load_attr,
.store_attr = type_store_attr,
.attr = type_attr,
};
mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict) {
@ -865,8 +874,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
o->call = mp_obj_instance_call;
o->unary_op = instance_unary_op;
o->binary_op = instance_binary_op;
o->load_attr = mp_obj_instance_load_attr;
o->store_attr = mp_obj_instance_store_attr;
o->attr = mp_obj_instance_attr;
o->subscr = instance_subscr;
o->getiter = instance_getiter;
//o->iternext = ; not implemented
@ -921,8 +929,12 @@ STATIC mp_obj_t super_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_k
return mp_obj_new_super(args[0], args[1]);
}
// for fail, do nothing; for attr, dest[0] = value; for method, dest[0] = method, dest[1] = self
STATIC void super_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
STATIC void super_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
if (dest[0] != MP_OBJ_NULL) {
// not load attribute
return;
}
assert(MP_OBJ_IS_TYPE(self_in, &mp_type_super));
mp_obj_super_t *self = self_in;
@ -960,7 +972,7 @@ const mp_obj_type_t mp_type_super = {
.name = MP_QSTR_super,
.print = super_print,
.make_new = super_make_new,
.load_attr = super_load_attr,
.attr = super_attr,
};
mp_obj_t mp_obj_new_super(mp_obj_t type, mp_obj_t obj) {

View File

@ -37,9 +37,8 @@ typedef struct _mp_obj_instance_t {
// TODO maybe cache __getattr__ and __setattr__ for efficient lookup of them
} mp_obj_instance_t;
// these need to be exposed for MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE to work
void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
bool mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value);
// this needs to be exposed for MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE to work
void mp_obj_instance_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
// these need to be exposed so mp_obj_is_callable can work correctly
bool mp_obj_instance_is_callable(mp_obj_t self_in);

View File

@ -917,9 +917,9 @@ void mp_load_method_maybe(mp_obj_t obj, qstr attr, mp_obj_t *dest) {
dest[0] = (mp_obj_t)&mp_builtin_next_obj;
dest[1] = obj;
} else if (type->load_attr != NULL) {
} else if (type->attr != NULL) {
// this type can do its own load, so call it
type->load_attr(obj, attr, dest);
type->attr(obj, attr, dest);
} else if (type->locals_dict != NULL) {
// generic method lookup
@ -961,8 +961,11 @@ void mp_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
void mp_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) {
DEBUG_OP_printf("store attr %p.%s <- %p\n", base, qstr_str(attr), value);
mp_obj_type_t *type = mp_obj_get_type(base);
if (type->store_attr != NULL) {
if (type->store_attr(base, attr, value)) {
if (type->attr != NULL) {
mp_obj_t dest[2] = {MP_OBJ_SENTINEL, value};
type->attr(base, attr, dest);
if (dest[0] == MP_OBJ_NULL) {
// success
return;
}
}

View File

@ -315,7 +315,7 @@ dispatch_loop:
MARK_EXC_IP_SELECTIVE();
DECODE_QSTR;
mp_obj_t top = TOP();
if (mp_obj_get_type(top)->load_attr == mp_obj_instance_load_attr) {
if (mp_obj_get_type(top)->attr == mp_obj_instance_attr) {
mp_obj_instance_t *self = top;
mp_uint_t x = *ip;
mp_obj_t key = MP_OBJ_NEW_QSTR(qst);
@ -405,7 +405,7 @@ dispatch_loop:
MARK_EXC_IP_SELECTIVE();
DECODE_QSTR;
mp_obj_t top = TOP();
if (mp_obj_get_type(top)->store_attr == mp_obj_instance_store_attr && sp[-1] != MP_OBJ_NULL) {
if (mp_obj_get_type(top)->attr == mp_obj_instance_attr && sp[-1] != MP_OBJ_NULL) {
mp_obj_instance_t *self = top;
mp_uint_t x = *ip;
mp_obj_t key = MP_OBJ_NEW_QSTR(qst);