首页 > 代码库 > Lua1.1 虚拟机指令分析(一)

Lua1.1 虚拟机指令分析(一)

在语法分析 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。
(未完待续)

Lua1.1 虚拟机指令分析(一)