6、标识符与相应对象的联系
Micropython中有很多标识符,我们的例子lcd.py中出现的标识符有:import、lcd、init、print、”hello”。这些标识符最终都需要与某个对象或操作联系起来。那么这种联系是如何建立的呢?
6.1 QSTR
QSTR是uniQue STRing的简称,是一种字符串内存驻留方法。我们知道同一个标识符可能在源代码中出现多次,如果我们在每个出现的地方都要保留一份这个标识符的拷贝,就会相当占用存储空间。
Micropython采取的方式是在存储空间内仅保留一份标识符主体,而每个标识符主体都有一个索引号,代码中凡是使用这个标识符的地方,都使用其索引号代替。
而最终执行时就通过这个索引号去内存中寻找对应的标识符。这就是Micropython中所谓的QSTR。
Micropython的C代码中所有需要使用QSTR的地方都用MP_QSTR_xxx表示,比如lcd就用MP_QSTR_lcd表示,这个MP_QSTR_lcd就是lcd这个标识符的索引号。
Micropython的Makefile会搜索C源文件中的所有MP_QSTR_xxx,并生成QSTR池,其中存放了QSTR对应的标识符的长度和哈希值。
Micropython的C代码中的QSTR定义最终会被放入mpy-cross/build/genhdr/qstrdefs.generated.h中:
// This file was automatically generated by makeqstrdata.py
QDEF(MP_QSTRnull, (const byte*)"\x00\x00\x00" "")
QDEF(MP_QSTR_, (const byte*)"\x05\x15\x00" "")
QDEF(MP_QSTR___dir__, (const byte*)"\x7a\x8f\x07" " dir ")
QDEF(MP_QSTR__0x0a_, (const byte*)"\xaf\xb5\x01" "\x0a")
QDEF(MP_QSTR__space_, (const byte*)"\x85\xb5\x01" " ")
QDEF(MP_QSTR__star_, (const byte*)"\x8f\xb5\x01" " *")
QDEF(MP_QSTR__slash_, (const byte* )"\x8a\xb5\x01" "/")
QDEF(MP_QSTR__lt_module_gt_, (const byte*)"\xbd\x94\x08" "")
QDEF(MP_QSTR__, (const byte*)"\xfa\xb5\x01" "_")
QDEF(MP_QSTR___call__, (const byte*)"\xa7\xf9\x08" " call ")
QDEF(MP_QSTR___class__, (const byte*)"\x2b\xc5\x09" " class ")
QDEF(MP_QSTR___delitem__, (const byte*)"\xfd\x35\x0b" " delitem ")
QDEF(MP_QSTR___enter__, (const byte*)"\x6d\xba\x09" " enter ")
QDEF(MP_QSTR___exit__, (const byte*)"\x45\xf8\x08" " exit ")
QDEF(MP_QSTR___getattr__, (const byte*)"\x40\xf8\x0b" " getattr ")
QDEF(MP_QSTR___getitem__, (const byte*)"\x26\x39\x0b" " getitem ")
QDEF(MP_QSTR___hash__, (const byte*)"\xf7\xc8\x08" " hash ")
QDEF(MP_QSTR___init__, (const byte*)"\x5f\xa5\x08" " init ")
QDEF(MP_QSTR___int__, (const byte*)"\x16\x1b\x07" " int ")
QDEF(MP_QSTR___iter__, (const byte*)"\xcf\x32\x08" " iter ")
QDEF(MP_QSTR___len__, (const byte*)"\xe2\xb0\x07" " len ")
QDEF(MP_QSTR___main__, (const byte*)"\x8e\x13\x08" " main ")
QDEF(MP_QSTR___module__, (const byte*)"\xff\x30\x0a" " module ")
...
其中是一系列的宏定义,格式为:
QDEF(MP_QSTR_xxx, (const byte*)"哈希值(2字节)长度(1字节)" "对应的字符串"
以QDEF(MP_QSTR___module__, (const byte*)"\xff\x30\x0a" " module ")为例,
MP_QSTR___module__是这个QSTR的索引号,\xff\x30是”module“的哈希值,而\x0a是”module“的长度。
这些宏定义是怎么被安排进QSTR池的呢?
QSTR池在py/qstr.c中定义:
/* py/qstr.c */
const qstr_pool_t mp_qstr_const_pool = {
NULL, // no previous pool
0, // no previous pool
MICROPY_ALLOC_QSTR_ENTRIES_INIT,
MP_QSTRnumber_of, // corresponds to number of strings in array just below
{
#ifndef NO_QSTR
#define QDEF(id, str) str,
#include "genhdr/qstrdefs.generated.h"
#undef QDEF
#endif
},
};
宏展开后为:
const qstr_pool_t mp_qstr_const_pool = {
NULL, // no previous pool
0, // no previous pool
MICROPY_ALLOC_QSTR_ENTRIES_INIT,
MP_QSTRnumber_of, // corresponds to number of strings in array just below
{
#ifndef NO_QSTR
(const byte*)"\x00\x00\x00" "",
(const byte*)"\x05\x15\x00" "",
(const byte*)"\x7a\x8f\x07" " dir ",
...
#endif
},
这些QSTR的索引号定义在py/qstr.h中,用一个枚举体表示:
/* py/qstr.h */
// first entry in enum will be MP_QSTRnull=0, which indicates invalid/no qstr
enum {
#ifndef NO_QSTR
#define QDEF(id, str) id,
#include "genhdr/qstrdefs.generated.h"
#undef QDEF
#endif
MP_QSTRnumber_of, // no underscore so it can't clash with any of the above
};
宏展开后为:
enum {
#ifndef NO_QSTR
MP_QSTRnull,
MP_QSTR_,
MP_QSTR___dir__,
...
#endif
MP_QSTRnumber_of, // no underscore so it can't clash with any of the above
};
这样就可以用MP_QSTR_xxx为索引去QSTR池中找到相应的QSTR了。
当然这些是C代码中已经出现的QSTR,而Micropython源代码中新出现的标识符,也会被转换成相应的QSTR加入另一个QSTR池,这个QSTR池会通过上面mp_qstr_const_pool的前两个字段与其链接起来。
6.2 查找标识符
我们以例子lcd.py里出现的标识符为例,依次看一下找到这些标识符对应的对象或操作的过程。
6.2.1 import lcd
在“5. 执行”章节的末尾,我们知道源代码中的import最终会执行mp_import_name函数:
/* py/runtime.c */
mp_obj_t mp_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level) {
DEBUG_printf("import name '%s' level=%d\n", qstr_str(name), MP_OBJ_SMALL_INT_VALUE(level));
// build args array
mp_obj_t args[5];
args[0] = MP_OBJ_NEW_QSTR(name);
args[1] = mp_const_none; // TODO should be globals
args[2] = mp_const_none; // TODO should be locals
args[3] = fromlist;
args[4] = level;
#if MICROPY_CAN_OVERRIDE_BUILTINS
// Lookup import and call that if it exists
mp_obj_dict_t *bo_dict = MP_STATE_VM(mp_module_builtins_override_dict);
if (bo_dict != NULL) {
mp_map_elem_t *import = mp_map_lookup(&bo_dict->map, MP_OBJ_NEW_QSTR(MP_QSTR___import__), MP_MAP_LOOKUP);
if (import != NULL) {
return mp_call_function_n_kw(import->value, 5, 0, args);
}
}
#endif
return mp_builtin___import__(5, args);
}
最后调用mp_builtin___import__完成lcd模块的加载。
注:在我们的例子中,lcd模块是作为内置模块存在的。
/* py/builtinimport.c */
mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) {
...
mp_obj_t module_name = args[0];
...
size_t mod_len;
const char *mod_str = mp_obj_str_get_data(module_name, &mod_len);
...
if (mod_len == 0) {
mp_raise_ValueError(NULL);
}
// check if module already exists
qstr module_name_qstr = mp_obj_str_get_qstr(module_name);
mp_obj_t module_obj = mp_module_get(module_name_qstr);
if (module_obj != MP_OBJ_NULL) {
DEBUG_printf("Module already loaded\n");
// If it's not a package, return module right away
char *p = strchr(mod_str, '.');
if (p == NULL) {
return module_obj;
}
...
}
...
}
mp_module_get函数查找模块是否已加载:
/* py/objmodule.c */
// returns MP_OBJ_NULL if not found
mp_obj_t mp_module_get(qstr module_name) {
mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map;
// lookup module
mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
if (el == NULL) {
// module not found, look for builtin module names
el = mp_map_lookup((mp_map_t *)&mp_builtin_module_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);
if (el == NULL) {
return MP_OBJ_NULL;
}
mp_module_call_init(module_name, el->value);
}
// module found, return it
return el->value;
}
最终在mp_builtin_module_map中找到lcd模块并返回。
在mp_builtin_module_map中我们会看到类似如下的一行:
{MP_ROM_QSTR(MP_QSTR_lcd), MP_ROM_PTR(&lcd_module)}
lcd_module就是我们定义的lcd模块,上面这行就把在lcd这个名字与lcd模块间建立了联系。
6.2.2 lcd.init()
从lcd.py生成的bytecode里可以看到,在MP_BC_IMPORT_NAME qstr_lcd[0:7] qstr_lcd[8:15]之后是MP_BC_STORE_NAME qstr_lcd[0:7] qstr_lcd[8:15],从字面意思看是要把QSTR_lcd存储起来。在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_STORE_NAME): {
MARK_EXC_IP_SELECTIVE();
DECODE_QSTR;
mp_store_name(qst, POP());
DISPATCH();
}
调用mp_store_name:
/* py/runtime.c */
void mp_store_name(qstr qst, mp_obj_t obj) {
DEBUG_OP_printf("store name %s <- %p\n", qstr_str(qst), obj);
mp_obj_dict_store(MP_OBJ_FROM_PTR(mp_locals_get()), MP_OBJ_NEW_QSTR(qst), obj);
}
也就是把QSTR_lcd存到了虚拟机的局部符号表里,而此时局部符号表与全局符号表其实是同一个表。
当要调用lcd的init方法时,要先找到lcd模块,在bytecode中对应MP_BC_LOAD_NAME。在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_LOAD_NAME): {
MARK_EXC_IP_SELECTIVE();
DECODE_QSTR;
PUSH(mp_load_name(qst));
DISPATCH();
}
mp_load_name->mp_load_global->mp_map_lookup(&mp_globals_get()->map,...),最后会在虚拟机的全局符号表中找到lcd模块,因为前面MP_BC_STORE_NAME已经把存入了。
调用init方法对应的bytecode首先是MP_BC_LOAD_METHOD qstr_init[0:7] qstr_init[8:15],在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_LOAD_METHOD): {
MARK_EXC_IP_SELECTIVE();
DECODE_QSTR;
mp_load_method(*sp, qst, sp);
sp += 1;
DISPATCH();
}
mp_load_method->mp_load_method_maybe->type->attr(...),type对应的是lcd模块的类型,即mp_type_module。因此最后调用的就是mp_type_module的attr方法:
/* py/objmodule.c */
const mp_obj_type_t mp_type_module = {
{ &mp_type_type },
.name = MP_QSTR_module,
.print = module_print,
.attr = module_attr,
};
mp_type_module的attr方法实际是调用module_attr:
/* py/objmodule.c */
STATIC void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in);
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;
...
}
} else {
// delete/store attribute
...
}
}
可见这里就是在lcd模块的全局符号表中查找init,在lcd模块的全局符号表中会有如下一行:
static const mp_map_elem_t globals_dict_table[] = {
...
{ MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&py_lcd_init_obj },
...
}
py_lcd_init_obj就是用于初始化的函数对象,这样就在init这个名字与lcd模块的init方法之间建立了联系。
bytecode中接下来是MP_BC_CALL_METHOD,在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_CALL_METHOD): {
FRAME_UPDATE();
MARK_EXC_IP_SELECTIVE();
DECODE_UINT;
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe) + 1;
#if MICROPY_STACKLESS
if (mp_obj_get_type(*sp) == &mp_type_fun_bc) {
code_state->ip = ip;
code_state->sp = sp;
code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp);
size_t n_args = unum & 0xff;
size_t n_kw = (unum >> 8) & 0xff;
int adjust = (sp[1] == MP_OBJ_NULL) ? 0 : 1;
mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, n_args + adjust, n_kw, sp + 2 - adjust);
#if !MICROPY_ENABLE_PYSTACK
if (new_state == NULL) {
#if MICROPY_STACKLESS_STRICT
goto deep_recursion_error;
#endif
} else
#endif
{
new_state->prev = code_state;
code_state = new_state;
nlr_pop();
goto run_code_state;
}
}
#endif
SET_TOP(mp_call_method_n_kw(unum & 0xff, (unum >> 8) & 0xff, sp));
DISPATCH();
}
mp_call_method_n_kw->mp_call_function_n_kw->type->call,这里的type是init对应的函数对象py_lcd_init_obj的类型。
在Micropython中定义函数对象的方法如下:
static mp_obj_t py_lcd_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args)
{
...
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_init_obj, 0, py_lcd_init);
首先定义函数主体;
然后通过MP_DEFINE_CONST_FUN_OBJ_KW宏定义函数对象。
除了MP_DEFINE_CONST_FUN_OBJ_KW可以定义函数对象,还有另外几个宏也完成类似的功能,包括:
MP_DEFINE_CONST_FUN_OBJ_0、MP_DEFINE_CONST_FUN_OBJ_1、MP_DEFINE_CONST_FUN_OBJ_2、MP_DEFINE_CONST_FUN_OBJ_3、MP_DEFINE_CONST_FUN_OBJ_VAR、MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN。这些宏之间的区别是对于函数可以接受的参数个数有不同的约束。
这里以MP_DEFINE_CONST_FUN_OBJ_KW为例,看一下其定义:
/* py/obj.h */
#define MP_DEFINE_CONST_FUN_OBJ_KW(obj_name, n_args_min, fun_name)
const mp_obj_fun_builtin_var_t obj_name =
{{&mp_type_fun_builtin_var}, MP_OBJ_FUN_MAKE_SIG(n_args_min, MP_OBJ_FUN_ARGS_MAX, true), .fun.kw = fun_name}
将上面的py_lcd_init_obj例子代入展开后为:
const mp_obj_fun_builtin_var_t py_lcd_init_obj=
{
{&mp_type_fun_builtin_var},
MP_OBJ_FUN_MAKE_SIG(0, MP_OBJ_FUN_ARGS_MAX, true),
.fun.kw = py_lcd_init
}
可见,这个宏实际是定义了一个mp_obj_fun_builtin_var_t对象,其类型为mp_type_fun_builtin_var,并把函数指针赋值给其.fun.kw字段。
所以执行MP_BC_CALL_METHOD最后的type->call,实际就是调用的mp_type_fun_builtin_var类型的call方法:
/* py/objfun.c */
const mp_obj_type_t mp_type_fun_builtin_var = {
{ &mp_type_type },
.flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN,
.name = MP_QSTR_function,
.call = fun_builtin_var_call,
.unary_op = mp_generic_unary_op,
};
对应调用的是fun_builtin_var_call函数:
/* py/objfun.c */
STATIC mp_obj_t fun_builtin_var_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
assert(mp_obj_is_type(self_in, &mp_type_fun_builtin_var));
mp_obj_fun_builtin_var_t *self = MP_OBJ_TO_PTR(self_in);
// check number of arguments
mp_arg_check_num_sig(n_args, n_kw, self->sig);
if (self->sig & 1) {
// function allows keywords
// we create a map directly from the given args array
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
return self->fun.kw(n_args, args, &kw_args);
} else {
// function takes a variable number of arguments, but no keywords
return self->fun.var(n_args, args);
}
}
在其中会调用函数对象的fun.kw方法,在我们的例子中也就是调用了py_lcd_init函数。
6.2.3 print(“hello”)
首先要找到print,在bytecode中对应MP_BC_LOAD_NAME qstr_print[0:7] qstr_print[8:15],与查找lcd的过程类似,通过mp_load_name->mp_load_global->mp_map_lookup((mp_map_t *)&mp_module_builtins_globals.map,...),在mp_module_builtins_globals.map中找到:
STATIC const mp_rom_map_elem_t mp_module_builtins_globals_table[] = {
...
{ MP_ROM_QSTR(MP_QSTR_print), MP_ROM_PTR(&mp_builtin_print_obj) },
...
}
MP_DEFINE_CONST_DICT(mp_module_builtins_globals, mp_module_builtins_globals_table);
接下来要找到”hello”,在bytecode中对应MP_BC_LOAD_CONST_STRING qstr_hello[0:7] qstr_hello[8:15],在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_LOAD_CONST_STRING): {
DECODE_QSTR;
PUSH(MP_OBJ_NEW_QSTR(qst));
DISPATCH();
}
这个比较简单,就是根据”hello”的QSTR索引号生成一个QSTR对象。
最后是对”hello”调用print函数,对应bytecode里的MP_BC_CALL_FUNCTION,在mp_execute_bytecode中会执行如下分支:
ENTRY(MP_BC_CALL_FUNCTION): {
FRAME_UPDATE();
MARK_EXC_IP_SELECTIVE();
DECODE_UINT;
sp -= (unum & 0xff) + ((unum >> 7) & 0x1fe);
#if MICROPY_STACKLESS
if (mp_obj_get_type(*sp) == &mp_type_fun_bc) {
code_state->ip = ip;
code_state->sp = sp;
code_state->exc_sp_idx = MP_CODE_STATE_EXC_SP_IDX_FROM_PTR(exc_stack, exc_sp);
mp_code_state_t *new_state = mp_obj_fun_bc_prepare_codestate(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1);
#if !MICROPY_ENABLE_PYSTACK
if (new_state == NULL) {
#if MICROPY_STACKLESS_STRICT
deep_recursion_error:
mp_raise_recursion_depth();
#endif
} else
#endif
{
new_state->prev = code_state;
code_state = new_state;
nlr_pop();
goto run_code_state;
}
}
#endif
SET_TOP(mp_call_function_n_kw(*sp, unum & 0xff, (unum >> 8) & 0xff, sp + 1));
DISPATCH();
}
实际调用过程与上述init基本相同,此处不再赘述。