core: Introduce MICROPY_MAP_SPLIT (unfinished).

Signed-off-by: Jeff Epler <jepler@gmail.com>
This commit is contained in:
Jeff Epler 2025-08-10 11:39:34 -05:00
parent 8bf6e22a2e
commit cb62a54966
8 changed files with 185 additions and 40 deletions

View file

@ -5,6 +5,9 @@
// Use the minimal starting configuration (disables all optional features).
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM)
// save a little ROM, maybe ...
#define MICROPY_MAP_SPLIT (1)
// You can disable the built-in MicroPython compiler by setting the following
// config option to 0. If you do this then you won't get a REPL prompt, but you
// will still be able to execute pre-compiled scripts, compiled with mpy-cross.

View file

@ -243,8 +243,8 @@ static void mp_emit_common_populate_module_context(mp_emit_common_t *emit, qstr
mp_module_context_alloc_tables(context, qstr_map_used, emit->const_obj_list.len);
for (size_t i = 0; i < emit->qstr_map.alloc; ++i) {
if (mp_map_slot_is_filled(&emit->qstr_map, i)) {
size_t idx = MP_OBJ_SMALL_INT_VALUE(emit->qstr_map.table[i].value);
qstr qst = MP_OBJ_QSTR_VALUE(emit->qstr_map.table[i].key);
size_t idx = MP_OBJ_SMALL_INT_VALUE(mp_map_slot_value(&emit->qstr_map, i));
qstr qst = MP_OBJ_QSTR_VALUE(mp_map_slot_key(&emit->qstr_map, i));
context->constants.qstr_table[idx] = qst;
}
}

View file

@ -89,17 +89,31 @@ static size_t get_hash_alloc_greater_or_equal_to(size_t x) {
void mp_map_init(mp_map_t *map, size_t n) {
if (n == 0) {
map->alloc = 0;
#if MICROPY_MAP_SPLIT
map->keys.o = NULL;
map->values = NULL;
#else
map->table = NULL;
#endif
} else {
map->alloc = n;
#if MICROPY_MAP_SPLIT
map->keys.q = m_new0(qstr_short_t, map->alloc);
map->values = m_new0(mp_obj_t, map->alloc);
#else
map->table = m_new0(mp_map_elem_t, map->alloc);
#endif
}
map->used = 0;
map->all_keys_are_qstrs = 1;
map->is_fixed = 0;
#if MICROPY_MAP_SPLIT
map->values_mutable = 1;
#endif
map->is_ordered = 0;
}
#if !MICROPY_MAP_SPLIT
void mp_map_init_fixed_table(mp_map_t *map, size_t n, const mp_obj_t *table) {
map->alloc = n;
map->used = n;
@ -108,30 +122,71 @@ void mp_map_init_fixed_table(mp_map_t *map, size_t n, const mp_obj_t *table) {
map->is_ordered = 1;
map->table = (mp_map_elem_t *)table;
}
#endif
// Differentiate from mp_map_clear() - semantics is different
void mp_map_deinit(mp_map_t *map) {
static void mp_map_release_storage(mp_map_t *map) {
if (!map->is_fixed) {
#if MICROPY_MAP_SPLIT
if (map->all_keys_are_qstrs) {
m_del(short_qstr_t, map->keys.q, map->alloc);
} else {
m_del(mp_obj_t, map->keys.o, map->alloc);
}
map->keys.q = NULL;
m_del(mp_obj_t, map->values, map->alloc);
map->values = NULL;
#else
m_del(mp_map_elem_t, map->table, map->alloc);
map->table = NULL;
#endif
}
map->used = map->alloc = 0;
map->all_keys_are_qstrs = 1;
map->is_fixed = 0;
}
// Differentiate from mp_map_clear() - semantics is different
void mp_map_deinit(mp_map_t *map) {
mp_map_release_storage(map);
}
void mp_map_clear(mp_map_t *map) {
if (!map->is_fixed) {
m_del(mp_map_elem_t, map->table, map->alloc);
}
map->alloc = 0;
map->used = 0;
map->all_keys_are_qstrs = 1;
map->is_fixed = 0;
map->table = NULL;
mp_map_release_storage(map);
}
static void mp_map_rehash(mp_map_t *map) {
size_t old_alloc = map->alloc;
size_t new_alloc = get_hash_alloc_greater_or_equal_to(map->alloc + 1);
DEBUG_printf("mp_map_rehash(%p): " UINT_FMT " -> " UINT_FMT "\n", map, old_alloc, new_alloc);
#if MICROPY_MAP_SPLIT
bool all_keys_are_qstrs = map->all_keys_are_qstrs
mp_map_key_u old_keys = map->keys;
mp_map_key_u new_keys;
if (all_keys_are_qstrs) {
new_keys.q = m_new0(short_qstr_t, new_alloc)
} else {
new_keys.o = m_new0(mp_obj_t, new_alloc);
}
mp_obj_t *old_values = map->values;
mp_obj_t *new_values = m_new0(mp_map_elem_t, new_alloc);
// If we reach this point, table resizing succeeded, now we can edit the old map.
map->alloc = new_alloc;
map->used = 0;
map->keys = new_keys;
map->values = new_values;
for (size_t i = 0; i < old_alloc; i++) {
if (old_table[i].key != MP_OBJ_NULL && old_table[i].key != MP_OBJ_SENTINEL) {
mp_obj_t key;
if (all_keys_are_qstrs) {
key = MP_OBJ_NEW_QSTR(old_table.q[i]);
} else {
key = old_table.o[i];
}
mp_map_lookup(map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = old_values[i];
}
}
m_del(mp_map_elem_t, old_keys, old_alloc);
m_del(mp_map_elem_t, old_values, old_alloc);
#else
mp_map_elem_t *old_table = map->table;
mp_map_elem_t *new_table = m_new0(mp_map_elem_t, new_alloc);
// If we reach this point, table resizing succeeded, now we can edit the old map.
@ -145,6 +200,7 @@ static void mp_map_rehash(mp_map_t *map) {
}
}
m_del(mp_map_elem_t, old_table, old_alloc);
#endif
}
// MP_MAP_LOOKUP behaviour:

View file

@ -515,6 +515,12 @@
#define MICROPY_COMP_RETURN_IF_EXPR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
#endif
// Whether to use the "split map" format for maps.
// This adds code in the map lookup paths but reduces the size of the maps themselves.
#ifndef MICROPY_MAP_SPLIT
#define MICROPY_MAP_SPLIT (0)
#endif
/*****************************************************************************/
/* Internal debugging stuff */

View file

@ -413,6 +413,35 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t;
// These macros are used to define constant map/dict objects
// You can put "static" in front of the definition to make it local
#if MICROPY_MAP_SPLIT
#define MP_TABLE_SIZE(table_name) MP_ARRAY_SIZE(table_name##_keys)
#define MP_DEFINE_CONST_MAP(map_name, table_name) \
const mp_map_t map_name = { \
.all_keys_are_qstrs = 1, \
.is_fixed = 1, \
.is_ordered = 1, \
.used = MP_TABLE_SIZE(table_name), \
.alloc = MP_TABLE_SIZE(table_name), \
.keys.u = (qstr_short_t *)(table_name##_keys), \
.values = (mp_obj_t *)(table_name##_values), \
}
#define MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(dict_name, table_name, o, n) \
const mp_obj_dict_t dict_name = { \
.base = {&mp_type_dict}, \
.map = { \
.all_keys_are_qstrs = 1, \
.is_fixed = 1, \
.is_ordered = 1, \
.used = n, \
.alloc = n, \
.table = MP_MAP_TABLE_CAST(table_name), \
.keys.u = (qstr_short_t *)(table_name##_keys), \
.values = (mp_obj_t *)(table_name##_values), \
}, \
}
#else
#define MP_TABLE_SIZE(table_name) MP_ARRAY_SIZE(table_name)
#define MP_DEFINE_CONST_MAP(map_name, table_name) \
const mp_map_t map_name = { \
.all_keys_are_qstrs = 1, \
@ -423,7 +452,7 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t;
.table = (mp_map_elem_t *)(mp_rom_map_elem_t *)table_name, \
}
#define MP_DEFINE_CONST_DICT_WITH_SIZE(dict_name, table_name, n) \
#define MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(dict_name, table_name, o, n) \
const mp_obj_dict_t dict_name = { \
.base = {&mp_type_dict}, \
.map = { \
@ -432,11 +461,16 @@ typedef struct _mp_rom_obj_t { mp_const_obj_t o; } mp_rom_obj_t;
.is_ordered = 1, \
.used = n, \
.alloc = n, \
.table = (mp_map_elem_t *)(mp_rom_map_elem_t *)table_name, \
.table = (mp_map_elem_t *)(mp_rom_map_elem_t *)(table_name + o), \
}, \
}
#define MP_DEFINE_CONST_DICT(dict_name, table_name) MP_DEFINE_CONST_DICT_WITH_SIZE(dict_name, table_name, MP_ARRAY_SIZE(table_name))
#endif
#define MP_DEFINE_CONST_DICT_WITH_SIZE(dict_name, table_name, n) \
MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(dict_name, table_name, 0, n)
#define MP_DEFINE_CONST_DICT(dict_name, table_name) MP_DEFINE_CONST_DICT_WITH_SIZE(dict_name, table_name, MP_TABLE_SIZE(table_name))
// These macros are used to declare and define constant staticmethod and classmethod objects
// You can put "static" in front of the definitions to make them local
@ -479,13 +513,29 @@ typedef struct _mp_rom_map_elem_t {
mp_rom_obj_t value;
} mp_rom_map_elem_t;
#if MICROPY_MAP_SPLIT
typedef union _mp_map_key_u {
qstr_short_t *q;
mp_obj_t *o;
} mp_map_key_u;
#endif
typedef struct _mp_map_t {
size_t all_keys_are_qstrs : 1;
size_t is_fixed : 1; // if set, table is fixed/read-only and can't be modified
size_t is_ordered : 1; // if set, table is an ordered array, not a hash map
size_t used : (8 * sizeof(size_t) - 3);
size_t is_fixed : 1; // if set, table is fixed/read-only and can't be modified
#if MICROPY_MAP_SPLIT
size_t values_mutable : 1; // if set, table's values are mutable even if the map is_fixed
#else
#endif
size_t used : (8 * sizeof(size_t) - 3 - MICROPY_MAP_SPLIT);
size_t alloc;
#if MICROPY_MAP_SPLIT
mp_map_key_u keys;
mp_obj_t *values;
#else
mp_map_elem_t *table;
#endif
} mp_map_t;
// mp_set_lookup requires these constants to have the values they do
@ -496,9 +546,30 @@ typedef enum _mp_map_lookup_kind_t {
MP_MAP_LOOKUP_ADD_IF_NOT_FOUND_OR_REMOVE_IF_FOUND = 3, // only valid for mp_set_lookup
} mp_map_lookup_kind_t;
static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) {
static inline mp_obj_t mp_map_slot_key(const mp_map_t *map, size_t pos) {
assert(pos < map->alloc);
return (map)->table[pos].key != MP_OBJ_NULL && (map)->table[pos].key != MP_OBJ_SENTINEL;
#if MICROPY_MAP_SPLIT
if ((map)->all_keys_are_qstrs) {
return MP_OBJ_NEW_QSTR(map->keys.q[pos]);
} else {
return map->keys.o[pos];
}
#else
return map->values[pos].key;
#endif
}
static inline mp_obj_t mp_map_slot_value(const mp_map_t *map, size_t pos) {
assert(pos < map->alloc);
#if MICROPY_MAP_SPLIT
return map->keys.o[pos];
#else
return map->values[pos].value;
#endif
}
static inline bool mp_map_slot_is_filled(const mp_map_t *map, size_t pos) {
mp_obj_t key = mp_map_slot_key(map, pos);
return key != MP_OBJ_NULL && key != MP_OBJ_SENTINEL;
}
void mp_map_init(mp_map_t *map, size_t n);

View file

@ -2149,33 +2149,38 @@ static const mp_rom_map_elem_t array_bytearray_str_bytes_locals_table[] = {
#define TABLE_ENTRIES_ARRAY 0
#endif
MP_DEFINE_CONST_DICT_WITH_SIZE(mp_obj_str_locals_dict,
array_bytearray_str_bytes_locals_table + TABLE_ENTRIES_ARRAY + TABLE_ENTRIES_HEX + TABLE_ENTRIES_COMPAT,
MP_ARRAY_SIZE(array_bytearray_str_bytes_locals_table) - (TABLE_ENTRIES_ARRAY + TABLE_ENTRIES_HEX + TABLE_ENTRIES_COMPAT));
MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(mp_obj_str_locals_dict,
array_bytearray_str_bytes_locals_table,
0,
TABLE_ENTRIES_ARRAY + TABLE_ENTRIES_HEX + TABLE_ENTRIES_COMPAT);
#if TABLE_ENTRIES_COMPAT == 0
#define mp_obj_bytes_locals_dict mp_obj_str_locals_dict
#else
MP_DEFINE_CONST_DICT_WITH_SIZE(mp_obj_bytes_locals_dict,
array_bytearray_str_bytes_locals_table + TABLE_ENTRIES_ARRAY,
MP_ARRAY_SIZE(array_bytearray_str_bytes_locals_table) - (TABLE_ENTRIES_ARRAY + TABLE_ENTRIES_COMPAT));
array_bytearray_str_bytes_locals_table,
TABLE_ENTRIES_ARRAY,
MP_TABLE_SIZE(array_bytearray_str_bytes_locals_table) - (TABLE_ENTRIES_ARRAY + TABLE_ENTRIES_COMPAT));
#endif
#if MICROPY_PY_BUILTINS_BYTEARRAY
MP_DEFINE_CONST_DICT_WITH_SIZE(mp_obj_bytearray_locals_dict,
MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(mp_obj_bytearray_locals_dict,
array_bytearray_str_bytes_locals_table,
MP_ARRAY_SIZE(array_bytearray_str_bytes_locals_table) - TABLE_ENTRIES_COMPAT);
0,
MP_TABLE_SIZE(array_bytearray_str_bytes_locals_table) - TABLE_ENTRIES_COMPAT);
#endif
#if MICROPY_PY_ARRAY
MP_DEFINE_CONST_DICT_WITH_SIZE(mp_obj_array_locals_dict,
MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(mp_obj_array_locals_dict,
array_bytearray_str_bytes_locals_table,
0,
TABLE_ENTRIES_ARRAY);
#endif
#if MICROPY_PY_BUILTINS_MEMORYVIEW && MICROPY_PY_BUILTINS_BYTES_HEX
MP_DEFINE_CONST_DICT_WITH_SIZE(mp_obj_memoryview_locals_dict,
array_bytearray_str_bytes_locals_table + TABLE_ENTRIES_ARRAY,
MP_DEFINE_CONST_DICT_WITH_SIZE_AND_OFFSET(mp_obj_memoryview_locals_dict,
array_bytearray_str_bytes_locals_table,
TABLE_ENTRIES_ARRAY,
1); // Just the "hex" entry.
#endif

View file

@ -38,19 +38,23 @@
#define MP_ROM_TABLE_MERGED(storage, name, contents) const storage mp_rom_map_elem_t name[] = { \
BOOST_PP_SEQ_FOR_EACH(MP_TABLE_ENTRY, _, contents) \
}
#define MP_ROM_TABLE MP_ROM_TABLE_MERGED
// A hypothetical future format for ROM tables
#define MP_TABLE_KEY(r, x, element) BOOST_PP_TUPLE_ELEM(0, element),
#define MP_TABLE_VALUE(r, x, element) \
BOOST_PP_EXPAND(BOOST_PP_TUPLE_ELEM(1, element) (BOOST_PP_TUPLE_ELEM(2, element))),
#define MP_ROM_TABLE_SEPARATE(storage, name, contents) \
const storage struct { \
qstr_short_t keys[BOOST_PP_SEQ_SIZE(contents)]; \
mp_rom_obj_t values[BOOST_PP_SEQ_SIZE(contents)]; \
} name = { \
{ BOOST_PP_SEQ_FOR_EACH(MP_TABLE_KEY, _, contents) }, \
#define MP_ROM_TABLE_SPLIT(storage, name, contents) \
const storage qstr_short_t name##keys[] = { \
BOOST_PP_SEQ_FOR_EACH(MP_TABLE_KEY, _, contents) \
}, \
const storage mp_rom_obj_t name##values[] = { \
{ BOOST_PP_SEQ_FOR_EACH(MP_TABLE_VALUE, _, contents) }, \
}
#if MICROPY_MAP_SPLIT
#define MP_ROM_TABLE MP_ROM_TABLE_SPLIT
#else
#define MP_ROM_TABLE MP_ROM_TABLE_MERGED
#endif
#endif

View file

@ -889,12 +889,12 @@ void mp_call_prepare_args_n_kw_var(bool have_self, size_t n_args_n_kw, const mp_
for (size_t j = 0; j < map->alloc; j++) {
if (mp_map_slot_is_filled(map, j)) {
// the key must be a qstr, so intern it if it's a string
mp_obj_t key = map->table[j].key;
mp_obj_t key = mp_map_slot_key(map, j);
if (!mp_obj_is_qstr(key)) {
key = mp_obj_str_intern_checked(key);
}
args2[args2_len++] = key;
args2[args2_len++] = map->table[j].value;
args2[args2_len++] = mp_map_slot_key(map->table, j);
}
}
} else {
@ -1634,9 +1634,9 @@ void mp_import_all(mp_obj_t module) {
// Entry in module global scope may be generated programmatically
// (and thus be not a qstr for longer names). Avoid turning it in
// qstr if it has '_' and was used exactly to save memory.
const char *name = mp_obj_str_get_str(map->table[i].key);
const char *name = mp_obj_str_get_str(mp_map_slot_key(map->table, i));
if (*name != '_') {
qstr qname = mp_obj_str_get_qstr(map->table[i].key);
qstr qname = mp_obj_str_get_qstr(mp_map_slot_key(map->table, i));
mp_store_name(qname, map->table[i].value);
}
}