Lua 指令结构与 luaV_execute 简单解析

2022 再见~ 今天以一篇小文章来结束 2022 啦。这一次的文章篇幅很短,只是一点开胃小菜,简单分析下 Lua 的 opcode 指令结构与 luaV_execute 函数。

指令结构

Lua 指令结构

Lua 有三类结构的指令,分别为 iABCiABxiAsBx,三者的示意图如上。绝大多数指令都是 iABC 型的,只有少量 iABxiAsBx。其中,iABx 的典型指令如 OP_LOADKOP_GETGLOBAL 等,iAsBx 的典型指令如 OP_JMPOP_FORLOP 等。

三种结构的指令里,opcode 的部分都固定为 6 bits,也就是说 Lua 最多只有 64 种指令。剩下的几个参数,分为 BBxsBx 三种,第一种一般是栈上相对于 base 指针的下标(寄存器下标),第二种一般是一些在 Kst 表里的下标或立即数,第三种则是第二种的带符号版本。

sBx 形式的数字并不是正常的带符号数字表示法,而是相对于 K 的偏置值。K 是该参数的最大可取值。因此当 sBx 等于 0 时,其实表示着 -K,当 sBx 等于 K 时,其实表示着 2*K

几个参数的寻址方式共有三类:

  • OpArgR + R 类:对应 RARBRC 等宏,寻址方式为 base + R
  • OpArgK + R 类:对应 RKBRKC 等宏,寻址方式为 ISK(R) ? k + INDEXK(R) : base + R
  • OpArgK + Rx 类:对应 KBx 等宏,寻址方式为 k + Rx

在第二种场景下,R 中的取值类型由最高位决定。如果最高位为 0,代表着是寄存器下标;如果最高位为 1,代表着是立即数(这也是 ISK 这个宏的含义)。而 INDEXK 宏的作用就是除去最高位。

几个相关的指针/表

  • StkId base = L->base: 栈基,供传递函数参数、局部变量等;
  • TValue *k = cl->p->k: 常量表,在解析源码过程中产生,放置一些常数、常量等;
  • TValue g = *cl->env: 全局变量表,顾名思义,放置一些全局的变量;
  • UpVal * cl->upvals: upval 表,在创建 closure 时往下层传递,后边分析 OP_CLOSURE 时再仔细看看;

luaV_execute 函数

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));
  // 当前指令
  // 这个 L->savedpc 在 luaD_precall 中已设置为 p->code,也就是语法解析获得的指令序列,如下:
  //    // 将 pc 指向解析出来的 Proto->code,也就是 opcode 数组
  //    L->savedpc = p->code;  /* starting point */
  pc = L->savedpc;
  // 当前 CallInfo 对应的闭包
  cl = &clvalue(L->ci->func)->l;
  // 栈基
  base = L->base;
  // 常量表,注意到此表随着 closure 一起管理
  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;
    }
    /* warning!! several calls may realloc the stack and invalidate `ra' */
    ra = RA(i);
    lua_assert(base == L->base && L->base == L->ci->base);
    lua_assert(base <= L->top && L->top <= L->stack + L->stacksize);
    lua_assert(L->top == L->ci->top || luaG_checkopenop(i));
    switch (GET_OPCODE(i)) {
      // ...

opcode 一览

typedef enum {
// 注:为了方便分组,opcode 的顺序被我做了调换

// 装载
OP_MOVE,/*      A B     R(A) := R(B)                                    */
OP_LOADK,/*     A Bx    R(A) := Kst(Bx)                                 */
OP_LOADBOOL,/*  A B C   R(A) := (Bool)B; if (C) pc++                    */
OP_LOADNIL,/*   A B     R(A) := ... := R(B) := nil                      */
OP_GETGLOBAL,/* A Bx    R(A) := Gbl[Kst(Bx)]                            */
OP_SETGLOBAL,/* A Bx    Gbl[Kst(Bx)] := R(A)                            */
OP_GETUPVAL,/*  A B     R(A) := UpValue[B]                              */
OP_SETUPVAL,/*  A B     UpValue[B] := R(A)                              */

// 基本运算
OP_ADD,/*       A B C   R(A) := RK(B) + RK(C)                           */
OP_SUB,/*       A B C   R(A) := RK(B) - RK(C)                           */
OP_MUL,/*       A B C   R(A) := RK(B) * RK(C)                           */
OP_DIV,/*       A B C   R(A) := RK(B) / RK(C)                           */
OP_MOD,/*       A B C   R(A) := RK(B) % RK(C)                           */
OP_POW,/*       A B C   R(A) := RK(B) ^ RK(C)                           */
OP_UNM,/*       A B     R(A) := -R(B)                                   */
OP_NOT,/*       A B     R(A) := not R(B)                                */
OP_LEN,/*       A B     R(A) := length of R(B)                          */
OP_CONCAT,/*    A B C   R(A) := R(B).. ... ..R(C)                       */

// 条件、循环、分支跳转
OP_EQ,/*        A B C   if ((RK(B) == RK(C)) ~= A) then pc++            */
OP_LT,/*        A B C   if ((RK(B) <  RK(C)) ~= A) then pc++            */
OP_LE,/*        A B C   if ((RK(B) <= RK(C)) ~= A) then pc++            */
OP_JMP,/*       sBx     pc+=sBx                                 */
OP_TEST,/*      A C     if not (R(A) <=> C) then pc++                   */ 
OP_TESTSET,/*   A B C   if (R(B) <=> C) then R(A) := R(B) else pc++     */ 
OP_FORLOOP,/*   A sBx   R(A)+=R(A+2);
                        if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
OP_FORPREP,/*   A sBx   R(A)-=R(A+2); pc+=sBx                           */
OP_TFORLOOP,/*  A C     R(A+3), ... ,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_SELF,/*      A B C   R(A+1) := R(B); R(A) := R(B)[RK(C)]             */
OP_CALL,/*      A B C   R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*  A B C   return R(A)(R(A+1), ... ,R(A+B-1))              */
OP_RETURN,/*    A B     return R(A), ... ,R(A+B-2)      (see note)      */
OP_CLOSE,/*     A       close all variables in the stack up to (>=) R(A)*/
OP_CLOSURE,/*   A Bx    R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))  */
OP_VARARG/*     A B     R(A), R(A+1), ..., R(A+B-1) = vararg            */

// 表
OP_GETTABLE,/*  A B C   R(A) := R(B)[RK(C)]                             */
OP_SETTABLE,/*  A B C   R(A)[RK(B)] := RK(C)                            */
OP_NEWTABLE,/*  A B C   R(A) := {} (size = B,C)                         */
OP_SETLIST,/*   A B C   R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B        */

} OpCode;

题图来自 Photo by Joshua Rodriguez on Unsplash

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据