diff --git a/py/builtin.c b/py/builtin.c
index 11268aed18a9ed2f90cca7b72f7b8fefca6d8280..a6d14fa48c927792f3ba2ced0d91e247f0929f29 100644
--- a/py/builtin.c
+++ b/py/builtin.c
@@ -20,10 +20,10 @@
 STATIC mp_obj_t mp_builtin___build_class__(uint n_args, const mp_obj_t *args) {
     assert(2 <= n_args);
 
-    // we differ from CPython: we set the new __locals__ object here
-    mp_map_t *old_locals = mp_locals_get();
+    // set the new classes __locals__ object
+    mp_obj_dict_t *old_locals = mp_locals_get();
     mp_obj_t class_locals = mp_obj_new_dict(0);
-    mp_locals_set(mp_obj_dict_get_map(class_locals));
+    mp_locals_set(class_locals);
 
     // call the class code
     mp_obj_t cell = mp_call_function_0(args[0]);
@@ -150,14 +150,14 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr);
 STATIC mp_obj_t mp_builtin_dir(uint n_args, const mp_obj_t *args) {
     // TODO make this function more general and less of a hack
 
-    mp_map_t *map = NULL;
+    mp_obj_dict_t *dict = NULL;
     if (n_args == 0) {
         // make a list of names in the local name space
-        map = mp_locals_get();
+        dict = mp_locals_get();
     } else { // n_args == 1
         // make a list of names in the given object
         if (MP_OBJ_IS_TYPE(args[0], &mp_type_module)) {
-            map = mp_obj_dict_get_map(mp_obj_module_get_globals(args[0]));
+            dict = mp_obj_module_get_globals(args[0]);
         } else {
             mp_obj_type_t *type;
             if (MP_OBJ_IS_TYPE(args[0], &mp_type_type)) {
@@ -166,16 +166,16 @@ STATIC mp_obj_t mp_builtin_dir(uint n_args, const mp_obj_t *args) {
                 type = mp_obj_get_type(args[0]);
             }
             if (type->locals_dict != MP_OBJ_NULL && MP_OBJ_IS_TYPE(type->locals_dict, &mp_type_dict)) {
-                map = mp_obj_dict_get_map(type->locals_dict);
+                dict = type->locals_dict;
             }
         }
     }
 
     mp_obj_t dir = mp_obj_new_list(0, NULL);
-    if (map != NULL) {
-        for (uint i = 0; i < map->alloc; i++) {
-            if (MP_MAP_SLOT_IS_FILLED(map, i)) {
-                mp_obj_list_append(dir, map->table[i].key);
+    if (dict != NULL) {
+        for (uint i = 0; i < dict->map.alloc; i++) {
+            if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
+                mp_obj_list_append(dir, dict->map.table[i].key);
             }
         }
     }
diff --git a/py/builtinevex.c b/py/builtinevex.c
index 4aafbdbe63e26a5c4839e2b43aca6ba150617b49..0a928199f04dbc599d154d29317ddd773023b0a7 100644
--- a/py/builtinevex.c
+++ b/py/builtinevex.c
@@ -54,8 +54,8 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_eval_obj, mp_builtin_eval);
 STATIC mp_obj_t mp_builtin_exec(uint n_args, const mp_obj_t *args) {
     // Unconditional getting/setting assumes that these operations
     // are cheap, which is the case when this comment was written.
-    mp_map_t *old_globals = mp_globals_get();
-    mp_map_t *old_locals = mp_locals_get();
+    mp_obj_dict_t *old_globals = mp_globals_get();
+    mp_obj_dict_t *old_locals = mp_locals_get();
     if (n_args > 1) {
         mp_obj_t globals = args[1];
         mp_obj_t locals;
@@ -64,8 +64,8 @@ STATIC mp_obj_t mp_builtin_exec(uint n_args, const mp_obj_t *args) {
         } else {
             locals = globals;
         }
-        mp_globals_set(mp_obj_dict_get_map(globals));
-        mp_locals_set(mp_obj_dict_get_map(locals));
+        mp_globals_set(globals);
+        mp_locals_set(locals);
     }
     mp_obj_t res = parse_compile_execute(args[0], MP_PARSE_FILE_INPUT);
     // TODO if the above call throws an exception, then we never get to reset the globals/locals
diff --git a/py/builtinimport.c b/py/builtinimport.c
index fd0689c6816e4141e94e2e2dbf5d1d5df72da996..e2137237f96783fa29ac65c614362297d1feba67 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -83,12 +83,12 @@ void do_load(mp_obj_t module_obj, vstr_t *file) {
     qstr source_name = mp_lexer_source_name(lex);
 
     // save the old context
-    mp_map_t *old_locals = mp_locals_get();
-    mp_map_t *old_globals = mp_globals_get();
+    mp_obj_dict_t *old_locals = mp_locals_get();
+    mp_obj_dict_t *old_globals = mp_globals_get();
 
     // set the new context
-    mp_locals_set(mp_obj_dict_get_map(mp_obj_module_get_globals(module_obj)));
-    mp_globals_set(mp_obj_dict_get_map(mp_obj_module_get_globals(module_obj)));
+    mp_locals_set(mp_obj_module_get_globals(module_obj));
+    mp_globals_set(mp_obj_module_get_globals(module_obj));
 
     // parse the imported script
     mp_parse_error_kind_t parse_error_kind;
diff --git a/py/builtintables.c b/py/builtintables.c
index 1edeefa441dd8321adb0a35e6dbbea908397c722..bc3d0deb16a3722100bba8087b9434b3c9b81849 100644
--- a/py/builtintables.c
+++ b/py/builtintables.c
@@ -8,148 +8,139 @@
 #include "builtintables.h"
 #include "objarray.h"
 
-// builtins
-// we put these tables in ROM because they're always needed and take up quite a bit of room in RAM
-// in fact, it uses less ROM here in table form than the equivalent in code form initialising a dynamic mp_map_t object in RAM
-// at the moment it's a linear table, but we could convert it to a const mp_map_t table with a simple preprocessing script
-
-typedef struct _mp_builtin_elem_t {
-    qstr qstr;
-    mp_obj_t elem;
-} mp_builtin_elem_t;
-
-STATIC const mp_builtin_elem_t builtin_object_table[] = {
+STATIC const mp_map_elem_t mp_builtin_object_table[] = {
     // built-in core functions
-    { MP_QSTR___build_class__, (mp_obj_t)&mp_builtin___build_class___obj },
-    { MP_QSTR___import__, (mp_obj_t)&mp_builtin___import___obj },
-    { MP_QSTR___repl_print__, (mp_obj_t)&mp_builtin___repl_print___obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR___build_class__), (mp_obj_t)&mp_builtin___build_class___obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR___import__), (mp_obj_t)&mp_builtin___import___obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR___repl_print__), (mp_obj_t)&mp_builtin___repl_print___obj },
 
     // built-in types
-    { MP_QSTR_bool, (mp_obj_t)&mp_type_bool },
-    { MP_QSTR_bytes, (mp_obj_t)&mp_type_bytes },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_bool), (mp_obj_t)&mp_type_bool },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_bytes), (mp_obj_t)&mp_type_bytes },
 #if MICROPY_ENABLE_FLOAT
