前面三篇请看我前面的 blog
这篇主要来分析lua的虚拟机的实现,我看的代码依旧是5.1
因此首先从luaL_loadfile开始,这个函数我们知道是在当前的lua state加载一个lua文件,其中第二个参数就是filename。
其中LoadF结构很简单,它用来表示一个load file:
struct LoadF { 1 2 3 4 5 6 7 8 int extraline; FILE *f; char buff[LUAL_BUFFERSIZE]; } LoadF;
其中会使用fopen来打开对应的文件名,然后根据第一个字符来判断是否是注释(#),如果是则跳过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lua_pushfstring(L, "@%s", filename); lf.f = fopen(filename, "r"); if (lf.f == NULL) return errfile(L, "open", fnameindex); } c = getc(lf.f); if (c == ‘#’) { /\* Unix exec. file? \*/ lf.extraline = 1; while ((c = getc(lf.f)) != EOF && c != ‘\n’) ; /\* skip first line \*/ if (c == ‘\n’) c = getc(lf.f); }
然后会判断是否是lua字节码文件,通过文件头的magic number(Lua),如果是,则用二进制打开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (c == LUA_SIGNATURE[0] && filename) { /\* binary file? \*/ lf.f = freopen(filename, "rb", lf.f); /\* reopen in binary mode \*/ if (lf.f == NULL) return errfile(L, "reopen", fnameindex); /\* skip eventual \`#!…’ \*/ while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; lf.extraline = 0; }
最后就是调用lua_load来load文件。这里有一个结构要注意,那就是zio,这个结构就是一个parse的结构。这里的reader就是一个回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct Zio { size_t n; /\* bytes still unread \*/ const char \*p; /\* current position in buffer */ lua_Reader reader; void\* data; /\* additional data */ lua_State \*L; /\* Lua state (for reader) */ };
可以看看这个结构如何被初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void luaZ_init (lua_State \*L, ZIO \*z, lua_Reader reader, void *data) { z->L = L; z->reader = reader; z->data = data; z->n = 0; z->p = NULL; }
先看看lua的BNF
chunk ::= {stat [`;´]} [laststat [`;´]] block ::= chunk stat ::= varlist `=´ explist | functioncall | do block end | while exp do block end | repeat block until exp | if exp then block {elseif exp then block} [else block] end | for Name `=´ exp `,´ exp [`,´ exp] do block end | for namelist in explist do block end | function funcname funcbody | local function Name funcbody | local namelist [`=´ explist] laststat ::= return [explist] | break funcname ::= Name {`.´ Name} [`:´ Name] varlist ::= var {`,´ var} var ::= Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name namelist ::= Name {`,´ Name} explist ::= {exp `,´} exp exp ::= nil | false | true | Number | String | `…´ | function | prefixexp | tableconstructor | exp binop exp | unop exp prefixexp ::= var | functioncall | `(´ exp `)´ functioncall ::= prefixexp args | prefixexp `:´ Name args args ::= `(´ [explist] `)´ | tableconstructor | String function ::= function funcbody funcbody ::= `(´ [parlist] `)´ block end parlist ::= namelist [`,´ `…´] | `…´ tableconstructor ::= `{´ [fieldlist] `}´ fieldlist ::= field {fieldsep field} [fieldsep] field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp fieldsep ::= `,´ | `;´ binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | `<´ | `<=´ | `]]>´ | `>=´ | `==´ | `~=´ | and | or unop ::= `-´ | not | `#´
可以看到reader就是被初始化为getF,而data是被初始化位上面的LoadF结构,然后就是调用luaD_protectedparser来parse源代码。
在初始化的时候调用luaX_init将保留关键字作为字符串放入全局的string hash中(ts->reserved),因此当解析到字符串时,能够很容易的到当前字符串的类型。
解析函数是luaY_parser, 而核心的parse方法是llex,这个函数会返回对应的token,然后根据解析出来的token,来决定接下来理应是什么符号(核心在chunk函数里面,这函数有一个循环), 然后每次都会调用luaX_next函数,来继续解析(它会调用llex).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Proto \*luaY_parser (lua_State \*L, ZIO \*z, Mbuffer \*buff, const char *name) { struct LexState lexstate; struct FuncState funcstate; lexstate.buff = buff; luaX_setinput(L, &lexstate, z, luaS_new(L, name)); open_func(&lexstate, &funcstate); funcstate.f->is_vararg = VARARG_ISVARARG; /\* main func. is always vararg \*/ luaX_next(&lexstate); /\* read first token \*/ chunk(&lexstate); check(&lexstate, TK_EOS); close_func(&lexstate); lua_assert(funcstate.prev == NULL); lua_assert(funcstate.f->nups == 0); lua_assert(lexstate.fs == NULL); return funcstate.f; }
可以看到最终luaY_parser返回的是一个proto,也就是说每一个lua文件也就相当于一个proto(函数).
下面湿核心的chunk方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static void chunk (LexState *ls) { /\* chunk -> { stat [\`;’] } \*/ int islast = 0; enterlevel(ls); while (!islast && !block_follow(ls->t.token)) { islast = statement(ls); testnext(ls, ‘;’); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg && ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /\* free registers \*/ } leavelevel(ls); }
lua里面token定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 enum RESERVED { /\* terminal symbols denoted by reserved words \*/ TK_AND = FIRST_RESERVED, TK_BREAK, TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT, TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE, /\* other terminal symbols \*/ TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER, TK_NAME, TK_STRING, TK_EOS }
可以看到上方是关键字,而下方是其他的一些终结符。
核心的parse结构LexState,它主要是用于parse期间保存状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 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;
上面这个结构中最需要注意的是Token结构,这个结构保存了对应的解析出来的token。seminfo保存了经过词法解析后的词(不包括运算符以及[]等),只包括字母以及数字number and string。在lua中所有的tiken分为13类,也就是BNF中的binop(不包括and or)+各种关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef union { lua_Number r; TString *ts; } SemInfo; /\* semantics information \*/ typedef struct Token { int token; SemInfo seminfo; } Token;
然后就是每个解析出来的函数的数据结构,这个结构保存了解析到的函数的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 typedef struct FuncState { Proto \*f; /\* current function header */ Table \*h; /\* table to find (and reuse) elements in \`k’ */ struct FuncState \*prev; /\* enclosing function */ struct LexState \*ls; /\* lexical state */ struct lua_State \*L; /\* copy of the Lua state */ struct BlockCnt \*bl; /\* chain of current blocks */ int pc; /\* next position to code (equivalent to \`ncode’) \*/ int lasttarget; /\* \`pc’ of last \`jump target’ \*/ int jpc; /\* list of pending jumps to \`pc’ \*/ int freereg; /\* first free register \*/ int nk; /\* number of elements in \`k’ \*/ int np; /\* number of elements in \`p’ \*/ short nlocvars; /\* number of elements in \`locvars’ \*/ lu_byte nactvar; /\* number of active local variables \*/ upvaldesc upvalues[LUAI_MAXUPVALUES]; /\* upvalues \*/ unsigned short actvar[LUAI_MAXVARS]; /\* declared-variable stack \*/ } FuncState;
上面这几个结构的初始化都是放在luaY_parser中的,llex方法其实只是一个词法扫描器,它只负责扫描符号以及单词,然后它会将扫描到的符号交给后续的语法分析器去判断。
其中luaX_setinput用于初始化设置lexState的一些输入属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void luaX_setinput (lua_State \*L, LexState \*ls, ZIO \*z, TString \*source) { ls->decpoint = ‘.’; ls->L = L; //前一个token ls->lookahead.token = TK_EOS; /\* no look-ahead token \*/ ls->z = z; ls->fs = NULL; ls->linenumber = 1; ls->lastline = 1; ls->source = source; luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /\* initialize buffer \*/ //第一次读取token next(ls); /\* read first char \*/ }
而open_func函数则是新建一个func对象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static void open_func (LexState \*ls, FuncState \*fs) { lua_State *L = ls->L; Proto *f = luaF_newproto(L); fs->f = f; fs->prev = ls->fs; /\* linked list of funcstates \*/ fs->ls = ls; fs->L = L; ls->fs = fs; fs->pc = 0; fs->lasttarget = -1; fs->jpc = NO_JUMP; fs->freereg = 0; fs->nk = 0; fs->np = 0; fs->nlocvars = 0; fs->nactvar = 0; fs->bl = NULL; f->source = ls->source; f->maxstacksize = 2; /\* registers 0/1 are always valid \*/ fs->h = luaH_new(L, 0, 0); /\* anchor table of constants and prototype (to avoid being collected) \*/ sethvalue2s(L, L->top, fs->h); incr_top(L); setptvalue2s(L, L->top, f); incr_top(L); }
然后会在statement函数中对语句进行语法解析,然后不同的函数解析不同的语句。
assignment函数用于解析赋值语法,而它会调用exploits1,explist1对应的解析 explist1 -> expr { `,’ expr } ,因此在它里面就会调用expr函数,而在expr中是直接调用subexpr函数,它主要是解析这类语法subexpr -> (simpleexp | unop subexpr) { binop subexpr } . 因此在subexpr中就会调用三个函数,分别是simpleexp (simpleexp -> NUMBER | STRING | NIL | true | false | … | constructor | FUNCTION body | primaryexp), getunopr来解析一元操作符,getbinopr来解析二元操作符。 在simpleexp中,则会调用primaryexp来解析 primaryexp -> prefixexp { `.’ NAME | `[‘ exp `]’ | `:’ NAME funcargs | funcargs } 这类语法。
每次使用checknext方法来检测下一个token是否是期望的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static void check (LexState *ls, int c) { if (ls->t.token != c) error_expected(ls, c); } static void checknext (LexState *ls, int c) { check(ls, c); luaX_next(ls); }
lua的自顶向下的解析最核心的就是lcode.c 和lparse.c.其中lua对待一个文件就是把这个文件当做一个函数,因此在解析完毕之后,返回的就是一个proto指针。对应的lparse是解析lua源代码,而lcode.c则将对应的源码翻译为虚拟机指令(保存在proto的code数组中).
在lua中每一个表达式解析完毕后就表示为一个这样的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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;
这里最重要的就是联合体u,其中info表示语句对应的索引(在指令数组code(proto结构体)中).或者说表示为对应constant 的索引(proto的TValue *k这个数组的索引).这就体现了程序是由数据+指令组成。
lua的虚拟机指令的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /*=========================================================================== We assume that instructions are unsigned numbers. All instructions have an opcode in the first 6 bits. Instructions can have the following fields: \`A’ : 8 bits \`B’ : 9 bits \`C’ : 9 bits \`Bx’ : 18 bits (\`B’ and \`C’ together) \`sBx’ : signed Bx A signed argument is represented in excess K; that is, the number value is the unsigned value minus K. K is exactly the maximum value for that argument (so that -max is represented by 0, and +max is represented by 2*max), which is half the maximum for the corresponding unsigned argument. ===========================================================================*/
1 2 enum OpMode {iABC, iABx, iAsBx}; /\* basic instruction format \*/
可以看到也就是3种指令,而下面的宏定义了指令的参数以及opcode的大小以及偏移,这里可以看到在lua里面所有的指令都是定长的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define SIZE_C9 #define SIZE_B9 #define SIZE_Bx(SIZE_C + SIZE_B) #define SIZE_A8 #define SIZE_OP6 #define POS_OP0 #define POS_A(POS_OP + SIZE_OP) #define POS_C(POS_A + SIZE_A) #define POS_B(POS_C + SIZE_C) #define POS_BxPOS_C
再接下来就是如何取得对应的opcode以及各个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /\* creates a mask with \`n’ 1 bits at position \`p’ \*/ #define MASK1(n,p)((~((~(Instruction)0)<<n))<<p) /\* creates a mask with \`n’ 0 bits at position \`p’ \*/ #define MASK0(n,p)(~MASK1(n,p)) /* ** the following macros help to manipulate instructions */ #define GET_OPCODE(i)(cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) #define SET_OPCODE(i,o)((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \ ((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP))))
对应的上面就是如何取得对应指令 opcode的宏。下面就是所有的opcode类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 typedef enum { /*———————————————————————- nameargsdescription ————————————————————————*/ OP_MOVE,/\*A BR(A) := R(B)\*/ OP_LOADK,/\*A BxR(A) := Kst(Bx)\*/ OP_LOADBOOL,/\*A B CR(A) := (Bool)B; if (C) pc++\*/ OP_LOADNIL,/\*A BR(A) := … := R(B) := nil\*/ OP_GETUPVAL,/\*A BR(A) := UpValue[B]\*/ OP_GETGLOBAL,/\*A BxR(A) := Gbl[Kst(Bx)]\*/ OP_GETTABLE,/\*A B CR(A) := R(B)[RK(C)]\*/ OP_SETGLOBAL,/\*A BxGbl[Kst(Bx)] := R(A)\*/ OP_SETUPVAL,/\*A BUpValue[B] := R(A)\*/ OP_SETTABLE,/\*A B CR(A)[RK(B)] := RK©\*/ OP_NEWTABLE,/\*A B CR(A) := {} (size = B,C)\*/ OP_SELF,/\*A B CR(A+1) := R(B); R(A) := R(B)[RK(C)]\*/ OP_ADD,/\*A B CR(A) := RK(B) + RK©\*/ OP_SUB,/\*A B CR(A) := RK(B) – RK©\*/ OP_MUL,/\*A B CR(A) := RK(B) \* RK©*/ OP_DIV,/\*A B CR(A) := RK(B) / RK©\*/ OP_MOD,/\*A B CR(A) := RK(B) % RK©\*/ OP_POW,/\*A B CR(A) := RK(B) ^ RK©\*/ OP_UNM,/\*A BR(A) := -R(B)\*/ OP_NOT,/\*A BR(A) := not R(B)\*/ OP_LEN,/\*A BR(A) := length of R(B)\*/ OP_CONCAT,/\*A B CR(A) := R(B).. … ..R©\*/ OP_JMP,/\*sBxpc+=sBx\*/ OP_EQ,/\*A B Cif ((RK(B) == RK(C)) ~= A) then pc++\*/ OP_LT,/\*A B Cif ((RK(B) < RK(C)) ~= A) then pc++ \*/ OP_LE,/\*A B Cif ((RK(B) <= RK(C)) ~= A) then pc++ \*/ OP_TEST,/\*A Cif not (R(A) <=> C) then pc++\*/ OP_TESTSET,/\*A B Cif (R(B) <=> C) then R(A) := R(B) else pc++\*/ OP_CALL,/\*A B CR(A), … ,R(A+C-2) := R(A)(R(A+1), … ,R(A+B-1)) \*/ OP_TAILCALL,/\*A B Creturn R(A)(R(A+1), … ,R(A+B-1))\*/ OP_RETURN,/\*A Breturn R(A), … ,R(A+B-2)(see note)\*/ OP_FORLOOP,/*A sBxR(A)+=R(A+2); if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/ OP_FORPREP,/\*A sBxR(A)-=R(A+2); pc+=sBx\*/ OP_TFORLOOP,/*A CR(A+3), … ,R(A+2+C) := R(A)(R(A+1), R(A+2)); if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++*/ OP_SETLIST,/\*A B CR(A)[(C-1)\*FPF+i] := R(A+i), 1 <= i <= B*/ OP_CLOSE,/\*A close all variables in the stack up to (>=) R(A)\*/ OP_CLOSURE,/\*A BxR(A) := closure(KPROTO[Bx], R(A), … ,R(A+n))\*/ OP_VARARG/\*A BR(A), R(A+1), …, R(A+B-1) = vararg\*/ } OpCode;
然后我们来看一个最简单的语句是如何解析进lua的虚拟机的,假设有一条 local t = 3 的语句。这个会通过parse进入localstat,然后调用new_localvar来新建一个local变量,所有的local变量都放在Proto结构的f->locvars中,locvars也就是一个数组
1 2 struct LocVar \*locvars; /\* information about local variables */
而对应的数组索引放到FuncState的actvar数组中。因为在lua中可以这么写local t, t1 = 3。解析完毕名字之后,就开始解析=,解析=会在explist1函数中进行。然后会讲解析掉的数字3 放入到表达式(expdesc)的nval域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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; case TK_NUMBER: { init_exp(v, VKNUM, 0); v->u.nval = ls->t.seminfo.r; break; }
这里可以看到直接赋值给nval。
然后就是adjust_assign以及adjustlocalvars两个函数,其中adjust_assign将会调用luaK_exp2nextreg函数,而在这个函数中则会调用discharge2reg函数来设置对应的指令, 这里比较关键的,就是lua如何来选择寄存器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void luaK_reserveregs (FuncState *fs, int n) { luaK_checkstack(fs, n); fs->freereg += n; } void luaK_exp2nextreg (FuncState \*fs, expdesc \*e) { luaK_dischargevars(fs, e); freeexp(fs, e); luaK_reserveregs(fs, 1); exp2reg(fs, e, fs->freereg – 1); } static void exp2reg (FuncState \*fs, expdesc \*e, int reg) { discharge2reg(fs, e, reg); ………….. } static void discharge2reg (FuncState \*fs, expdesc \*e, int reg) { luaK_dischargevars(fs, e); …………………….. case VKNUM: { luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval)); break; } …………………….. e->u.s.info = reg; e->k = VNONRELOC; }
通过上面我们可以看到寄存器的选择是通过funcstate的freereg域来进行选择的。最终我们可以看到当字节码生成之后,e->u.s.info中放的就是寄存器了。而在一开始进入luaK_exp2nextreg之后,立即对freereg进行+1,这里寄存器我们可以看做是一个索引.
接下来我们来看luaK_numberK的实现,也就是lua会把constant放到哪里去。
1 2 3 4 5 6 7 8 9 10 int luaK_numberK (FuncState *fs, lua_Number r) { TValue o; setnvalue(&o, r); return addk(fs, &o, &o); }
static int addk (FuncState *fs, TValue *k, TValue *v) {
lua_State *L = fs->L;
TValue *idx = luaH_set(L, fs->h, k);
Proto *f = fs->f;
int oldsize = f->sizek;
if (ttisnumber(idx)) {
lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v));
return cast_int(nvalue(idx));
}
else { /* constant not found; create a new entry */
setnvalue(idx, cast_num(fs->nk));
luaM_growvector(L, f->k, fs->nk, f->sizek, TValue,
MAXARG_Bx, “constant table overflow”);
while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]);
//存储值
setobj(L, &f->k[fs->nk], v);
luaC_barrier(L, f, v);
return fs->nk++;
}
}
1 2 3 4 可以看到最终值会放到f->k这个数组中。并且会返回对应的索引,然后讲索引保存到字节码中。 这个时候可以看到这条语句对应的字节码是LOADK, 而loadk对应的指令类型是ABx,我们来看对应的域都填充的是什么。
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);
}
1 2 3 4 可以看到对应的a填充到ABx类型的A,而bc则填充到bx域,而a是什么呢?通过上面的代码可以看到a就是寄存器,bc就是对应的值.也就是说讲3这个值放到bx,而把t对应的寄存器 放到a域.字节码生成了。 然后看来luaK_code这个函数,他也就是讲指令放入到proto结构体的code数组中
static int luaK_code (FuncState *fs, Instruction i, int line) {
Proto *f = fs->f;
dischargejpc(fs); /* `pc’ will change */
/* put new instruction in code array */
luaM_growvector(fs->L, f->code, fs->pc, f->sizecode, Instruction,
MAX_INT, “code size overflow”);
f->code[fs->pc] = i;
/* save corresponding line information */
luaM_growvector(fs->L, f->lineinfo, fs->pc, f->sizelineinfo, int,
MAX_INT, “code size overflow”);
f->lineinfo[fs->pc] = line;
return fs->pc++;
}
1 2 3 4 接下来我们来看lua虚拟机会如何来解析字节码。 所有的解析都在luaV_execute中。来看对应的代码, luaV_execute也就是会通过遍历L->code(索引在savedpc中)来执行所有的字节码。
void luaV_execute (lua_State *L) {
CallInfo *ci = L->ci;
……………..
pc = L->savedpc;
cl = &clvalue(L->ci->func)->l;
base = L->base;
//取出constants数组
k = cl->p->k;
/* main loop of interpreter */
for (;;) {
const Instruction i = *pc++;
StkId ra;
if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) &&
(–L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
traceexec(L, pc);
if (L->status == LUA_YIELD) { /* did hook yield? */
L->savedpc = pc – 1;
return;
}
base = L->base;
}
…………………
ra = RA(i);
………….
case OP_LOADK: {
setobj2s(L, ra, KBx(i));
continue;
}
}
1 2 这里ra就是对应的栈的位置(通过寄存器来确定栈的位置), 然后KBx主要是用于得到对应的值(也就是我们例子中的5).
#define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))
1 2 3 4 可以看到他是以k = cl->p->k;为基准值,然后加上对应的偏移. 最后我们来看setobj2s这个函数,这个函数其实很简单,就是把从寄存器取出来的值放入到对应的value结构中。
#define setobj(L,obj1,obj2) \
{ const TValue *o2=(obj2); TValue *o1=(obj1); \
o1->value = o2->value; o1->tt=o2->tt; \
checkliveness(G(L),o1); }
```
可以看到最终t的值被放入到L->base为基础的一段内存中。