如今編譯器相關的部分就剩下 luac.c 中的 do_dump 函數的分析了。
這個函數裏面主要有兩種調用,存儲字節碼和打印字節碼。
先來看一下打印字節吧,要打印字節碼,須要在編譯器的命令行選項中有 "-l" 選項。
數組
static void do_dump(TFunc* tf) /* only for tf==main */ { if (dumping) DumpHeader(D); while (tf!=NULL) { TFunc* nf; if (listing) PrintFunction(tf); if (dumping) DumpFunction(tf,D); nf=tf->next; /* list only built after first main */ luaI_freefunc(tf); tf=nf; } }
打印字節碼調用的就是上面的 PrintFunction,能夠看出,它是經過 listing 條件控制的,而 listing 是在命令行中有 「-l" 選項時纔會爲 1。
實際中用的時候,發現這裏有一個小錯誤。當 -p -l 選項同時使用時(只進行語法分析不 dump 字節碼),PrintFunction 只被調用一次,也就是隻打印主函數的字節碼,其它自定義的函數的字節碼不會被打印。去掉 -p 選項時(也就是同時存儲字節碼到默認輸出 luac.out 文件)則正常。
舉個例子,對於下面的 Lua 腳本:
函數
function add(x, y) return x + y end print (add(3, 4))
當用 -p -l 執行它時,打印的結果以下:
main of "test.lua" (25 bytes at 00602090)
0 PUSHFUNCTION 00602120 ; "test.lua":1
5 STOREGLOBAL 13 ; add
8 PUSHGLOBAL 7 ; print
11 PUSHGLOBAL 13 ; add
14 PUSHBYTE 3
16 PUSHBYTE 4
18 CALLFUNC 2 1
21 CALLFUNC 1 0
24 RETCODE0
奇怪,函數 add 哪兒去了?
當用 -l 執行它時,打印的結果以下:
main of "test.lua" (25 bytes at 001120D8)
0 PUSHFUNCTION 00112168 ; "test.lua":1
5 STOREGLOBAL 13 ; add
8 PUSHGLOBAL 7 ; print
11 PUSHGLOBAL 13 ; add
14 PUSHBYTE 3
16 PUSHBYTE 4
18 CALLFUNC 2 1
21 CALLFUNC 1 0
24 RETCODE0
function "test.lua":1 (9 bytes at 00112168); used at main+1
0 ADJUST 2
2 PUSHLOCAL0 0 ;
3 PUSHLOCAL1 1 ;
4 ADDOP
5 RETCODE 2
7 RETCODE 2
看到了吧,這裏多了下面的 add 函數的字節碼。
這是爲何?應該是程序出現了錯誤。
分析了一下,發現,當有 -p 選項時,do_dump 代碼裏的 tf->next 爲 NULL,而沒有 -p 選項時是好的。調試了一下,發現,在執行 DumpFunction 以後,tf->next 被賦值,跟到 DumpFunction 裏 ThreadCode 裏發現了下面這句:
ui
case PUSHFUNCTION: { CodeCode c; p++; get_code(c,p); c.tf->marked=at; c.tf->next=NULL; /* TODO: remove? */ lastF=lastF->next=c.tf; break; }
而在有 -p 選項時,上面這句不執行,因此 tf->next 沒有賦值,上面的打印字節碼就打印不了函數 add 的字節碼了。
正常狀況下,主函數和全部的 Lua 腳本中定義的函數編譯後造成一個 TFunc 鏈,函數的字節碼存在 TFunc 的 code 字段中。
print.h 裏定義了了一個字節碼指令的名字數組,它在打印字節碼的時候會用到。
看下 PrintFunction 的代碼
lua
void PrintFunction(TFunc* tf) { if (IsMain(tf)) printf("\nmain of \"%s\" (%d bytes at %p)\n",tf->fileName,tf->size,tf); else printf("\nfunction \"%s\":%d (%d bytes at %p); used at main+%d\n", tf->fileName,tf->lineDefined,tf->size,tf,tf->marked); V=tf->locvars; PrintCode(tf->code,tf->code+tf->size); }
主函數和用戶自定義的函數的打印出來的描述信息不同,經過 IsMain 宏判斷是不是主函數。
#define IsMain(f) (f->lineDefined==0)
用戶自定義函數的定義行必定爲大於 0 的值,而主函數設定爲 0。
而後,調用 PrintCode 打印字節碼指令。
命令行
static void PrintCode(Byte* code, Byte* end) { Byte* p; for (p=code; p!=end;) { OpCode op=(OpCode)*p; if (op>SETLINE) op=SETLINE+1; printf("%6d\t%s",p-code,OpCodeName[op]); switch (op) { /*cases and other codes*/ } } }
打印指令的位置和指令的名字,就是 printf 那一句。
若是是非法指令的話,就是 op>SETLINE,會打印一個空字符串。由於 OpCodeName 數組中 "SETLINE" 下一個字符串就是空串 「」。
接着是一個 switch case 打印其它指令中的一些其它數據相關的信息。包括指令的數據部分和會用到的字符串相關的部分。對比打印出來的字節碼很容易看出代碼的用意,這部分至關於對字節碼進行了一個簡單的解析(說它簡單是和虛擬機中真正執行字節碼時的對比)。因此,就不對它進行一行行的分析了。
----------------------------------------
到目前爲止的問題:
> do_dump 方法裏調的 dump 相關的方法是幹什麼的?
----------------------------------------
調試