-    { MP_QSTR_complex, (mp_obj_t)&mp_type_complex },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_complex), (mp_obj_t)&mp_type_complex },
 #endif
-    { MP_QSTR_dict, (mp_obj_t)&mp_type_dict },
-    { MP_QSTR_enumerate, (mp_obj_t)&mp_type_enumerate },
-    { MP_QSTR_filter, (mp_obj_t)&mp_type_filter },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_dict), (mp_obj_t)&mp_type_dict },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_enumerate), (mp_obj_t)&mp_type_enumerate },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_filter), (mp_obj_t)&mp_type_filter },
 #if MICROPY_ENABLE_FLOAT
-    { MP_QSTR_float, (mp_obj_t)&mp_type_float },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_float), (mp_obj_t)&mp_type_float },
 #endif
-    { MP_QSTR_int, (mp_obj_t)&mp_type_int },
-    { MP_QSTR_list, (mp_obj_t)&mp_type_list },
-    { MP_QSTR_map, (mp_obj_t)&mp_type_map },
-    { MP_QSTR_object, (mp_obj_t)&mp_type_object },
-    { MP_QSTR_set, (mp_obj_t)&mp_type_set },
-    { MP_QSTR_str, (mp_obj_t)&mp_type_str },
-    { MP_QSTR_super, (mp_obj_t)&mp_type_super },
-    { MP_QSTR_tuple, (mp_obj_t)&mp_type_tuple },
-    { MP_QSTR_type, (mp_obj_t)&mp_type_type },
-    { MP_QSTR_zip, (mp_obj_t)&mp_type_zip },
-
-    { MP_QSTR_classmethod, (mp_obj_t)&mp_type_classmethod },
-    { MP_QSTR_staticmethod, (mp_obj_t)&mp_type_staticmethod },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_int), (mp_obj_t)&mp_type_int },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_list), (mp_obj_t)&mp_type_list },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_map), (mp_obj_t)&mp_type_map },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_object), (mp_obj_t)&mp_type_object },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_set), (mp_obj_t)&mp_type_set },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_str), (mp_obj_t)&mp_type_str },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_super), (mp_obj_t)&mp_type_super },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_tuple), (mp_obj_t)&mp_type_tuple },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_type), (mp_obj_t)&mp_type_type },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_zip), (mp_obj_t)&mp_type_zip },
+
+    { MP_OBJ_NEW_QSTR(MP_QSTR_classmethod), (mp_obj_t)&mp_type_classmethod },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_staticmethod), (mp_obj_t)&mp_type_staticmethod },
 
     // built-in objects
