diff --git a/py/makeobjdefiner.py b/py/makeobjdefiner.py new file mode 100644 index 0000000000..05b4d2d3ca --- /dev/null +++ b/py/makeobjdefiner.py @@ -0,0 +1,16 @@ +for i in range(13): + r = range(i) + filled_slots = "|".join(f"slot_weight_##f{j}" for j in r) if i else "0" + slots = "".join( + f"\n [MP_OBJ_FIND_SLOT_STATIC(f{j}, {filled_slots})] = v{j}, \\" for j in r + ) + print(f"""\ +#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{"".join(f", f{j}, v{j}" for j in r)}) \\ + const _struct_type _typename = {{ \\ + .base = {{ &mp_type_type }}, \\ + .flags = _flags, \\ + .name = _name, \\ + .filled_slots = {filled_slots}, \\ + .slots = {{ \\{slots} + }}, \\ + }}""") diff --git a/py/mkrules.mk b/py/mkrules.mk index 3120066fd4..e7d938747d 100644 --- a/py/mkrules.mk +++ b/py/mkrules.mk @@ -121,7 +121,7 @@ $(BUILD)/%.o: $(BUILD)/%.c # the right .o's to get recompiled if the generated.h file changes. Adding # an order-only dependency to all of the .o's will cause the generated .h # to get built before we try to compile any of them. -$(OBJ): | $(HEADER_BUILD)/qstrdefs.generated.h $(HEADER_BUILD)/mpversion.h $(OBJ_EXTRA_ORDER_DEPS) +$(OBJ): | $(HEADER_BUILD)/qstrdefs.generated.h $(HEADER_BUILD)/mpversion.h $(OBJ_EXTRA_ORDER_DEPS) $(HEADER_BUILD)/define_obj.h # The logic for qstr regeneration (applied by makeqstrdefs.py) is: # - if anything in QSTR_GLOBAL_DEPENDENCIES is newer, then process all source files ($^) @@ -171,6 +171,10 @@ $(HEADER_BUILD)/compressed.collected: $(HEADER_BUILD)/compressed.split $(ECHO) "GEN $@" $(Q)$(PYTHON) $(PY_SRC)/makeqstrdefs.py cat compress _ $(HEADER_BUILD)/compress $@ +$(HEADER_BUILD)/define_obj.h: + $(ECHO) "GEN $@" + $(Q)$(PYTHON) $(PY_SRC)/makeobjdefiner.py > $@ + # $(sort $(var)) removes duplicates # # The net effect of this, is it causes the objects to depend on the diff --git a/py/obj.h b/py/obj.h index 69d6e626db..2bf61329cc 100644 --- a/py/obj.h +++ b/py/obj.h @@ -630,38 +630,20 @@ static inline void mp_get_buffer_raise(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_get_buffer(obj, bufinfo, flags | MP_BUFFER_RAISE_IF_UNSUPPORTED); } -// This struct will be updated to become a variable sized struct. In order to -// use this as a member, or allocate dynamically, use the mp_obj_empty_type_t -// or mp_obj_full_type_t structs below (which must be kept in sync). -struct _mp_obj_type_t { - // A type is an object so must start with this entry, which points to mp_type_type. - mp_obj_base_t base; - - // Flags associated with this type. - uint16_t flags; - - // The name of this type, a qstr. - uint16_t name; - - // Slots: For the rest of the fields, the slot index points to the - // relevant function in the variable-length "slots" field. Ideally these - // would be only 4 bits, but the extra overhead of accessing them adds - // more code, and we also need to be able to take the address of them for - // mp_obj_class_lookup. - +typedef enum _mp_slot_index_t { // Corresponds to __new__ and __init__ special methods, to make an instance of the type. - uint8_t slot_index_make_new; + slot_index_make_new, // Corresponds to __repr__ and __str__ special methods. - uint8_t slot_index_print; + slot_index_print, // Corresponds to __call__ special method, ie T(...). - uint8_t slot_index_call; + slot_index_call, // Implements unary and binary operations. // Can return MP_OBJ_NULL if the operation is not supported. - uint8_t slot_index_unary_op; - uint8_t slot_index_binary_op; + slot_index_unary_op, + slot_index_binary_op, // Implements load, store and delete attribute. // @@ -675,14 +657,14 @@ struct _mp_obj_type_t { // dest[0,1] = {MP_OBJ_SENTINEL, object} means store // return: for fail, do nothing // for success set dest[0] = MP_OBJ_NULL - uint8_t slot_index_attr; + slot_index_attr, // Implements load, store and delete subscripting: // - value = MP_OBJ_SENTINEL means load // - value = MP_OBJ_NULL means delete // - all other values mean store the value // Can return MP_OBJ_NULL if operation not supported. - uint8_t slot_index_subscr; + slot_index_subscr, // This slot's behaviour depends on the MP_TYPE_FLAG_ITER_IS_* flags above. // - If MP_TYPE_FLAG_ITER_IS_GETITER flag is set, then this corresponds to the __iter__ @@ -694,22 +676,60 @@ struct _mp_obj_type_t { // - If MP_TYPE_FLAG_ITER_IS_CUSTOM is set, then this slot must point to an // mp_getiter_iternext_custom_t instance with both the getiter and iternext fields set. // - If MP_TYPE_FLAG_ITER_IS_STREAM is set, this this slot should be unset. - uint8_t slot_index_iter; + slot_index_iter, // Implements the buffer protocol if supported by this type. - uint8_t slot_index_buffer; + slot_index_buffer, // One of disjoint protocols (interfaces), like mp_stream_p_t, etc. - uint8_t slot_index_protocol; + slot_index_protocol, // A pointer to the parents of this type: // - 0 parents: pointer is NULL (object is implicitly the single parent) // - 1 parent: a pointer to the type of that parent // - 2 or more parents: pointer to a tuple object containing the parent types - uint8_t slot_index_parent; + slot_index_parent, // A dict mapping qstrs to objects local methods/constants/etc. - uint8_t slot_index_locals_dict; + slot_index_locals_dict, + + // The maximum number of slots + slot_index_max, +} mp_slot_index_t; + +typedef enum _mp_slot_weight_t { + slot_weight_make_new = (1 << slot_index_make_new), + slot_weight_print = (1 << slot_index_print), + slot_weight_call = (1 << slot_index_call), + slot_weight_unary_op = (1 << slot_index_unary_op), + slot_weight_binary_op = (1 << slot_index_binary_op), + slot_weight_attr = (1 << slot_index_attr), + slot_weight_subscr = (1 << slot_index_subscr), + slot_weight_iter = (1 << slot_index_iter), + slot_weight_buffer = (1 << slot_index_buffer), + slot_weight_protocol = (1 << slot_index_protocol), + slot_weight_parent = (1 << slot_index_parent), + slot_weight_locals_dict = (1 << slot_index_locals_dict), +} mp_slot_weight_t; + +// This struct is a variable sized struct. In order to use this as a +// member, or allocate dynamically, use the mp_obj_empty_type_t or +// mp_obj_full_type_t structs below (which must be kept in sync). +struct _mp_obj_type_t { + // A type is an object so must start with this entry, which points to mp_type_type. + mp_obj_base_t base; + + // Flags associated with this type. + uint16_t flags; + + // The name of this type, a qstr. + uint16_t name; + + // Slots: If a slot is populated, the corresponding bit in `filled_slots` is set. + // The index in `slots[]` is simply the number of set bits "below" that one, + // which can be determined with __builtin_popcnt(). + uint16_t filled_slots; + // (16 spare bits here on most ports!) const void *slots[]; }; @@ -723,18 +743,7 @@ typedef struct _mp_obj_empty_type_t { uint16_t flags; uint16_t name; - uint8_t slot_index_make_new; - uint8_t slot_index_print; - uint8_t slot_index_call; - uint8_t slot_index_unary_op; - uint8_t slot_index_binary_op; - uint8_t slot_index_attr; - uint8_t slot_index_subscr; - uint8_t slot_index_iter; - uint8_t slot_index_buffer; - uint8_t slot_index_protocol; - uint8_t slot_index_parent; - uint8_t slot_index_locals_dict; + uint16_t filled_slots; // No slots member. } mp_obj_empty_type_t; @@ -744,21 +753,10 @@ typedef struct _mp_obj_full_type_t { uint16_t flags; uint16_t name; - uint8_t slot_index_make_new; - uint8_t slot_index_print; - uint8_t slot_index_call; - uint8_t slot_index_unary_op; - uint8_t slot_index_binary_op; - uint8_t slot_index_attr; - uint8_t slot_index_subscr; - uint8_t slot_index_iter; - uint8_t slot_index_buffer; - uint8_t slot_index_protocol; - uint8_t slot_index_parent; - uint8_t slot_index_locals_dict; + uint16_t filled_slots; - // Explicitly add 12 slots. - const void *slots[11]; + // Explicitly add all possible slots. + const void *slots[slot_index_max]; } mp_obj_full_type_t; #define _MP_OBJ_TYPE_SLOT_TYPE_make_new (mp_make_new_fun_t) @@ -776,35 +774,34 @@ typedef struct _mp_obj_full_type_t { // Implementation of MP_DEFINE_CONST_OBJ_TYPE for each number of arguments. // Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE. -// Generated with: -// for i in range(13): -// print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{''.join(f', f{j+1}, v{j+1}' for j in range(i))}) const _struct_type _typename = {{ .base = {{ &mp_type_type }}, .flags = _flags, .name = _name{''.join(f', .slot_index_##f{j+1} = {j+1}' for j in range(i))}{', .slots = { ' + ''.join(f'v{j+1}, ' for j in range(i)) + '}' if i else '' } }}") -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } } -#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } } +#ifndef NO_QSTR +#include "genhdr/define_obj.h" +#endif -// Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take -// slot_index_foo=0 to mean that the slot is unset. This also simplifies checking -// if the slot is set. That means that we need to store index+1 in slot_index_foo -// though and then access it as slots[slot_index_foo - 1]. This is an implementation -// detail, the user of these macros doesn't need to be aware of it, and when using -// MP_OBJ_TYPE_OFFSETOF_SLOT you should use zero-based indexing. -#define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->slot_index_##f) -#define MP_OBJ_TYPE_GET_SLOT(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(t)->slots[(t)->slot_index_##f - 1]) -#define MP_OBJ_TYPE_GET_SLOT_OR_NULL(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(MP_OBJ_TYPE_HAS_SLOT(t, f) ? MP_OBJ_TYPE_GET_SLOT(t, f) : NULL)) -#define MP_OBJ_TYPE_SET_SLOT(t, f, v, n) ((t)->slot_index_##f = (n) + 1, (t)->slots[(n)] = (void *)v) -#define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (offsetof(mp_obj_type_t, slot_index_##f)) -#define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (*(uint8_t *)((char *)(t) + (offset)) != 0) +#define MP_CONST_POPCNT8(x) ( \ + !!((x) & 1) + \ + !!((x) & 2) + \ + !!((x) & 4) + \ + !!((x) & 8) + \ + !!((x) & 16) + \ + !!((x) & 32) + \ + !!((x) & 64) + \ + !!((x) & 128)) +#define MP_CONST_POPCNT16(x) (MP_CONST_POPCNT8((x) & 0xff) + MP_CONST_POPCNT8((x) >> 8)) + +#define MP_OBJ_FIND_SLOT_STATIC(f, filled) MP_CONST_POPCNT16((filled) & (slot_weight_##f - 1)) +#define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->filled_slots & slot_weight_##f) +#define MP_OBJ_SLOT_IDX(t, f) (mp_popcount((t)->filled_slots & (slot_weight_##f - 1))) +#define MP_OBJ_TYPE_GET_SLOT(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(t)->slots[MP_OBJ_SLOT_IDX(t, f)]) +#define MP_OBJ_TYPE_GET_SLOT_OR_NULL(t, f) (MP_OBJ_TYPE_HAS_SLOT(t, f) ? MP_OBJ_TYPE_GET_SLOT(t, f) : NULL) +// Slots must be set in ascending slot order. +#define MP_OBJ_TYPE_SET_SLOT(t, f, v, _) do { \ + assert(!(t->filled_slots & ~(slot_weight_##f - 1))); \ + t->slots[mp_popcount(t->filled_slots)] = (void *)v; \ + t->filled_slots |= slot_weight_##f; \ +} while (0) +#define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (slot_weight_##f) +#define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (t->filled_slots & offset) // Workaround for https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview?view=msvc-160#macro-arguments-are-unpacked #define MP_DEFINE_CONST_OBJ_TYPE_EXPAND(x) x diff --git a/py/objtype.c b/py/objtype.c index d40f619fae..1996762de5 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -1212,6 +1212,7 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals base_protocol = MP_OBJ_TYPE_GET_SLOT_OR_NULL(((mp_obj_type_t *)MP_OBJ_TO_PTR(bases_items[0])), protocol); } + // TODO: Update this comment given that slot index storage was shrunk // Allocate a variable-sized mp_obj_type_t with as many slots as we need // (currently 10, plus 1 for base, plus 1 for base-protocol). // Note: mp_obj_type_t is (2 + 3 + #slots) words, so going from 11 to 12 slots @@ -1220,6 +1221,7 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals o->base.type = &mp_type_type; o->flags = base_flags; o->name = name; + // These must be in slot order! MP_OBJ_TYPE_SET_SLOT(o, make_new, mp_obj_instance_make_new, 0); MP_OBJ_TYPE_SET_SLOT(o, print, instance_print, 1); MP_OBJ_TYPE_SET_SLOT(o, call, mp_obj_instance_call, 2); @@ -1230,10 +1232,15 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals MP_OBJ_TYPE_SET_SLOT(o, iter, mp_obj_instance_getiter, 7); MP_OBJ_TYPE_SET_SLOT(o, buffer, instance_get_buffer, 8); - mp_obj_dict_t *locals_ptr = MP_OBJ_TO_PTR(locals_dict); - MP_OBJ_TYPE_SET_SLOT(o, locals_dict, locals_ptr, 9); - if (bases_len > 0) { + // Inherit protocol from a base class. This allows to define an + // abstract base class which would translate C-level protocol to + // Python method calls, and any subclass inheriting from it will + // support this feature. + if (base_protocol) { + MP_OBJ_TYPE_SET_SLOT(o, protocol, base_protocol, 11); + } + if (bases_len >= 2) { #if MICROPY_MULTIPLE_INHERITANCE MP_OBJ_TYPE_SET_SLOT(o, parent, MP_OBJ_TO_PTR(bases_tuple), 10); @@ -1243,16 +1250,11 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals } else { MP_OBJ_TYPE_SET_SLOT(o, parent, MP_OBJ_TO_PTR(bases_items[0]), 10); } - - // Inherit protocol from a base class. This allows to define an - // abstract base class which would translate C-level protocol to - // Python method calls, and any subclass inheriting from it will - // support this feature. - if (base_protocol) { - MP_OBJ_TYPE_SET_SLOT(o, protocol, base_protocol, 11); - } } + mp_obj_dict_t *locals_ptr = MP_OBJ_TO_PTR(locals_dict); + MP_OBJ_TYPE_SET_SLOT(o, locals_dict, locals_ptr, 9); + #if MICROPY_PY_DESCRIPTORS // To avoid any dynamic allocations when no __set_name__ exists, // the head of this list is kept on the stack (marked blank with `next = NULL`).