詳解關(guān)于Lua源碼分析學(xué)習(xí)教程
關(guān)于Lua源碼分析學(xué)習(xí)教程是本文要介紹的內(nèi)容,主要來了解LUA中源碼的使用方法。Lua首先將源程序編譯成為字節(jié)碼,然后交由虛擬機(jī)解釋執(zhí)行。對(duì)于每一個(gè)函數(shù),Lua的編譯器將創(chuàng)建一個(gè)原型(prototype),它由一組指令及其使用到的常量組成[1]。最初的Lua虛擬機(jī)是基于棧的。到1993年,Lua5.0版本,采用了基于寄存器的虛擬機(jī),使得Lua的解釋效率得到提升。
體系結(jié)構(gòu)與指令系統(tǒng)
與虛擬機(jī)和指令相關(guān)的文件主要有兩個(gè): lopcodes.c 和 lvm.c。從名稱可以看出來,這兩個(gè)文件分別用于描述操作碼(指令)和虛擬機(jī)。
首先來看指令:
Lua共有38條指令,在下面兩處地方分別描述了這些指令的名稱和模式, 如下:
- lopcodes.c:16
- const char *const luaP_opnames[NUM_OPCODES+1] = {
- "MOVE",
- "LOADK",
- "LOADBOOL",
- "LOADNIL",
- "GETUPVAL",
- "GETGLOBAL",
- "GETTABLE",
- "SETGLOBAL",
- "SETUPVAL",
- "SETTABLE",
- "NEWTABLE",
- "SELF",
- "ADD",
- "SUB",
- "MUL",
- "DIV",
- "MOD",
- "POW",
- "UNM",
- "NOT",
- "LEN",
- "CONCAT",
- "JMP",
- "EQ",
- "LT",
- "LE",
- "TEST",
- "TESTSET",
- "CALL",
- "TAILCALL",
- "RETURN",
- "FORLOOP",
- "FORPREP",
- "TFORLOOP",
- "SETLIST",
- "CLOSE",
- "CLOSURE",
- "VARARG",
- NULL
- };
- #define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))
- const lu_byte luaP_opmodes[NUM_OPCODES] = {
- /* T A B C mode opcode */
- opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */
- ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */
- ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */
- ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LOADNIL */
- ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */
- ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_GETGLOBAL */
- ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */
- ,opmode(0, 0, OpArgK, OpArgN, iABx) /* OP_SETGLOBAL */
- ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */
- ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */
- ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */
- ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */
- ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */
- ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */
- ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */
- ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */
- ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */
- ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */
- ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */
- ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */
- ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */
- ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TEST */
- ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */
- ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */
- ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */
- ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */
- ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */
- ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */
- ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TFORLOOP */
- ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */
- ,opmode(0, 0, OpArgN, OpArgN, iABC) /* OP_CLOSE */
- ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */
- ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */
- };
前面一個(gè)數(shù)組容易理解,表示了每條指令的名稱。后面一個(gè)數(shù)組表示的是指令的模式。奇怪的符號(hào)讓人有些費(fèi)解。在看模式之前, 首先來看Lua指令的格式,如圖:
如上圖, Lua的指令可以分成三種形式. 即在上面的模式數(shù)組中也可以看到的iABC, iABx 和 iAsBx. 對(duì)于三種形式的指令來說, 前兩部分都是一樣的, 分別是6位的操作碼和8位A操作數(shù); 區(qū)別在于, 后面部是分割成為兩個(gè)長(zhǎng)度為9位的操作符(B, C),一個(gè)無符號(hào)的18位操作符Bx還是有符號(hào)的18位操作符sBx. 這些定義的代碼如下:
- lopcodes.c : 34
- /*
- ** size and position of opcode arguments.
- */
- #define SIZE_C 9
- #define SIZE_B 9
- #define SIZE_Bx (SIZE_C + SIZE_B)
- #define SIZE_A 8
- #define SIZE_OP 6
- #define POS_OP 0
- #define POS_A (POS_OP + SIZE_OP)
- #define POS_C (POS_A + SIZE_A)
- #define POS_B (POS_C + SIZE_C)
- #define POS_Bx POS_C
再來看指令的操作模式, Lua使用一個(gè)字節(jié)來表示指令的操作模式. 具體的含義如下:
1、使用最高位來表示是否是一條測(cè)試指令. 之所以將這一類型的指令特別地標(biāo)識(shí)出來, 是因?yàn)長(zhǎng)ua的指令長(zhǎng)度是32位,對(duì)于分支指令來說, 要想在這32位中既表示兩個(gè)操作數(shù)來做比較, 同時(shí)還要表示一個(gè)跳轉(zhuǎn)的地址, 是很困難的. 因此將這種指令分成兩條, 第一條是測(cè)試指令, 緊接著一條無條件跳轉(zhuǎn). 如果判斷條件成立則將PC(Program Counter, 指示下一條要執(zhí)行的指令)加一, 跳過下一條無條件跳轉(zhuǎn)指令, 繼續(xù)執(zhí)行; 否則跳轉(zhuǎn).
2、第二位用于表示A操作數(shù)是否被設(shè)置
3、接下來的二位用于表示操作數(shù)B的格式,OpArgN表示操作數(shù)未被使用, OpArgU表示操作數(shù)被使用(立即數(shù)?), OpArgR表示表示操作數(shù)是寄存器或者跳轉(zhuǎn)的偏移量, OpArgK表示操作數(shù)是寄存器或者常量.
最后, 給出Lua虛擬機(jī)的體系結(jié)構(gòu)圖(根據(jù)源代碼分析得出):
首先, 我們注意到, Lua的解釋器還是一個(gè)以棧為中心的結(jié)構(gòu). 在lua_State這個(gè)結(jié)構(gòu)中,有許多個(gè)字段用于描述這個(gè)結(jié)構(gòu).stack用于指向絕對(duì)棧底, 而base指向了當(dāng)前正在執(zhí)行的函數(shù)的第一個(gè)參數(shù), 而top指向棧頂?shù)牡谝粋€(gè)空元素.
我們可以看到,這個(gè)體系結(jié)構(gòu)中并沒有獨(dú)立出來的寄存器. 從以下代碼來看:
- lvm.c:343
- #define RA(i) (base+GETARG_A(i))
- /* to be used after possible stack reallocation */
- #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
- #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
- #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
- ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
- #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
- ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
- #define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))
當(dāng)指令操作數(shù)的類型是寄存器時(shí),它的內(nèi)容是以base為基址在棧上的索引值.如圖所示.寄存器實(shí)際是base之上棧元素的別名;當(dāng)指令操作數(shù)的類型的常數(shù)時(shí), 它首先判斷B操作數(shù)的最位是否為零.如果是零,則按照和寄存器的處理方法一樣做,如果不是零,則在常數(shù)表中找相應(yīng)的值.
我們知道Lua中函數(shù)的執(zhí)行過程是這樣的. 首先將函數(shù)壓棧,然后依次將參數(shù)壓棧,形成圖中所示的棧的內(nèi)容. 因此R[0]到R[n]也分別表示了Arg[1]到Arg[N+1].在第一個(gè)參數(shù)之下,就是當(dāng)前正在執(zhí)行的函數(shù),對(duì)于Lua的函數(shù)(相對(duì)C函數(shù))來說,它是指向類型為 Prototype的TValue, 在Prototype中字段code指向了一個(gè)數(shù)組用來表示組成這個(gè)函數(shù)的所有指令,字段k指向一個(gè)數(shù)組來表示這個(gè)函數(shù)使用到的所有常量.最后,Lua在解釋執(zhí)行過程中有專門的變量pc來指向下一條要執(zhí)行的指令.
指令解釋器
有了前面對(duì)指令格式和體系結(jié)構(gòu)的介紹,現(xiàn)在我們可以進(jìn)入正題, 來看看Lua的指令是如何執(zhí)行的了.主函數(shù)如下:
- lvm.c:373
- void luaV_execute (lua_State *L, int nexeccalls) {
- LClosure *cl;
- StkId base;
- TValue *k;
- const Instruction *pc;
- reentry: /* entry point */
- lua_assert(isLua(L->ci));
- pc = L->savedpc;
- cl = &clvalue(L->ci->func)->l;
- base = L->base;
- k = cl->p->k;
這是最開始的初始化過程.其中, pc被初始化成為了L->savedpc,base被初始化成為了L->base, 即程序從L->savedpc開始執(zhí)行 (在下一篇專題中,將會(huì)介紹到 L->savedpc在函數(shù)調(diào)用的預(yù)處理過程中指向了當(dāng)前函數(shù)的code),而L->base指向棧中當(dāng)前函數(shù)的下一個(gè)位置.cl表示當(dāng)前正在執(zhí)行閉包(當(dāng)前可以理解成為函數(shù)),k指向當(dāng)前閉包的常量表.
接下來(注意,為了專注主要邏輯, 我將其中用于Debugger支持,斷言等代碼省略了):
- /* main loop of interpreter */
- for (;;) {
- const Instruction i = *pc++;
- StkId ra;
- /* 省略Debugger支持和Coroutine支持*/
- /* warning!! several calls may realloc the stack and invalidate `ra' */
- ra = RA(i);
- /* 省略斷言 */
- switch (GET_OPCODE(i)) {
進(jìn)入到解釋器的主循環(huán),處理很簡(jiǎn)單,取得當(dāng)前指令,pc遞增,初始化ra,然后根據(jù)指令的操作碼進(jìn)行選擇. 接下來的代碼是什么樣的, 估計(jì)大家都能想到,一大串的case來指示每條指令的執(zhí)行.具體的實(shí)現(xiàn)可以參考源碼, 在這里不對(duì)每一條指令展開, 只是對(duì)其中有主要的幾類指令進(jìn)行說明:
傳值類的指令,與MOVE為代表:
- lvm.c:403
- case OP_MOVE: {
- setobjs2s(L, ra, RB(i));
- continue;
- }
- lopcodes:154
- OP_MOVE,/* A B R(A) := R(B) */
- lobject.h:161
- #define setobj(L,obj1,obj2) \
- { const TValue *o2=(obj2); TValue *o1=(obj1); \
- o1->value = o2->value; o1->tt=o2->tt; \
- checkliveness(G(L),o1); }
- /*
- ** different types of sets, according to destination
- */
- /* from stack to (same) stack */
- #define setobjs2s setobj
從注釋來看, 這條指令是將操作數(shù)A,B都做為寄存器,然后將B的值給A. 而實(shí)現(xiàn)也是簡(jiǎn)單明了,只使用了一句. 宏展開以后, 可以看到, R[A],R[B]的類型是TValue, 只是將這兩域的值傳過來即可. 對(duì)于可回收對(duì)象來說,真實(shí)值不會(huì)保存在棧上,所以只是改了指針,而對(duì)于非可回收對(duì)象來說,則是直接將值從R[B]賦到R[A].
數(shù)值運(yùn)算類指令,與ADD為代表:
- lvm.c:470
- case OP_ADD: {
- arith_op(luai_numadd, TM_ADD);
- continue;
- }
- lvm.c:360
- #define arith_op(op,tm) { \
- TValue *rb = RKB(i); \
- TValue *rc = RKC(i); \
- if (ttisnumber(rb) && ttisnumber(rc)) { \
- lua_Number nb = nvalue(rb), nc = nvalue(rc); \
- setnvalue(ra, op(nb, nc)); \
- } \
- else \
- Protect(Arith(L, ra, rb, rc, tm)); \
- }
- lopcodes.c:171
- OP_ADD,/* A B C R(A) := RK(B) + RK(C) */
如果兩個(gè)操作數(shù)都是數(shù)值的話,關(guān)鍵的一行是:
- setnvalue(ra,op(nb,nc));
即兩個(gè)操作數(shù)相加以后,把值賦給R[A].值得注意的是,操作數(shù)B,C都是RK, 即可能是寄存器也可能是常量,這最決于最B和C的最高位是否為1,如果是1,則是常量,反之則是寄存器.具體可以參考宏ISK的實(shí)現(xiàn).
如果兩個(gè)操作數(shù)不是數(shù)值,即調(diào)用了Arith函數(shù),它嘗試將兩個(gè)操作轉(zhuǎn)換成數(shù)值進(jìn)行計(jì)算,如果無法轉(zhuǎn)換,則使用元表機(jī)制.該函數(shù)的實(shí)現(xiàn)如下:
- lvm.c:313
- static void Arith (lua_State *L, StkId ra, const TValue *rb,
- const TValue *rc, TMS op) {
- TValue tempb, tempc;
- const TValue *b, *c;
- if ((b = luaV_tonumber(rb, &tempb)) != NULL &&
- (c = luaV_tonumber(rc, &tempc)) != NULL) {
- lua_Number nb = nvalue(b), nc = nvalue(c);
- switch (op) {
- case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break;
- case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
- case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
- case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break;
- case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break;
- case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
- case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
- default: lua_assert(0); break;
- }
- }
- else if (!call_binTM(L, rb, rc, ra, op))
- luaG_aritherror(L, rb, rc);
- }
在上面call_binTM用于調(diào)用到元表中的元方法,因?yàn)樵贚ua以前的版本中元方法也被叫做tag method, 所以函數(shù)最后是以TM結(jié)尾的.
- lvm:163
- static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2,
- StkId res, TMS event) {
- const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */
- if (ttisnil(tm))
- tm = luaT_gettmbyobj(L, p2, event); /* try second operand */
- if (!ttisfunction(tm)) return 0;
- callTMres(L, res, tm, p1, p2);
- return 1;
- }
在 這個(gè)函數(shù)中,試著從二個(gè)操作數(shù)中找到其中一個(gè)操作數(shù)的元方法(第一個(gè)操作數(shù)優(yōu)先), 這里event表示具體哪一個(gè)元方法,找到了之后,再使用函數(shù)callTMres()去調(diào)用相應(yīng)的元方法. callTMres()的實(shí)現(xiàn)很簡(jiǎn)單,只是將元方法,第一,第二操作數(shù)先后壓棧,再調(diào)用并取因返回值.具體如下:
- lvm.c:82
- static void callTMres (lua_State *L, StkId res, const TValue *f,
- const TValue *p1, const TValue *p2) {
- ptrdiff_t result = savestack(L, res);
- setobj2s(L, L->top, f); /* push function */
- setobj2s(L, L->top+1, p1); /* 1st argument */
- setobj2s(L, L->top+2, p2); /* 2nd argument */
- luaD_checkstack(L, 3);
- L->top += 3;
- luaD_call(L, L->top - 3, 1);
- res = restorestack(L, result);
- L->top--;
- setobjs2s(L, res, L->top);
- }
邏輯運(yùn)算類指令,與EQ為代表:
- lvm.c:541
- case OP_EQ: {
- TValue *rb = RKB(i);
- TValue *rc = RKC(i);
- Protect(
- if (equalobj(L, rb, rc) == GETARG_A(i))
- dojump(L, pc, GETARG_sBx(*pc));
- )
- pc++;
- continue;
- }
- lopcodes.c:185
- OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */
在這條指令實(shí)現(xiàn)的過程中,equalobj與之前的算術(shù)運(yùn)算類似,讀者可以自行分析.關(guān)鍵看它是如果實(shí)現(xiàn)中跳轉(zhuǎn)的,如果RK[B]==RK[C]并且A為1 的情況下(即條件為真),則會(huì)使用pc取出下一條指令,調(diào)用dojump進(jìn)行跳轉(zhuǎn),否則pc++,掛空緊接著的無條件跳轉(zhuǎn)指令. dojump的實(shí)現(xiàn)如下:
- lvm.c:354
- #define dojump(L,pc,i) {(pc) += (i); luai_threadyield(L);}
luai_threadyield只是順序地調(diào)用lua_unlock和lua_lock,這里為釋放一次鎖,使得別的線程可以得到調(diào)度.
函數(shù)調(diào)用類指令,與CALL為代表:
- lvm.c:582
- case OP_CALL: {
- int b = GETARG_B(i);
- int nresults = GETARG_C(i) - 1;
- if (b != 0) L->top = ra+b; /* else previous instruction set top */
- L->savedpc = pc;
- switch (luaD_precall(L, ra, nresults)) {
- case PCRLUA: {
- nexeccalls++;
- goto reentry; /* restart luaV_execute over new Lua function */
- }
- case PCRC: {
- /* it was a C function (`precall' called it); adjust results */
- if (nresults >= 0) L->top = L->ci->top;
- base = L->base;
- continue;
- }
- default: {
- return; /* yield */
- }
- }
- }
- lopcodes.c:192
- OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
這一條指令將在下一個(gè)介紹Lua函數(shù)調(diào)用規(guī)范的專題中詳細(xì)介紹. 在這里只是簡(jiǎn)單地說明CALL指令的R[A]表示的是即將要調(diào)用的函數(shù),而B和C則分別表示參數(shù)個(gè)數(shù)加1,和返回值個(gè)數(shù)加1. 之所以這里需要加1,其原因是:B和C使用零來表示變長(zhǎng)的參數(shù)和變長(zhǎng)的返回值,而實(shí)際參數(shù)個(gè)數(shù)就向后推了一個(gè).
指令的介紹就先到此為止了, 其它的指令的實(shí)現(xiàn)也比較類似.仔細(xì)閱讀源碼就可很容易地分析出它的意義來. 下一篇將是一個(gè)專題, 詳細(xì)地介紹Lua中函數(shù)的調(diào)用是如何實(shí)現(xiàn)的.
小結(jié):詳解關(guān)于Lua源碼分析學(xué)習(xí)教程的內(nèi)容介紹完了,希望通過本文的學(xué)習(xí)能對(duì)你有所幫助!