-    { MP_QSTR_Ellipsis, (mp_obj_t)&mp_const_ellipsis_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_Ellipsis), (mp_obj_t)&mp_const_ellipsis_obj },
 
     // built-in user functions
-    { MP_QSTR_abs, (mp_obj_t)&mp_builtin_abs_obj },
-    { MP_QSTR_all, (mp_obj_t)&mp_builtin_all_obj },
-    { MP_QSTR_any, (mp_obj_t)&mp_builtin_any_obj },
-    { MP_QSTR_callable, (mp_obj_t)&mp_builtin_callable_obj },
-    { MP_QSTR_chr, (mp_obj_t)&mp_builtin_chr_obj },
-    { MP_QSTR_dir, (mp_obj_t)&mp_builtin_dir_obj },
-    { MP_QSTR_divmod, (mp_obj_t)&mp_builtin_divmod_obj },
-    { MP_QSTR_eval, (mp_obj_t)&mp_builtin_eval_obj },
-    { MP_QSTR_exec, (mp_obj_t)&mp_builtin_exec_obj },
-    { MP_QSTR_getattr, (mp_obj_t)&mp_builtin_getattr_obj },
-    { MP_QSTR_hash, (mp_obj_t)&mp_builtin_hash_obj },
-    { MP_QSTR_id, (mp_obj_t)&mp_builtin_id_obj },
-    { MP_QSTR_isinstance, (mp_obj_t)&mp_builtin_isinstance_obj },
-    { MP_QSTR_issubclass, (mp_obj_t)&mp_builtin_issubclass_obj },
-    { MP_QSTR_iter, (mp_obj_t)&mp_builtin_iter_obj },
-    { MP_QSTR_len, (mp_obj_t)&mp_builtin_len_obj },
-    { MP_QSTR_max, (mp_obj_t)&mp_builtin_max_obj },
-    { MP_QSTR_min, (mp_obj_t)&mp_builtin_min_obj },
-    { MP_QSTR_next, (mp_obj_t)&mp_builtin_next_obj },
-    { MP_QSTR_ord, (mp_obj_t)&mp_builtin_ord_obj },
-    { MP_QSTR_pow, (mp_obj_t)&mp_builtin_pow_obj },
-    { MP_QSTR_print, (mp_obj_t)&mp_builtin_print_obj },
-    { MP_QSTR_range, (mp_obj_t)&mp_builtin_range_obj },
-    { MP_QSTR_repr, (mp_obj_t)&mp_builtin_repr_obj },
-    { MP_QSTR_sorted, (mp_obj_t)&mp_builtin_sorted_obj },
-    { MP_QSTR_sum, (mp_obj_t)&mp_builtin_sum_obj },
-    { MP_QSTR_bytearray, (mp_obj_t)&mp_builtin_bytearray_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_abs), (mp_obj_t)&mp_builtin_abs_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_all), (mp_obj_t)&mp_builtin_all_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_any), (mp_obj_t)&mp_builtin_any_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_callable), (mp_obj_t)&mp_builtin_callable_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_chr), (mp_obj_t)&mp_builtin_chr_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_dir), (mp_obj_t)&mp_builtin_dir_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_divmod), (mp_obj_t)&mp_builtin_divmod_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_eval), (mp_obj_t)&mp_builtin_eval_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_exec), (mp_obj_t)&mp_builtin_exec_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_getattr), (mp_obj_t)&mp_builtin_getattr_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_hash), (mp_obj_t)&mp_builtin_hash_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_id), (mp_obj_t)&mp_builtin_id_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_isinstance), (mp_obj_t)&mp_builtin_isinstance_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_issubclass), (mp_obj_t)&mp_builtin_issubclass_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_iter), (mp_obj_t)&mp_builtin_iter_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_len), (mp_obj_t)&mp_builtin_len_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&mp_builtin_max_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&mp_builtin_min_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_next), (mp_obj_t)&mp_builtin_next_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_ord), (mp_obj_t)&mp_builtin_ord_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_pow), (mp_obj_t)&mp_builtin_pow_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_print), (mp_obj_t)&mp_builtin_print_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_range), (mp_obj_t)&mp_builtin_range_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_repr), (mp_obj_t)&mp_builtin_repr_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_sorted), (mp_obj_t)&mp_builtin_sorted_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&mp_builtin_sum_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_bytearray), (mp_obj_t)&mp_builtin_bytearray_obj },
 
     // built-in exceptions
