在语法分析 lua_parse 之后,调用 lua_execute 来执行语法分析生成的字节码。
虚拟机的指令是一个枚举型,就是在 opcode.h 中的 OpCode, 通过 lua_execute 中的那个 switch case 来看下指令对应的操作。
> PUSHNIL
case PUSHNIL: tag(top++) = T_NIL; break;
设置栈顶的 Object 类型为 T_NIL,这个栈就是之前所说的那个 Lua 和 C 之间交互的那个栈。
> PUSH0, PUSH1, PUSH2
case PUSH0: tag(top) = T_NUMBER; nvalue(top++) = 0; break; case PUSH1: tag(top) = T_NUMBER; nvalue(top++) = 1; break; case PUSH2: tag(top) = T_NUMBER; nvalue(top++) = 2; break;
设置栈顶的 Object 类型为 T_NUMBER,值为 0/1/2。 这个是指令优化,把操作数放到指令里,从而让指令更短,执行的更快些。
这几个指令是 PUSHBYTE 指令的优化版,或者叫特化版更合适一些。
> PUSHBYTE
case PUSHBYTE: tag(top) = T_NUMBER; nvalue(top++) = *pc++; break;
设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的一个字节)取得。值在字节码中占一个字节。
> PUSHWORD
case PUSHWORD: { CodeWord code; get_word(code,pc); tag(top) = T_NUMBER; nvalue(top++) = code.w; } break;
设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的两个字节)取得。值在字节码中占两个字节。
get_word 是 y.tab.c 中的 code_word 的相反过程,CodeWord 是个联合体
typedef union { struct {char c1; char c2;} m; Word w; } CodeWord;
在 code_word 方法
static void code_word (Word n) { CodeWord code; code.w = n; code_byte(code.m.c1); code_byte(code.m.c2); }
可以看到,设置的时候把值设置给 w, 之后调用 code_byte 分别对 w 的两个字节生成字节码。
由于 CodeWord 是个联合体,这里的 w 和 m 是同一片内存,所以 code_word 可以按预期正确执行。
这样的联合体操作是 C 语言里的一个小技巧,比如测字节序(例如你的处理器体系结构是大端序还是小端序)的时候就可以用这样的技巧。
> PUSHFLOAT
case PUSHFLOAT: { CodeFloat code; get_float(code,pc); tag(top) = T_NUMBER; nvalue(top++) = code.f; } break;
设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的四个字节)取得。值在字节码中占四个字节。
get_float 和上面的 get_word 情况一样,不再重复。
> PUSHSTRING
case PUSHSTRING: { CodeWord code; get_word(code,pc); tag(top) = T_STRING; svalue(top++) = lua_constant[code.w]; } break;
设置栈顶的 Object 类型为 T_STRING,值从常量表从取得,下标从当前字节码处取得。下标占两个字节。
> PUSHLOCAL0, PUSHLOCAL1, PUSHLOCAL2, PUSHLOCAL3, PUSHLOCAL4,
PUSHLOCAL5, PUSHLOCAL6, PUSHLOCAL7, PUSHLOCAL8, PUSHLOCAL9,
case PUSHLOCAL0: case PUSHLOCAL1: case PUSHLOCAL2: case PUSHLOCAL3: case PUSHLOCAL4: case PUSHLOCAL5: case PUSHLOCAL6: case PUSHLOCAL7: case PUSHLOCAL8: case PUSHLOCAL9: *top++ = *(base + (int)(opcode-PUSHLOCAL0)); break;
设置栈顶的 Object 为局部变量 N (0<=N<=9),这个也是指令优化。把当前指令 opcode 减去 PUSHLOCAL0 取得偏移量 N。
局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
这几个指令是 PUSHLOCAL 的特化版。
> PUSHLOCAL
case PUSHLOCAL: *top++ = *(base + (*pc++)); break;
设置栈顶的 Object 为局部变量 N (N 的偏移量从字节码中取得)。
> PUSHGLOBAL,
case PUSHGLOBAL: { CodeWord code; get_word(code,pc); *top++ = s_object(code.w); } break;
设置栈顶的 Object 为全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)。
> PUSHINDEXED,
case PUSHINDEXED: --top; if (tag(top-1) != T_ARRAY) { lua_reportbug ("indexed expression not a table"); return 1; } { Object *h = lua_hashdefine (avalue(top-1), top); if (h == NULL) return 1; *(top-1) = *h; } break;
设置数组元素索引,当前的栈的 top 处为要设置的元素索引,top-1 为数组。
通过 lua_hashdefine 把 top 做为索引设置进数组,返回其所在键值对儿 Node 值 val 地址。
设置到 top-1 处,以备后续使用。
> PUSHMARK
case PUSHMARK: tag(top++) = T_MARK; break;
设置栈顶的 Object 为 T_MARK,这个用于标记,比如在函数调用时会在函数入栈后再入栈一个 T_MARK。
> PUSHOBJECT
case PUSHOBJECT: *top = *(top-3); top++; break;
设置栈顶的 Object 为栈顶的倒数第 4 个 Object。
> STORELOCAL0, STORELOCAL1, STORELOCAL2, STORELOCAL3, STORELOCAL4,
STORELOCAL5, STORELOCAL6, STORELOCAL7, STORELOCAL8, STORELOCAL9,
case STORELOCAL0: case STORELOCAL1: case STORELOCAL2: case STORELOCAL3: case STORELOCAL4: case STORELOCAL5: case STORELOCAL6: case STORELOCAL7: case STORELOCAL8: case STORELOCAL9: *(base + (int)(opcode-STORELOCAL0)) = *(--top); break;
设置局部变量 N (0<=N<=9)为栈顶的 Object,这个也是指令优化。把当前指令 opcode 减去 STORELOCAL0 取得偏移量 N。
局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
这几个指令是 STORELOCAL 的特化版。
> STORELOCAL
case STORELOCAL: *(base + (*pc++)) = *(--top); break;
设置局部变量 N (N 的偏移量从字节码中取得)为栈顶的 Object。
> STOREGLOBAL
case STOREGLOBAL: { CodeWord code; get_word(code,pc); s_object(code.w) = *(--top); } break;
设置全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)为栈顶的 Object。
> STOREINDEXED0
case STOREINDEXED0: if (tag(top-3) != T_ARRAY) { lua_reportbug ("indexed expression not a table"); return 1; } { Object *h = lua_hashdefine (avalue(top-3), top-2); if (h == NULL) return 1; *h = *(top-1); } top -= 3; break;
设置数组元素,数组位于 top-3, 数组索引位于 top-2, 数组值位于 top-1。
设置完成后,这三个 Object 出栈。
> STOREINDEXED
case STOREINDEXED: { int n = *pc++; if (tag(top-3-n) != T_ARRAY) { lua_reportbug ("indexed expression not a table"); return 1; } { Object *h = lua_hashdefine (avalue(top-3-n), top-2-n); if (h == NULL) return 1; *h = *(top-1); } top--; } break;
设置指定偏移量的数组元素,偏移量从字节码中取得,数组位于 top-3-n,索引位于 top-2-n,值位于栈顶。
设置完成后,栈顶值出栈。
> STORELIST0,
STORELIST
case STORELIST0: case STORELIST: { int m, n; Object *arr; if (opcode == STORELIST0) m = 0; else m = *(pc++) * FIELDS_PER_FLUSH; n = *(pc++); arr = top-n-1; if (tag(arr) != T_ARRAY) { lua_reportbug ("internal error - table expected"); return 1; } while (n) { tag(top) = T_NUMBER; nvalue(top) = n+m; *(lua_hashdefine (avalue(arr), top)) = *(top-1); top--; n--; } } break;
设置数组值,m 为下标,n 为数组元素个数。要设置的数组值都位于栈上,栈顶为最后一个元素。
所以在 while 循环里给数组赋值是先给下标大的赋值,再给小的赋值,每赋值一个就出栈一个。
> STORERECORD
case STORERECORD: { int n = *(pc++); Object *arr = top-n-1; if (tag(arr) != T_ARRAY) { lua_reportbug ("internal error - table expected"); return 1; } while (n) { CodeWord code; get_word(code,pc); tag(top) = T_STRING; svalue(top) = lua_constant[code.w]; *(lua_hashdefine (avalue(arr), top)) = *(top-1); top--; n--; } } break;
给记录赋值,n 为要赋值的个数。被赋值的元素索引从字节码中取得,右值从栈上取得,赋值后出栈。
记录是指下标为常量字符串的表,数组是指下标为整数的表。具体的区别请参见手册。
> ADJUST
case ADJUST: { Object *newtop = base + *(pc++); while (top < newtop) tag(top++) = T_NIL; top = newtop; /* top could be bigger than newtop */ } break;
调整栈元素个数,元素个数从字节码取出。如果新的栈顶高于当前栈顶,当前栈顶到新的栈顶之间的元素赋空。
一般函数调用之后会调用它调整栈。 ADJUST 的下一个字节码(pc)是需要返回的函数返回值个数。
while 补空的意义就是如果需要返回的函数个数大于实际返回的,则补空。
(反之,需要的返回值少于实际返回的个数的话,多余的会被丢弃,这是通过设置 top 实现的。)
> CREATEARRAY
case CREATEARRAY: if (tag(top-1) == T_NIL) nvalue(top-1) = 101; else { if (tonumber(top-1)) return 1; if (nvalue(top-1) <= 0) nvalue(top-1) = 101; } avalue(top-1) = lua_createarray(nvalue(top-1)); if (avalue(top-1) == NULL) return 1; tag(top-1) = T_ARRAY; break;
新建数组,元素个数从栈顶取得,如果没有指定或者指定一个负数,把它修正为 101。如果栈顶不是个数字,出错。
新建数组,赋值给栈顶元素,并设置栈顶 Object 类型为 T_ARRAY。
(未完待续)