Lua VM等二三事
TODO: 更新进展
Luac Introduction
本次参考lua5-4.2的源代码,luac的主体代码在luac.c文件中,通过ldump.c中的luaU_dump函数将lua代码编译luac文件输出至输出文件中。执行则是在undump中。
int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data,
int strip) {
DumpState D;
D.L = L;
D.writer = w;
D.data = data;
D.strip = strip;
D.status = 0;
dumpHeader(&D);
dumpByte(&D, f->sizeupvalues);
dumpFunction(&D, f, NULL);
return D.status;
}
实验中采用learnxinyminutes中提供的learnlua.lua作为源代码,如下命令获取编译后的luac文件
luac -o learnlua.luac learnlua.lua
后文中的一些lua代码也会作为例子,用来对比之类的。另外我还创建了一个空的lua文件,方便查看编译后的结果。lua中的main拥有可变参数列表,且包含一个upvalue–LUA_ENV。
>>> touch none.lua
>>> luac -o none.luac none.lua
>>> luac -l none.lua
main <none.lua:0,0> (2 instructions at 0x55e43b8eacc0)
0+ params, 2 slots, 1 upvalue, 0 locals, 0 constants, 0 functions
1 [1] VARARGPREP 0
2 [1] RETURN 0 1 1 ; 0 out
>>> r2 none.luac
[0x00000000]> px
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00000000 1b4c 7561 5400 1993 0d0a 1a0a 0408 0878 .LuaT..........x
0x00000010 5600 0000 0000 0000 0000 0000 2877 4001 V...........(w@.
0x00000020 8a40 6e6f 6e65 2e6c 7561 8080 0001 0282 .@none.lua......
0x00000030 5100 0000 4600 0101 8081 0100 0080 8201 Q...F...........
0x00000040 0080 8081 855f 454e 56ff ffff ffff ffff ....._ENV.......
0x00000050 ffff ffff ffff ffff ffff ffff ffff ffff ................
Luac Helper Function
这一部分内容主要是方便分析时进行参考,可暂时略过。Dump(即所谓"compile")过程中常见到的一个结构体DumpState在后续dump过程中多用于锁操作(strip则是由命令行的-s选项控制是否去除debug信息),对于解析luac文件可暂时忽略其技术细节,这里为了方便还是贴上DumpState的定义。
typedef struct {
lua_State *L;
lua_Writer writer;
void *data;
int strip;
int status;
} DumpState;
辅助函数,dumpBlock实现的功能类似memcpy。
// Base Dump Function, similar to 'memcpy'
static void dumpBlock (DumpState *D, const void *b, size_t size) {
if (D->status == 0 && size > 0) {
lua_unlock(D->L);
D->status = (*D->writer)(D->L, b, size, D->data);
lua_lock(D->L);
}
}
// Macros For Dumping Basic Objects
#define dumpLiteral(D, s) dumpBlock(D,s,sizeof(s) - sizeof(char))
#define dumpVector(D,v,n) dumpBlock(D,v,(n)*sizeof((v)[0]))
#define dumpVar(D,x) dumpVector(D,&x,1)
// wrapper for dumping the size of Object
// tricky function
// : transform the size -> several bytes in reverse order
// every byte's MSB is 0 except the last byte.
// and keep the size of 'size' as small as possible
// the size_t is uint32 in my machine so DIBS is 10
#define DIBS ((sizeof(size_t) * 8 / 7) + 1)
static void dumpSize (DumpState *D, size_t x) {
lu_byte buff[DIBS];
int n = 0;
do {
buff[DIBS - (++n)] = x & 0x7f; /* fill buffer in reverse order */
x >>= 7;
} while (x != 0);
buff[DIBS - 1] |= 0x80; /* mark last byte */
dumpVector(D, buff + DIBS - n, n);
}
上述基础函数组合对具体的数据类型进行dump
static void dumpByte (DumpState *D, int y) {
lu_byte x = (lu_byte)y;
dumpVar(D, x);
}
static void dumpInt (DumpState *D, int x) {
dumpSize(D, x);
}
static void dumpNumber (DumpState *D, lua_Number x) {
dumpVar(D, x);
}
static void dumpInteger (DumpState *D, lua_Integer x) {
dumpVar(D, x);
}
// dump format
// -----------------------------
// | SizeOfThisString | String |
// -----------------------------
//
static void dumpString (DumpState *D, const TString *s) {
if (s == NULL)
dumpSize(D, 0);
else {
size_t size = tsslen(s);
const char *str = getstr(s);
dumpSize(D, size + 1);
dumpVector(D, str, size);
}
}
剩下的一些主要是封装了对lua脚本中存在的各个constant、proto等数据的统一dump,打包dumpFunction这个函数中。
Other Data Struct
typedef struct Proto {
CommonHeader;
lu_byte numparams; /* number of fixed (named) parameters */
lu_byte is_vararg;
lu_byte maxstacksize; /* number of registers needed by this function */
int sizeupvalues; /* size of 'upvalues' */
int sizek; /* size of 'k' */
int sizecode;
int sizelineinfo;
int sizep; /* size of 'p' */
int sizelocvars;
int sizeabslineinfo; /* size of 'abslineinfo' */
int linedefined; /* debug information */
int lastlinedefined; /* debug information */
TValue *k; /* constants used by the function */
Instruction *code; /* opcodes */
struct Proto **p; /* functions defined inside the function */
Upvaldesc *upvalues; /* upvalue information */
ls_byte *lineinfo; /* information about source lines (debug information) */
AbsLineInfo *abslineinfo; /* idem */
LocVar *locvars; /* information about local variables (debug information) */
TString *source; /* used for debug information */
GCObject *gclist;
} Proto;
Luac Header Format
dumpHeader函数用于将luac header写入luac文件中,这里为了方便把相关的定义都放在一起。说实话我不太理解LUAC_DATA的作用,注释中写着用于错误处理,但那个1993感觉更像是一种纪念性质的MagicNumber?它只在后续undump中checkHeader时从文件中加载然后比较原始的LUAC_DATA宏,以此判断是否发生文件损坏。
/* mark for precompiled code ('<esc>Lua') */
#define LUA_SIGNATURE "\x1bLua"
#define LUA_VERSION_MAJOR "5"
#define LUA_VERSION_MINOR "4"
#define MYINT(s) (s[0]-'0') /* assume one-digit numerals */
/* Version is 0x54 in this case */
#define LUAC_VERSION (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR))
#define LUAC_FORMAT 0 /* this is the official format */
/* data to catch conversion errors */
#define LUAC_DATA "\x19\x93\r\n\x1a\n"
/* Related Types */
#define LUA_INTEGER long long
#define LUA_NUMBER double
#define LUAC_INT 0x5678
#define LUAC_NUM cast_num(370.5)
typedef l_uint32 Instruction;
typedef LUA_INTEGER lua_Integer;
typedef LUA_NUMBER lua_Number;
/* Force Cast */
#define cast(t, exp) ((t)(exp))
#define cast_num(i) cast(lua_Number, (i))
static void dumpHeader (DumpState *D) {
dumpLiteral(D, LUA_SIGNATURE);
dumpByte(D, LUAC_VERSION);
dumpByte(D, LUAC_FORMAT);
dumpLiteral(D, LUAC_DATA);
dumpByte(D, sizeof(Instruction));
dumpByte(D, sizeof(lua_Integer));
dumpByte(D, sizeof(lua_Number));
dumpInteger(D, LUAC_INT);
dumpNumber(D, LUAC_NUM);
}
这里分别将SIGNATURE,LUAC_VERSION等元信息保存在luac header中。可总结出Header中各成员对应的偏移位置(以5.4.2为例)。最后的两个dump对象在后续的undump中分别被作为int和float载入,以检查int和float的编译格式是否正确。(类似测试用例)
| offset (int Hex) | offset (in Byte) | member_desc | value |
|---|---|---|---|
| 0x00-0x03 | 0-3 | lua’s signature | “\x{=tex}1bLua” |
| 0x04 | 4 | luac’s version | 0x54 |
| 0x05 | 5 | luac’s format | 0x00 |
| 0x06-0x0B | 6-11 (6B) | luac data | “\x19{=tex}\x93{=tex}\r\n{=tex}\x{=tex}1a\n{=tex}” |
| 0x0C | 12 | type-Instruction’s size (uint32) | 0x04 |
| 0x0D | 13 | type-lua_integer’s size (long long) | 0x08 |
| 0x0E | 14 | type-lua_Number’s size (double) | 0x08 |
| 0x0F-0x16 | 15-22 (8B) | check if the integer format is matched | “\x78{=tex}\x56{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}” |
| 0x17-0x1E | 23-30 (8B) | check if the float format is matched | “\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x28{=tex}\x77{=tex}\x40{=tex}” |
| 0x1F | 31 | number of upvalue arrays | 1 at least (as I know) |
前文中的learnlua.lua编译后的部分二进制数据如下,可以对照参考。
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00000000 1b4c 7561 5400 1993 0d0a 1a0a 0408 0878 .LuaT..........x
0x00000010 5600 0000 0000 0000 0000 0000 2877 4001 V...........(w@.
0x00000020 8e40 6c65 6172 6e6c 7561 2e6c 7561 8080 .@learnlua.lua..
0x00000030 0001 0b02 a851 0000 000f 8000 010f 8002 .....Q..........
0x1F位置的代码来依照代码中的定义应该不属于luac header,但可以与header中的定义合并到一起处理,暂且也作为header的一部分吧。
dumpByte(&D, f->sizeupvalues);
upvalue涉及lua中变量作用域的概念,可理解为在作用域内的一种局部变量。下面例子的局部变量n即为f2的upvalue(f1的本地局部变量),由于n为f2的upvalue,f2被赋值给g1,n尽管是局部变量但并不会因为f1的执行完成而结束生命周期,而是继续留存在内存空间中。(参加ref3)
function f1(n)
local function f2()
print(n)
end
n = n + 10
return f2
end
g1 = f1(1979)
g1() -- print out 1989
可以通过luac -l看看各闭包的参数
> luac -l upv.lua
main <upv.lua:0,0> (10 instructions at 0x55c54d5e7cc0)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 1 function
1 [1] VARARGPREP 0
2 [6] CLOSURE 0 0 ; 0x55c54d5e7f20
3 [1] SETTABUP 0 0 0 ; _ENV "f1"
4 [7] GETTABUP 0 0 0 ; _ENV "f1"
5 [7] LOADI 1 1979
6 [7] CALL 0 2 2 ; 1 in 1 out
7 [7] SETTABUP 0 1 0 ; _ENV "g1"
8 [8] GETTABUP 0 0 1 ; _ENV "g1"
9 [8] CALL 0 1 1 ; 0 in 0 out
10 [8] RETURN 0 1 1 ; 0 out
function <upv.lua:1,6> (3 instructions at 0x55c54d5e7f20)
1 param, 2 slots, 1 upvalue, 2 locals, 0 constants, 1 function
1 [4] CLOSURE 1 0 ; 0x55c54d5e8240
2 [5] RETURN 1 2 0 ; 1 out
3 [6] RETURN 2 1 0 ; 0 out
function <upv.lua:2,4> (4 instructions at 0x55c54d5e8240)
0 params, 2 slots, 2 upvalues, 0 locals, 1 constant, 0 functions
1 [3] GETTABUP 0 0 0 ; _ENV "print"
2 [3] GETUPVAL 1 1 ; n
3 [3] CALL 0 2 1 ; 1 in 0 out
Luac Body (Functions)
luaU_dump的调用的最后一个函数即为dumpFunction,它完成了luac文件剩下数据的生成。
static void dumpFunction (DumpState *D, const Proto *f, TString *psource) {
if (D->strip || f->source == psource)
dumpString(D, NULL); /* no debug info or same source as its parent */
else
dumpString(D, f->source);
dumpInt(D, f->linedefined);
dumpInt(D, f->lastlinedefined);
dumpByte(D, f->numparams);
dumpByte(D, f->is_vararg);
dumpByte(D, f->maxstacksize);
dumpCode(D, f);
dumpConstants(D, f);
dumpUpvalues(D, f);
dumpProtos(D, f);
dumpDebug(D, f);
}
Offset Overview
Source File Info
这里调用链为dumpString->dumpSize,简单来说就是将源文件名保存(可以用-s去除),格式为源文件名长度 源文件名。尽管dumpSize设计时支持多字节长度(通过检测字节最高位bit是否为1判断是否为size变量的最后一个字节),这里在处理源文件名时通常都为1个字节,实验环境下的linux我难以用编辑工具创建更长的文件名了。
因此通常情况下可以用size_byte & 0x7f获得源文件名的长度。若要追求更高的鲁棒性就只能老老实实采用上面的loadSize完成。
如果是stripped之后的luac文件,该字节设置为0x80,代表0值,无长度。
Function Prototype Info
首先是两个int变量,各占4字节。且虽说这东西也算某种意义上的debug info, 但luac在处理这个信息时并不会受-s选项控制,而是忠实地记录下行号。
- linedefined 函数定义起始处的行号
- lastlinedefined 函数定义结束处的行号
luac文件的开头描述的是main这一对象,因此在开头的linedefined和lastlinedefined值都为0。目前我并不知道有什么技巧可以定义lua中的main,因此我倾向于认为在任何luac文件里最初的这两个值都是0。(另外就是由于dumpInt底层实现为dumpSize,所以在luac文件这两个位置上的字节都是0x80)
- numparams 代表函数参数的个数,这个很好理解
- is_vararg 是否为变长参数,类似printf(…)这样的函数。
这两个参量都比较容易理解,开头的main是可变参的,此外大多文件不涉及main故参数个数为0,它们采用的是dumpByte因此luac开头对应位置上的字节分别为0x00和0x01(可变)
- maxstacksize 记录的是函数所需的最大寄存器数目,这个名字可能是历史遗留。由于0号和1号寄存器永远保持可用,故这个值至少会是2
总结一下,上述信息都能在luac -l xxx.luac输出的结果中找到,其中slot对应的就是maxstacksize
Function Code Info
格式为"code_size + code_data",与前文中所提到的字符串dump方法相同(dumpCode中对size的dump也采用dumpSize实现,对数据的dump则是使用dumpVector)。这些数据都是对应的opcode数据,至于代码到opcode的转换则是在其他地方完成的。
- constants的dump首先dump保存了constants的数目(依旧是
dumpSize,后续相同则省略)。对每个constant都用TAG标记变量类型,因此dump的时候先dumpTAG(本质为dumpByte),再调用变量对应的dump函数进行操作。TAG的类型和对应的dump函数一并记上。- LUA_VNUMFLT – dumpNuber
- LUA_VNUMINT – dumpInteger
- LUA_VSHRSTR – dumpString
- LUA_VLNGSTR – dumpString
- LUA_VNIL / LUA_VFALSE / LUA_VTRUE
- upvalues 则是记录sizeupvalues,再对upvalue的数据进行dump。每个upvalue dump占3个字节,分别为upvalue中的属性:
- instack
- idx
- kind
Protos
这一步首先记录所有的proto数,之后便是遍历所有proto,调用dumpFunction进行处理,递归的过程。
Debug Info – Controled by Strip Option
luac的-s参数控制,若是stripped的版本以下这些信息都会用0x80填充。
- lineinfo – size + lineinfo_datas (vector) 源代码行数信息
- abslineinfo – size + abslineinfo_datas 每个data都dump两个属性,absline用于构建源代码位置(line)和指令(pc)的对应关系
- pc – dumpInt
- line – dumpInt
- localvars – size + local_var_datas,本地变量,每个data对如下三个属性进行dump
- varname – dumpString
- startpc – dumpInt 变量第一次激活所对应的指令位置
- endpc – dumpInt 变量第一次死亡的指令位置
- upvalues – size + upvalue_datas,对每个data只dump一个属性
- name – dumpString
Luac Opcodes
Opcode Format
opcode的格式等定义于lopcodes.h当中,共计五种类型的指令。
/*===========================================================================
We assume that instructions are unsigned 32-bit integers.
All instructions have an opcode in the first 7 bits.
Instructions can have the following formats:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
iABC C(8) | B(8) |k| A(8) | Op(7) |
iABx Bx(17) | A(8) | Op(7) |
iAsBx sBx (signed)(17) | A(8) | Op(7) |
iAx Ax(25) | Op(7) |
isJ sJ(25) | Op(7) |
A signed argument is represented in excess K: the represented value is
the written unsigned value minus K, where K is half the maximum for the
corresponding unsigned argument.
===========================================================================*/
完整的opcode指令采用enum给出,注释中也标记了所属的指令类型
typedef enum {
/*----------------------------------------------------------------------
name args description
------------------------------------------------------------------------*/
OP_MOVE,/* A B R[A] := R[B] */
OP_LOADI,/* A sBx R[A] := sBx */
OP_LOADF,/* A sBx R[A] := (lua_Number)sBx */
OP_LOADK,/* A Bx R[A] := K[Bx] */
OP_LOADKX,/* A R[A] := K[extra arg] */
OP_LOADFALSE,/* A R[A] := false */
OP_LFALSESKIP,/*A R[A] := false; pc++ */
OP_LOADTRUE,/* A R[A] := true */
OP_LOADNIL,/* A B R[A], R[A+1], ..., R[A+B] := nil */
OP_GETUPVAL,/* A B R[A] := UpValue[B] */
OP_SETUPVAL,/* A B UpValue[B] := R[A] */
OP_GETTABUP,/* A B C R[A] := UpValue[B][K[C]:string] */
OP_GETTABLE,/* A B C R[A] := R[B][R[C]] */
OP_GETI,/* A B C R[A] := R[B][C] */
OP_GETFIELD,/* A B C R[A] := R[B][K[C]:string] */
OP_SETTABUP,/* A B C UpValue[A][K[B]:string] := RK(C) */
OP_SETTABLE,/* A B C R[A][R[B]] := RK(C) */
OP_SETI,/* A B C R[A][B] := RK(C) */
OP_SETFIELD,/* A B C R[A][K[B]:string] := RK(C) */
OP_NEWTABLE,/* A B C k R[A] := {} */
OP_SELF,/* A B C R[A+1] := R[B]; R[A] := R[B][RK(C):string] */
OP_ADDI,/* A B sC R[A] := R[B] + sC */
OP_ADDK,/* A B C R[A] := R[B] + K[C] */
OP_SUBK,/* A B C R[A] := R[B] - K[C] */
OP_MULK,/* A B C R[A] := R[B] * K[C] */
OP_MODK,/* A B C R[A] := R[B] % K[C] */
OP_POWK,/* A B C R[A] := R[B] ^ K[C] */
OP_DIVK,/* A B C R[A] := R[B] / K[C] */
OP_IDIVK,/* A B C R[A] := R[B] // K[C] */
OP_BANDK,/* A B C R[A] := R[B] & K[C]:integer */
OP_BORK,/* A B C R[A] := R[B] | K[C]:integer */
OP_BXORK,/* A B C R[A] := R[B] ~ K[C]:integer */
OP_SHRI,/* A B sC R[A] := R[B] >> sC */
OP_SHLI,/* A B sC R[A] := sC << R[B] */
OP_ADD,/* A B C R[A] := R[B] + R[C] */
OP_SUB,/* A B C R[A] := R[B] - R[C] */
OP_MUL,/* A B C R[A] := R[B] * R[C] */
OP_MOD,/* A B C R[A] := R[B] % R[C] */
OP_POW,/* A B C R[A] := R[B] ^ R[C] */
OP_DIV,/* A B C R[A] := R[B] / R[C] */
OP_IDIV,/* A B C R[A] := R[B] // R[C] */
OP_BAND,/* A B C R[A] := R[B] & R[C] */
OP_BOR,/* A B C R[A] := R[B] | R[C] */
OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */
OP_SHL,/* A B C R[A] := R[B] << R[C] */
OP_SHR,/* A B C R[A] := R[B] >> R[C] */
OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] */
OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */
OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */
OP_UNM,/* A B R[A] := -R[B] */
OP_BNOT,/* A B R[A] := ~R[B] */
OP_NOT,/* A B R[A] := not R[B] */
OP_LEN,/* A B R[A] := #R[B] (length operator) */
OP_CONCAT,/* A B R[A] := R[A].. ... ..R[A + B - 1] */
OP_CLOSE,/* A close all upvalues >= R[A] */
OP_TBC,/* A mark variable A "to be closed" */
OP_JMP,/* sJ pc += sJ */
OP_EQ,/* A B k if ((R[A] == R[B]) ~= k) then pc++ */
OP_LT,/* A B k if ((R[A] < R[B]) ~= k) then pc++ */
OP_LE,/* A B k if ((R[A] <= R[B]) ~= k) then pc++ */
OP_EQK,/* A B k if ((R[A] == K[B]) ~= k) then pc++ */
OP_EQI,/* A sB k if ((R[A] == sB) ~= k) then pc++ */
OP_LTI,/* A sB k if ((R[A] < sB) ~= k) then pc++ */
OP_LEI,/* A sB k if ((R[A] <= sB) ~= k) then pc++ */
OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */
OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */
OP_TEST,/* A k if (not R[A] == k) then pc++ */
OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] */
OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */
OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */
OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */
OP_RETURN0,/* return */
OP_RETURN1,/* A return R[A] */
OP_FORLOOP,/* A Bx update counters; if loop continues then pc-=Bx; */
OP_FORPREP,/* A Bx <check values and prepare counters>;
if not to run then pc+=Bx+1; */
OP_TFORPREP,/* A Bx create upvalue for R[A + 3]; pc+=Bx */
OP_TFORCALL,/* A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]); */
OP_TFORLOOP,/* A Bx if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */
OP_SETLIST,/* A B C k R[A][C+i] := R[A+i], 1 <= i <= B */
OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */
OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */
OP_VARARGPREP,/*A (adjust vararg parameters) */
OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
} OpCode;
Generate Opcode
lua代码到opcode的转换实现在lparser中,此功能的入口为luaY_parser --> mainfunc,先对main进行编译,之后对main中定义的函数进行编译,是一个递归的过程。
luaY_parser负责一些准备工作,处理主要在mainfunc里,新的lua版本出于性能等考虑放弃了yacc实现,采用手写的parser,因此该文件里有相当一部分函数是用于处理代码解析token的。
/*
** compiles the main function, which is a regular vararg function with an
** upvalue named LUA_ENV
*/
static void mainfunc (LexState *ls, FuncState *fs) {
BlockCnt bl;
Upvaldesc *env;
open_func(ls, fs, &bl);
setvararg(fs, 0); /* main function is always declared vararg */
env = allocupvalue(fs); /* ...set environment upvalue */
env->instack = 1;
env->idx = 0;
env->kind = VDKREG;
env->name = ls->envn;
luaC_objbarrier(ls->L, fs->f, env->name);
luaX_next(ls); /* read first token */
statlist(ls); /* parse main body */
check(ls, TK_EOS);
close_func(ls);
}
而从token到instruction的过程则是在statement中完成。statlist内部循环调用statement处理这些token
static void statement (LexState *ls) {
int line = ls->linenumber; /* may be needed for error messages */
enterlevel(ls);
switch (ls->t.token) {
case ';': { /* stat -> ';' (empty statement) */
luaX_next(ls); /* skip ';' */
break;
}
case TK_IF: { /* stat -> ifstat */
ifstat(ls, line);
break;
}
case TK_WHILE: { /* stat -> whilestat */
whilestat(ls, line);
break;
}
case TK_DO: { /* stat -> DO block END */
luaX_next(ls); /* skip DO */
block(ls);
check_match(ls, TK_END, TK_DO, line);
break;
}
case TK_FOR: { /* stat -> forstat */
forstat(ls, line);
break;
}
case TK_REPEAT: { /* stat -> repeatstat */
repeatstat(ls, line);
break;
}
case TK_FUNCTION: { /* stat -> funcstat */
funcstat(ls, line);
break;
}
case TK_LOCAL: { /* stat -> localstat */
luaX_next(ls); /* skip LOCAL */
if (testnext(ls, TK_FUNCTION)) /* local function? */
localfunc(ls);
else
localstat(ls);
break;
}
case TK_DBCOLON: { /* stat -> label */
luaX_next(ls); /* skip double colon */
labelstat(ls, str_checkname(ls), line);
break;
}
case TK_RETURN: { /* stat -> retstat */
luaX_next(ls); /* skip RETURN */
retstat(ls);
break;
}
case TK_BREAK: { /* stat -> breakstat */
breakstat(ls);
break;
}
case TK_GOTO: { /* stat -> 'goto' NAME */
luaX_next(ls); /* skip 'goto' */
gotostat(ls);
break;
}
default: { /* stat -> func | assignment */
exprstat(ls);
break;
}
}
lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
ls->fs->freereg >= luaY_nvarstack(ls->fs));
ls->fs->freereg = luaY_nvarstack(ls->fs); /* free registers */
leavelevel(ls);
}
/* }====================================================================== */
上面就是根据不同的Token调用相应的处理器函数,整个流程类似状态机模型