-    { MP_QSTR_BaseException, (mp_obj_t)&mp_type_BaseException },
-    { MP_QSTR_ArithmeticError, (mp_obj_t)&mp_type_ArithmeticError },
-    { MP_QSTR_AssertionError, (mp_obj_t)&mp_type_AssertionError },
-    { MP_QSTR_AttributeError, (mp_obj_t)&mp_type_AttributeError },
-    { MP_QSTR_EOFError, (mp_obj_t)&mp_type_EOFError },
-    { MP_QSTR_Exception, (mp_obj_t)&mp_type_Exception },
-    { MP_QSTR_GeneratorExit, (mp_obj_t)&mp_type_GeneratorExit },
-    { MP_QSTR_IOError, (mp_obj_t)&mp_type_IOError },
-    { MP_QSTR_ImportError, (mp_obj_t)&mp_type_ImportError },
-    { MP_QSTR_IndentationError, (mp_obj_t)&mp_type_IndentationError },
-    { MP_QSTR_IndexError, (mp_obj_t)&mp_type_IndexError },
-    { MP_QSTR_KeyError, (mp_obj_t)&mp_type_KeyError },
-    { MP_QSTR_LookupError, (mp_obj_t)&mp_type_LookupError },
-    { MP_QSTR_MemoryError, (mp_obj_t)&mp_type_MemoryError },
-    { MP_QSTR_NameError, (mp_obj_t)&mp_type_NameError },
-    { MP_QSTR_NotImplementedError, (mp_obj_t)&mp_type_NotImplementedError },
-    { MP_QSTR_OSError, (mp_obj_t)&mp_type_OSError },
-    { MP_QSTR_OverflowError, (mp_obj_t)&mp_type_OverflowError },
-    { MP_QSTR_RuntimeError, (mp_obj_t)&mp_type_RuntimeError },
-    { MP_QSTR_StopIteration, (mp_obj_t)&mp_type_StopIteration },
-    { MP_QSTR_SyntaxError, (mp_obj_t)&mp_type_SyntaxError },
-    { MP_QSTR_SystemError, (mp_obj_t)&mp_type_SystemError },
-    { MP_QSTR_TypeError, (mp_obj_t)&mp_type_TypeError },
-    { MP_QSTR_ValueError, (mp_obj_t)&mp_type_ValueError },
-    { MP_QSTR_ZeroDivisionError, (mp_obj_t)&mp_type_ZeroDivisionError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_BaseException), (mp_obj_t)&mp_type_BaseException },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_ArithmeticError), (mp_obj_t)&mp_type_ArithmeticError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_AssertionError), (mp_obj_t)&mp_type_AssertionError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_AttributeError), (mp_obj_t)&mp_type_AttributeError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_EOFError), (mp_obj_t)&mp_type_EOFError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_Exception), (mp_obj_t)&mp_type_Exception },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_GeneratorExit), (mp_obj_t)&mp_type_GeneratorExit },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_IOError), (mp_obj_t)&mp_type_IOError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_ImportError), (mp_obj_t)&mp_type_ImportError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_IndentationError), (mp_obj_t)&mp_type_IndentationError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_IndexError), (mp_obj_t)&mp_type_IndexError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_KeyError), (mp_obj_t)&mp_type_KeyError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_LookupError), (mp_obj_t)&mp_type_LookupError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_MemoryError), (mp_obj_t)&mp_type_MemoryError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_NameError), (mp_obj_t)&mp_type_NameError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_NotImplementedError), (mp_obj_t)&mp_type_NotImplementedError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_OSError), (mp_obj_t)&mp_type_OSError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_OverflowError), (mp_obj_t)&mp_type_OverflowError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_RuntimeError), (mp_obj_t)&mp_type_RuntimeError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_StopIteration), (mp_obj_t)&mp_type_StopIteration },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_SyntaxError), (mp_obj_t)&mp_type_SyntaxError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_SystemError), (mp_obj_t)&mp_type_SystemError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_TypeError), (mp_obj_t)&mp_type_TypeError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_ValueError), (mp_obj_t)&mp_type_ValueError },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_ZeroDivisionError), (mp_obj_t)&mp_type_ZeroDivisionError },
     // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/
     // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation
 
     // Extra builtins as defined by a port
     MICROPY_EXTRA_BUILTINS
