6.1 Lua词法
语言的解析一般是两遍遍历的过程,第一遍生成AST,第二遍将AST翻译为字节码。
Lua使用一遍扫描代码文件的方式生成字节码,以加快解释执行的速度。但缺点是代码比较难以理解。如
dostat -> DO block END
函数 -> 终结符 语法块 终结符
6.2 赋值类指令
--filename中的lua代码 local a = 10
使用myloadfile(个人测试代码)加载上述 filename 文件中的 lua 代码进行加载和解析:
int myloadfile(const char *filename) { lua_State *L = luaL_newstate(); luaL_openlibs(L); // open all the libs above if (luaL_loadfile(L, filename)) { error(L, "luaL_loadfile %s", lua_tostring(L, -1)); } lua_close(L); return 0; }
解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示
chunk -> { stat [';'] } statement -> localstat localstat -> LOCAL NAME {',' NAME} [ '=' explist1] explist1 -> expr {',' expr} expr -> subexpr subexpr -> simpleexp simpleexp -> NUMBER
详细函数调用顺序如下
int myloadfile(const char *filename); LUALIB_API int luaL_loadfile (lua_State *L, const char *filename); LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname); int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef); int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud); static void f_parser (lua_State *L, void *ud); Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name); // EBNF 对应函数 static void chunk (LexState *ls); static int statement (LexState *ls); static void localstat (LexState *ls); static int explist1 (LexState *ls, expdesc *v); static void expr (LexState *ls, expdesc *v); static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit); static void simpleexp (LexState *ls, expdesc *v);
EBNF 对应函数中,有一个贯穿始终的用于词法分析的中间临时变量ls(llex.h),结构体为:
typedef struct LexState { int current; /* current character (charint) */ int linenumber; /* input line counter */ int lastline; /* line of last token `consumed' */ Token t; /* current token */ Token lookahead; /* look ahead token */ struct FuncState *fs; /* `FuncState' is private to the parser */ struct lua_State *L; ZIO *z; /* input stream */ Mbuffer *buff; /* buffer for tokens */ TString *source; /* current source name */ char decpoint; /* locale decimal point */ } LexState;
ls->fs->freereg (int型) 存放的就是当前函数栈的下一个可用位置,在每个 chunk 函数中,都会根据当前函数栈存放的变量数量(包括函数的局部变量、函数的参数等)进行调整:
static void chunk (LexState *ls) { /* chunk -> { stat [`;'] } */ int islast = 0; enterlevel(ls); while (!islast && !block_follow(ls->t.token)) { islast = statement(ls); //此函数中将选择进入localstat testnext(ls, ';'); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg); lua_assert(ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /* free registers */ } leavelevel(ls); }
在函数 localstat(lparser.c) 中,循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量,对应的结构体是 LocVar,存放于ls->fs->f->locvars(LocVar *)
typedef struct LocVar { TString *varname; /* 变量名 */ int startpc; /* first point where variable is active */ int endpc; /* first point where variable is dead */ } LocVar;
localstat用于生成局部变量
static void localstat (LexState *ls) { /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */ int nvars = 0; int nexps; expdesc e; do { // 循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量。 new_localvar(ls, str_checkname(ls), nvars++); } while (testnext(ls, ',')); if (testnext(ls, '=')) nexps = explist1(ls, &e); else { e.k = VVOID; nexps = 0; } adjust_assign(ls, nvars, nexps, &e); adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar }
adjustlocalvars用于调整ls->fs->nactvar
static void adjustlocalvars (LexState *ls, int nvars) { FuncState *fs = ls->fs; fs->nactvar = cast_byte(fs->nactvar + nvars); for (; nvars; nvars--) { getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc; } }
#define getlocvar(fs, i) ((fs)->f->locvars[(fs)->actvar[i]])
下面介绍右边表达式结果的存储
解析表达式的结果会存储在一个临时数据结构 expdesc (lparser.h)中:
/* ** Expression descriptor */ typedef enum { VVOID, /* no value */ VNIL, VTRUE, VFALSE, VK, /* info = index of constant in `k' */ VKNUM, /* nval = numerical value */ VLOCAL, /* info = local register */ VUPVAL, /* info = index of upvalue in `upvalues' */ VGLOBAL, /* info = index of table; aux = index of global name in `k' */ VINDEXED, /* info = table register; aux = index register (or `k') */ VJMP, /* info = instruction pc */ VRELOCABLE, /* info = instruction pc */ VNONRELOC, /* info = result register */ VCALL, /* info = instruction pc */ VVARARG /* info = instruction pc */ } expkind; typedef struct expdesc { expkind k; /* 具体类型 */ union { struct { int info, aux; } s; lua_Number nval; } u; int t; /* patch list of `exit when true' */ int f; /* patch list of `exit when false' */ } expdesc;
localstat会调用explist1(lparser.c),将结果缓存在 expdesc 中
static int explist1 (LexState *ls, expdesc *v) { /* explist1 -> expr { `,' expr } */ int n = 1; /* at least one expression */ expr(ls, v); //解析表达式 while (testnext(ls, ',')) { //将表达式存入当前函数的下一个可用寄存器中 luaK_exp2nextreg(ls->fs, v); expr(ls, v); //解析表达式 n++; } return n; }
expr->subexpr ->simpleexp,赋值表达式右边为10,最终走入函数simpleexp(lparser.c)
static void simpleexp (LexState *ls, expdesc *v) { /* simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | FUNCTION body | primaryexp */ switch (ls->t.token) { case TK_NUMBER: { init_exp(v, VKNUM, 0); //VKNUM表示数字常量 //union u 的数据根据不同的类型会存储不同的信息,这里将10赋值给nval v->u.nval = ls->t.seminfo.r; break; }
void luaK_exp2nextreg (FuncState *fs, expdesc *e) { // 根据变量所在的不同作用域( local, global, upvalue )来决定这个变量是否需要重定向。 luaK_dischargevars(fs, e); freeexp(fs, e); // 分配可用的函数寄存器空间,得到这个空间对应的寄存器索引。有了空间,才能存储变量。 luaK_reserveregs(fs, 1); // 把表达式的数据放入寄存器空间。最终又会调用 discharge2reg 函数, // 这个函数式根据不同的表达式类型( NIL ,布尔表达式,数字等) 来生成存取表达式的值到寄存器的字节码。 exp2reg(fs, e, fs->freereg - 1); }
luaK_exp2nextreg -> discharge2reg(lcode.c)
static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { // ... ... case VKNUM: { luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval)); break; }
设置 fs->f->code[fs->pc] = i; // i 为 Instruction
int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) { lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx); lua_assert(getCMode(o) == OpArgN); return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline); }
如果左边的变量变成下面这样
--filename中的lua代码 local a = 10
回头看看前面的 localstat 函数最后两行代码
static void localstat (LexState *ls) { // ...... adjust_assign(ls, nvars, nexps, &e); // 根据等号两边变量和表达式的数量来调整赋值,b会赋值为nil adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar,并记录这些局部变量的 startpc值 }
再次修改示例
--filename中的lua代码 local a = 10 local b = a
Chunk:Spy反编译的指令如下:
; source chunk: c_call_lua/a.lua ; x86 standard (32-bit, little endian, doubles) ; function [0] definition (level 1) ; 0 upvalues, 0 params, 2 stacks .function 0 0 2 2 .local "a" ; 0 .local "b" ; 1 .const 1 ; 0 [1] loadk 0 0 ; 1 [2] move 1 0 [3] return 0 1 ; end of function
对于loadk,前面已经介绍过了。对于move指令,解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示
chunk -> { stat [';'] } statement -> localstat localstat -> LOCAL NAME {',' NAME} [ '=' explist1] explist1 -> expr {',' expr} expr -> subexpr subexpr -> simpleexp simpleexp -> primaryexp primaryexp-> prefixexp prefixexp -> NAME
与前面的区别是,这里走入了 primaryexp,然后在 prefixexp中判断这是一个变量时,会调用 singlevar(lparser.c) 进行变量查找
static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; if (singlevaraux(fs, varname, var, 1) == VGLOBAL) var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */ }
singlevar会调用singlevaraux进行递归查找
static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) { /* no more levels? */ init_exp(var, VGLOBAL, NO_REG); /* default is global variable */ return VGLOBAL; } else { int v = searchvar(fs, n); /* look up at current level */ if (v >= 0) { init_exp(var, VLOCAL, v); if (!base) markupval(fs, v); /* local will be used as an upval */ return VLOCAL; } else { /* not found at current level; try upper one */ if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL) return VGLOBAL; var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */ var->k = VUPVAL; /* upvalue in this level */ return VUPVAL; } } }
localstat -> adjust_assign -> luaK_exp2nextreg->luaK_dischargevars(lcode.c)根据三种不同的类型,有三种不同的操作
void luaK_dischargevars (FuncState *fs, expdesc *e) { switch (e->k) { case VLOCAL: { //局部变量不需要重定向,也无需额外的语句把这个值加载进来 e->k = VNONRELOC; break; }
localstat -> adjust_assign -> luaK_exp2nextreg -> exp2reg -> discharge2reg(lcode.c)
static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { // ...... case VNONRELOC: {// 不需要重定位,那么直接生成MOVE 指令来完成变量的赋值 if (reg != e->u.s.info) luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0); break; } default: { lua_assert(e->k == VVOID || e->k == VJMP); return; /* nothing to do... */ } } e->u.s.info = reg; e->k = VNONRELOC; }
赋值元源数据是局部变量,使用MOVE指令完成赋值。