前面三篇请看我前面的 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 [\`;&#8217;] } \*/

int islast = 0;

enterlevel(ls);

while (!islast && !block_follow(ls->t.token)) {

islast = statement(ls);

testnext(ls, &#8216;;&#8217;);

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&#8217; \*/

Token t; /\* current token \*/

//表示前一个token

Token lookahead; /\* look ahead token \*/

struct FuncState \*fs; /\* \`FuncState&#8217; 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&#8217; */

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&#8217;) \*/

int lasttarget; /\* \`pc&#8217; of last \`jump target&#8217; \*/

int jpc; /\* list of pending jumps to \`pc&#8217; \*/

int freereg; /\* first free register \*/

int nk; /\* number of elements in \`k&#8217; \*/

int np; /\* number of elements in \`p&#8217; \*/

short nlocvars; /\* number of elements in \`locvars&#8217; \*/

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 = &#8216;.&#8217;;

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&#8217; \*/

int f; /\* patch list of \`exit when false&#8217; \*/

} 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&#8217; : 8 bits

\`B&#8217; : 9 bits

\`C&#8217; : 9 bits

\`Bx&#8217; : 18 bits (\`B&#8217; and \`C&#8217; together)

\`sBx&#8217; : 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&#8217; 1 bits at position \`p&#8217; \*/

#define MASK1(n,p)((~((~(Instruction)0)<<n))<<p)

/\* creates a mask with \`n&#8217; 0 bits at position \`p&#8217; \*/

#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 {

/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-

nameargsdescription

&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;*/

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) := &#8230; := 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) &#8211; 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).. &#8230; ..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), &#8230; ,R(A+C-2) := R(A)(R(A+1), &#8230; ,R(A+B-1)) \*/

OP_TAILCALL,/\*A B Creturn R(A)(R(A+1), &#8230; ,R(A+B-1))\*/

OP_RETURN,/\*A Breturn R(A), &#8230; ,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), &#8230; ,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), &#8230; ,R(A+n))\*/

OP_VARARG/\*A BR(A), R(A+1), &#8230;, 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&#8217; \*/

int f; /\* patch list of \`exit when false&#8217; \*/

} 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 &#8211; 1);

}

static void exp2reg (FuncState \*fs, expdesc \*e, int reg) {

discharge2reg(fs, e, reg);

&#8230;&#8230;&#8230;&#8230;..

}

static void discharge2reg (FuncState \*fs, expdesc \*e, int reg) {

luaK_dischargevars(fs, e);

&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;..

case VKNUM: {

luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval));

break;

}

&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;..

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为基础的一段内存中。