+};
 
-    { MP_QSTR_, MP_OBJ_NULL }, // end of list sentinel
+const mp_obj_dict_t mp_builtin_object_dict_obj = {
+    .base = {&mp_type_dict},
+    .map = {
+        .all_keys_are_qstrs = 1,
+        .table_is_fixed_array = 1,
+        .used = sizeof(mp_builtin_object_table) / sizeof(mp_map_elem_t),
+        .alloc = sizeof(mp_builtin_object_table) / sizeof(mp_map_elem_t),
+        .table = (mp_map_elem_t*)mp_builtin_object_table,
+    },
 };
 
-STATIC const mp_builtin_elem_t builtin_module_table[] = {
-    { MP_QSTR___main__, (mp_obj_t)&mp_module___main__ },
-    { MP_QSTR_micropython, (mp_obj_t)&mp_module_micropython },
+STATIC const mp_map_elem_t mp_builtin_module_table[] = {
+    { MP_OBJ_NEW_QSTR(MP_QSTR___main__), (mp_obj_t)&mp_module___main__ },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_micropython), (mp_obj_t)&mp_module_micropython },
 
-    { MP_QSTR_array, (mp_obj_t)&mp_module_array },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_array), (mp_obj_t)&mp_module_array },
 #if MICROPY_ENABLE_MOD_IO
-    { MP_QSTR_io, (mp_obj_t)&mp_module_io },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_io), (mp_obj_t)&mp_module_io },
 #endif
-    { MP_QSTR_collections, (mp_obj_t)&mp_module_collections },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_collections), (mp_obj_t)&mp_module_collections },
 
 #if MICROPY_ENABLE_FLOAT
-    { MP_QSTR_math, (mp_obj_t)&mp_module_math },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_math), (mp_obj_t)&mp_module_math },
 #endif
 
     // extra builtin modules as defined by a port
     MICROPY_EXTRA_BUILTIN_MODULES
-
-    { MP_QSTR_, MP_OBJ_NULL }, // end of list sentinel
 };
 
-STATIC mp_obj_t mp_builtin_tables_lookup(const mp_builtin_elem_t *table, qstr q) {
-    for (; table->qstr != MP_QSTR_; table++) {
-        if (table->qstr == q) {
-            return table->elem;
-        }
-    }
-    return MP_OBJ_NULL;
-}
-
-mp_obj_t mp_builtin_tables_lookup_object(qstr q) {
-    return mp_builtin_tables_lookup(&builtin_object_table[0], q);
-}
-
-mp_obj_t mp_builtin_tables_lookup_module(qstr q) {
-    return mp_builtin_tables_lookup(&builtin_module_table[0], q);
-}
+const mp_obj_dict_t mp_builtin_module_dict_obj = {
+    .base = {&mp_type_dict},
+    .map = {
+        .all_keys_are_qstrs = 1,
+        .table_is_fixed_array = 1,
+        .used = sizeof(mp_builtin_module_table) / sizeof(mp_map_elem_t),
+        .alloc = sizeof(mp_builtin_module_table) / sizeof(mp_map_elem_t),
+        .table = (mp_map_elem_t*)mp_builtin_module_table,
+    },
+};
diff --git a/py/builtintables.h b/py/builtintables.h
index 9b22b66e032359bfb6167ff167582c30b417a1cf..caea79f31c538dbd70b8a0da7f3cde8ff37ec8c2 100644
--- a/py/builtintables.h
+++ b/py/builtintables.h
@@ -1,2 +1,2 @@
-mp_obj_t mp_builtin_tables_lookup_object(qstr q);
-mp_obj_t mp_builtin_tables_lookup_module(qstr q);
+extern const mp_obj_dict_t mp_builtin_object_dict_obj;
+extern const mp_obj_dict_t mp_builtin_module_dict_obj;
diff --git a/py/objfun.c b/py/objfun.c
index d006c8715adbd9ce703e0f496190f6e144d0b197..0b0df3ba4db6cdc81e841eb176a763d324dc6746 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -152,7 +152,7 @@ mp_obj_t mp_make_function_var_between(int n_args_min, int n_args_max, mp_fun_var
 
 typedef struct _mp_obj_fun_bc_t {
     mp_obj_base_t base;
-    mp_map_t *globals;      // the context within which this function was defined
+    mp_obj_dict_t *globals; // the context within which this function was defined
     machine_uint_t n_args : 15;         // number of arguments this function takes
     machine_uint_t n_def_args : 15;     // number of default arguments
     machine_uint_t takes_var_args : 1;  // set if this function takes variable args
@@ -326,7 +326,7 @@ continue2:;
         }
     }
 
-    mp_map_t *old_globals = mp_globals_get();
+    mp_obj_dict_t *old_globals = mp_globals_get();
     mp_globals_set(self->globals);
     mp_obj_t result;
     DEBUG_printf("Calling: args=%p, n_args=%d, extra_args=%p, n_extra_args=%d\n", args, n_args, extra_args, n_extra_args);
diff --git a/py/objmodule.c b/py/objmodule.c
index 783aa8a6fc1340697a152e2ed7e20067bc1909df..df7c991b8cc5eb04926458198fc98d5417b3b7f5 100644
--- a/py/objmodule.c
+++ b/py/objmodule.c
@@ -86,14 +86,16 @@ mp_obj_t mp_module_get(qstr module_name) {
     // lookup module
     mp_map_elem_t *el = mp_map_lookup(&mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
 
-    // module found, return it
-    if (el != NULL) {
-        return el->value;
+    if (el == NULL) {
+        // module not found, look for builtin module names
+        el = mp_map_lookup((mp_map_t*)&mp_builtin_module_dict_obj.map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
+        if (el == NULL) {
+            return MP_OBJ_NULL;
+        }
     }
 
-    // module not found, look for builtin module names
-    // it will return MP_OBJ_NULL if nothing found
-    return mp_builtin_tables_lookup_module(module_name);
+    // module found, return it
+    return el->value;
 }
 
 void mp_module_register(qstr qstr, mp_obj_t module) {
diff --git a/py/runtime.c b/py/runtime.c
index a8bd36bfac6eea0e33b2d325efd0ce6e6a4bdbf6..f9361a8a5ed2c1b3039ff1b8a7736adaa03600f0 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -29,10 +29,10 @@
 #endif
 
 // locals and globals need to be pointers because they can be the same in outer module scope
-STATIC mp_map_t *map_locals;
-STATIC mp_map_t *map_globals;
-STATIC mp_map_t map_builtins;
+STATIC mp_obj_dict_t *dict_locals;
+STATIC mp_obj_dict_t *dict_globals;
 
+// dictionary for the __main__ module
 STATIC mp_obj_dict_t dict_main;
 
 const mp_obj_module_t mp_module___main__ = {
@@ -47,15 +47,12 @@ void mp_init(void) {
     // init global module stuff
     mp_module_init();
 
+    // initialise the __main__ module
     mp_obj_dict_init(&dict_main, 1);
-    // add some builtins that can't be done in ROM
     mp_obj_dict_store(&dict_main, MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR___main__));
 
     // locals = globals for outer module (see Objects/frameobject.c/PyFrame_New())
-    map_locals = map_globals = &dict_main.map;
-
-    // init built-in hash table
-    mp_map_init(&map_builtins, 3);
+    dict_locals = dict_globals = &dict_main;
 
 #if MICROPY_CPYTHON_COMPAT
     // Precreate sys module, so "import sys" didn't throw exceptions.
@@ -70,8 +67,7 @@ void mp_init(void) {
 }
 
 void mp_deinit(void) {
-    mp_map_free(map_globals);
-    mp_map_deinit(&map_builtins);
+    //mp_obj_dict_free(&dict_main);
     mp_module_deinit();
     mp_emit_glue_deinit();
 }
@@ -97,10 +93,10 @@ mp_obj_t mp_load_const_bytes(qstr qstr) {
 
 mp_obj_t mp_load_name(qstr qstr) {
     // logic: search locals, globals, builtins
-    DEBUG_OP_printf("load name %s\n", map_locals, qstr_str(qstr));
+    DEBUG_OP_printf("load name %s\n", qstr_str(qstr));
     // If we're at the outer scope (locals == globals), dispatch to load_global right away
-    if (map_locals != map_globals) {
-        mp_map_elem_t *elem = mp_map_lookup(map_locals, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
+    if (dict_locals != dict_globals) {
+        mp_map_elem_t *elem = mp_map_lookup(&dict_locals->map, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
         if (elem != NULL) {
             return elem->value;
         }
@@ -111,14 +107,11 @@ mp_obj_t mp_load_name(qstr qstr) {
 mp_obj_t mp_load_global(qstr qstr) {
     // logic: search globals, builtins
     DEBUG_OP_printf("load global %s\n", qstr_str(qstr));
-    mp_map_elem_t *elem = mp_map_lookup(map_globals, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
+    mp_map_elem_t *elem = mp_map_lookup(&dict_globals->map, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
     if (elem == NULL) {
-        elem = mp_map_lookup(&map_builtins, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
+        // TODO lookup in dynamic table of builtins first
+        elem = mp_map_lookup((mp_map_t*)&mp_builtin_object_dict_obj.map, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP);
         if (elem == NULL) {
-            mp_obj_t o = mp_builtin_tables_lookup_object(qstr);
-            if (o != MP_OBJ_NULL) {
-                return o;
-            }
             nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_NameError, "name '%s' is not defined", qstr_str(qstr)));
         }
     }
@@ -127,31 +120,25 @@ mp_obj_t mp_load_global(qstr qstr) {
 
 mp_obj_t mp_load_build_class(void) {
     DEBUG_OP_printf("load_build_class\n");
-    // lookup __build_class__ in dynamic table of builtins first
-    mp_map_elem_t *elem = mp_map_lookup(&map_builtins, MP_OBJ_NEW_QSTR(MP_QSTR___build_class__), MP_MAP_LOOKUP);
-    if (elem != NULL) {
-        // found user-defined __build_class__, return it
-        return elem->value;
-    } else {
-        // no user-defined __build_class__, return builtin one
-        return (mp_obj_t)&mp_builtin___build_class___obj;
-    }
+    // TODO lookup __build_class__ in dynamic table of builtins first
+    // ... else no user-defined __build_class__, return builtin one
+    return (mp_obj_t)&mp_builtin___build_class___obj;
 }
 
 void mp_store_name(qstr qstr, mp_obj_t obj) {
     DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qstr), obj);
-    mp_map_lookup(map_locals, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = obj;
+    mp_obj_dict_store(dict_locals, MP_OBJ_NEW_QSTR(qstr), obj);
 }
 
 void mp_delete_name(qstr qstr) {
     DEBUG_OP_printf("delete name %s\n", qstr_str(qstr));
     // TODO raise NameError if qstr not found
-    mp_map_lookup(map_locals, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
+    mp_map_lookup(&dict_locals->map, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
 }
 
 void mp_store_global(qstr qstr, mp_obj_t obj) {
     DEBUG_OP_printf("store global %s <- %p\n", qstr_str(qstr), obj);
-    mp_map_lookup(map_globals, MP_OBJ_NEW_QSTR(qstr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = obj;
+    mp_obj_dict_store(dict_globals, MP_OBJ_NEW_QSTR(qstr), obj);
 }
 
 mp_obj_t mp_unary_op(int op, mp_obj_t arg) {
@@ -1020,22 +1007,22 @@ void mp_import_all(mp_obj_t module) {
     }
 }
 
-mp_map_t *mp_locals_get(void) {
-    return map_locals;
+mp_obj_dict_t *mp_locals_get(void) {
+    return dict_locals;
 }
 
-void mp_locals_set(mp_map_t *m) {
-    DEBUG_OP_printf("mp_locals_set(%p)\n", m);
-    map_locals = m;
+void mp_locals_set(mp_obj_dict_t *d) {
+    DEBUG_OP_printf("mp_locals_set(%p)\n", d);
+    dict_locals = d;
 }
 
-mp_map_t *mp_globals_get(void) {
-    return map_globals;
+mp_obj_dict_t *mp_globals_get(void) {
+    return dict_globals;
 }
 
-void mp_globals_set(mp_map_t *m) {
-    DEBUG_OP_printf("mp_globals_set(%p)\n", m);
-    map_globals = m;
+void mp_globals_set(mp_obj_dict_t *d) {
+    DEBUG_OP_printf("mp_globals_set(%p)\n", d);
+    dict_globals = d;
 }
 
 void *m_malloc_fail(int num_bytes) {
diff --git a/py/runtime.h b/py/runtime.h
index 6b3ab73d8f759115c78666993f9f1fc7d7f78f3b..a627fa509b0b20383ef3e4ea95746a94e7d9c961 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -9,10 +9,10 @@ void mp_deinit(void);
 
 void mp_check_nargs(int n_args, machine_uint_t n_args_min, machine_uint_t n_args_max, int n_kw, bool is_kw);
 
-mp_map_t *mp_locals_get(void);
-void mp_locals_set(mp_map_t *m);
-mp_map_t *mp_globals_get(void);
-void mp_globals_set(mp_map_t *m);
+mp_obj_dict_t *mp_locals_get(void);
+void mp_locals_set(mp_obj_dict_t *d);
+mp_obj_dict_t *mp_globals_get(void);
+void mp_globals_set(mp_obj_dict_t *d);
 
 mp_obj_t mp_load_name(qstr qstr);
 mp_obj_t mp_load_global(qstr qstr);
diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h
index 0bb11cae5ab83fdb4229dc4d3c6d4cf2a4381a02..08ba923e405f7e1cf1c0b5b91c1a383866ca4e0f 100644
--- a/stmhal/mpconfigport.h
+++ b/stmhal/mpconfigport.h
@@ -24,18 +24,18 @@ extern const struct _mp_obj_fun_native_t mp_builtin_help_obj;
 extern const struct _mp_obj_fun_native_t mp_builtin_input_obj;
 extern const struct _mp_obj_fun_native_t mp_builtin_open_obj;
 #define MICROPY_EXTRA_BUILTINS \
-    { MP_QSTR_help, (mp_obj_t)&mp_builtin_help_obj }, \
-    { MP_QSTR_input, (mp_obj_t)&mp_builtin_input_obj }, \
-    { MP_QSTR_open, (mp_obj_t)&mp_builtin_open_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_help), (mp_obj_t)&mp_builtin_help_obj }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_input), (mp_obj_t)&mp_builtin_input_obj }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj },
 
 // extra built in modules to add to the list of known ones
 extern const struct _mp_obj_module_t os_module;
 extern const struct _mp_obj_module_t pyb_module;
 extern const struct _mp_obj_module_t time_module;
 #define MICROPY_EXTRA_BUILTIN_MODULES \
-    { MP_QSTR_os, (mp_obj_t)&os_module }, \
-    { MP_QSTR_pyb, (mp_obj_t)&pyb_module }, \
-    { MP_QSTR_time, (mp_obj_t)&time_module }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_os), (mp_obj_t)&os_module }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_pyb), (mp_obj_t)&pyb_module }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&time_module }, \
 
 // type definitions for the specific machine
 
diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h
index b8ee020f1ec8ffd3b17733eb008bb1e78d371c96..3798f88b5835af6c8ed04ccf99b33e06843c4bdc 100644
--- a/unix/mpconfigport.h
+++ b/unix/mpconfigport.h
@@ -34,4 +34,4 @@ typedef const void *machine_const_ptr_t; // must be of pointer size
 struct _mp_obj_fun_native_t;
 extern const struct _mp_obj_fun_native_t mp_builtin_open_obj;
 #define MICROPY_EXTRA_BUILTINS \
-    { MP_QSTR_open, (mp_obj_t)&mp_builtin_open_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj },