Nook in the Lunar Mare

Lua VM等二三事

Feb 26, 2021


TODO: 更新进展


Luac Introduction

本次参考lua5-4.2的源代码,luac的主体代码在luac.c文件中,通过ldump.c中的luaU_dump函数将lua代码编译luac文件输出至输出文件中。执行则是在undump中。

int luaU_dump(lua_State *L, const Proto *f, lua_Writer w, void *data,
              int strip) {
  DumpState D;
  D.L = L;
  D.writer = w;
  D.data = data;
  D.strip = strip;
  D.status = 0;
  dumpHeader(&D);
  dumpByte(&D, f->sizeupvalues);
  dumpFunction(&D, f, NULL);
  return D.status;
}

实验中采用learnxinyminutes中提供的learnlua.lua作为源代码,如下命令获取编译后的luac文件

luac -o learnlua.luac learnlua.lua

后文中的一些lua代码也会作为例子,用来对比之类的。另外我还创建了一个空的lua文件,方便查看编译后的结果。lua中的main拥有可变参数列表,且包含一个upvalue–LUA_ENV。

>>> touch none.lua
>>> luac -o none.luac none.lua
>>> luac -l none.lua
main <none.lua:0,0> (2 instructions at 0x55e43b8eacc0)
0+ params, 2 slots, 1 upvalue, 0 locals, 0 constants, 0 functions
        1       [1]     VARARGPREP      0
        2       [1]     RETURN          0 1 1   ; 0 out
>>> r2 none.luac
[0x00000000]> px
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000000  1b4c 7561 5400 1993 0d0a 1a0a 0408 0878  .LuaT..........x
0x00000010  5600 0000 0000 0000 0000 0000 2877 4001  V...........(w@.
0x00000020  8a40 6e6f 6e65 2e6c 7561 8080 0001 0282  .@none.lua......
0x00000030  5100 0000 4600 0101 8081 0100 0080 8201  Q...F...........
0x00000040  0080 8081 855f 454e 56ff ffff ffff ffff  ....._ENV.......
0x00000050  ffff ffff ffff ffff ffff ffff ffff ffff  ................

Luac Helper Function

这一部分内容主要是方便分析时进行参考,可暂时略过。Dump(即所谓"compile")过程中常见到的一个结构体DumpState在后续dump过程中多用于锁操作(strip则是由命令行的-s选项控制是否去除debug信息),对于解析luac文件可暂时忽略其技术细节,这里为了方便还是贴上DumpState的定义。

typedef struct {
  lua_State *L;
  lua_Writer writer;
  void *data;
  int strip;
  int status;
} DumpState;

辅助函数,dumpBlock实现的功能类似memcpy。

// Base Dump Function, similar to 'memcpy'
static void dumpBlock (DumpState *D, const void *b, size_t size) {
  if (D->status == 0 && size > 0) {
    lua_unlock(D->L);
    D->status = (*D->writer)(D->L, b, size, D->data);
    lua_lock(D->L);
  }
}

// Macros For Dumping Basic Objects
#define dumpLiteral(D, s)   dumpBlock(D,s,sizeof(s) - sizeof(char))
#define dumpVector(D,v,n)   dumpBlock(D,v,(n)*sizeof((v)[0]))
#define dumpVar(D,x)        dumpVector(D,&x,1)

// wrapper for dumping the size of Object 
// tricky function
// : transform the size -> several bytes in reverse order
// every byte's MSB is 0 except the last byte.
// and keep the size of 'size' as small as possible
// the size_t is uint32 in my machine so DIBS is 10
#define DIBS    ((sizeof(size_t) * 8 / 7) + 1)
static void dumpSize (DumpState *D, size_t x) {
  lu_byte buff[DIBS];
  int n = 0;
  do {
    buff[DIBS - (++n)] = x & 0x7f;  /* fill buffer in reverse order */
    x >>= 7;
  } while (x != 0);
  buff[DIBS - 1] |= 0x80;  /* mark last byte */
  dumpVector(D, buff + DIBS - n, n);
}

上述基础函数组合对具体的数据类型进行dump

static void dumpByte (DumpState *D, int y) {
  lu_byte x = (lu_byte)y;
  dumpVar(D, x);
}

static void dumpInt (DumpState *D, int x) {
  dumpSize(D, x);
}

static void dumpNumber (DumpState *D, lua_Number x) {
  dumpVar(D, x);
}

static void dumpInteger (DumpState *D, lua_Integer x) {
  dumpVar(D, x);
}

// dump format 
// -----------------------------
// | SizeOfThisString | String |
// -----------------------------
// 
static void dumpString (DumpState *D, const TString *s) {
  if (s == NULL)
    dumpSize(D, 0);
  else {
    size_t size = tsslen(s);
    const char *str = getstr(s);
    dumpSize(D, size + 1);
    dumpVector(D, str, size);
  }
}

剩下的一些主要是封装了对lua脚本中存在的各个constant、proto等数据的统一dump,打包dumpFunction这个函数中。

Other Data Struct

typedef struct Proto {
  CommonHeader;
  lu_byte numparams;  /* number of fixed (named) parameters */
  lu_byte is_vararg;
  lu_byte maxstacksize;  /* number of registers needed by this function */
  int sizeupvalues;  /* size of 'upvalues' */
  int sizek;  /* size of 'k' */
  int sizecode;
  int sizelineinfo;
  int sizep;  /* size of 'p' */
  int sizelocvars;
  int sizeabslineinfo;  /* size of 'abslineinfo' */
  int linedefined;  /* debug information  */
  int lastlinedefined;  /* debug information  */
  TValue *k;  /* constants used by the function */
  Instruction *code;  /* opcodes */
  struct Proto **p;  /* functions defined inside the function */
  Upvaldesc *upvalues;  /* upvalue information */
  ls_byte *lineinfo;  /* information about source lines (debug information) */
  AbsLineInfo *abslineinfo;  /* idem */
  LocVar *locvars;  /* information about local variables (debug information) */
  TString  *source;  /* used for debug information */
  GCObject *gclist;
} Proto;

Luac Header Format

dumpHeader函数用于将luac header写入luac文件中,这里为了方便把相关的定义都放在一起。说实话我不太理解LUAC_DATA的作用,注释中写着用于错误处理,但那个1993感觉更像是一种纪念性质的MagicNumber?它只在后续undump中checkHeader时从文件中加载然后比较原始的LUAC_DATA宏,以此判断是否发生文件损坏。

/* mark for precompiled code ('<esc>Lua') */
#define LUA_SIGNATURE   "\x1bLua"

#define LUA_VERSION_MAJOR   "5"
#define LUA_VERSION_MINOR   "4"
#define MYINT(s)    (s[0]-'0')  /* assume one-digit numerals */

/* Version is 0x54 in this case */
#define LUAC_VERSION    (MYINT(LUA_VERSION_MAJOR)*16+MYINT(LUA_VERSION_MINOR))
#define LUAC_FORMAT 0   /* this is the official format */

/* data to catch conversion errors */
#define LUAC_DATA   "\x19\x93\r\n\x1a\n"

/* Related Types */
#define LUA_INTEGER     long long
#define LUA_NUMBER  double
#define LUAC_INT    0x5678
#define LUAC_NUM    cast_num(370.5)

typedef l_uint32 Instruction;
typedef LUA_INTEGER lua_Integer;
typedef LUA_NUMBER lua_Number;

/* Force Cast */
#define cast(t, exp)    ((t)(exp))
#define cast_num(i) cast(lua_Number, (i))


static void dumpHeader (DumpState *D) {
  dumpLiteral(D, LUA_SIGNATURE);
  dumpByte(D, LUAC_VERSION);
  dumpByte(D, LUAC_FORMAT);
  dumpLiteral(D, LUAC_DATA);
  dumpByte(D, sizeof(Instruction));
  dumpByte(D, sizeof(lua_Integer));
  dumpByte(D, sizeof(lua_Number));
  dumpInteger(D, LUAC_INT);
  dumpNumber(D, LUAC_NUM);
}

这里分别将SIGNATURELUAC_VERSION等元信息保存在luac header中。可总结出Header中各成员对应的偏移位置(以5.4.2为例)。最后的两个dump对象在后续的undump中分别被作为int和float载入,以检查int和float的编译格式是否正确。(类似测试用例)

offset (int Hex)offset (in Byte)member_descvalue
0x00-0x030-3lua’s signature\x{=tex}1bLua”
0x044luac’s version0x54
0x055luac’s format0x00
0x06-0x0B6-11 (6B)luac data\x19{=tex}\x93{=tex}\r\n{=tex}\x{=tex}1a\n{=tex}”
0x0C12type-Instruction’s size (uint32)0x04
0x0D13type-lua_integer’s size (long long)0x08
0x0E14type-lua_Number’s size (double)0x08
0x0F-0x1615-22 (8B)check if the integer format is matched\x78{=tex}\x56{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}”
0x17-0x1E23-30 (8B)check if the float format is matched\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x00{=tex}\x28{=tex}\x77{=tex}\x40{=tex}”
0x1F31number of upvalue arrays1 at least (as I know)

前文中的learnlua.lua编译后的部分二进制数据如下,可以对照参考。

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000000  1b4c 7561 5400 1993 0d0a 1a0a 0408 0878  .LuaT..........x
0x00000010  5600 0000 0000 0000 0000 0000 2877 4001  V...........(w@.
0x00000020  8e40 6c65 6172 6e6c 7561 2e6c 7561 8080  .@learnlua.lua..
0x00000030  0001 0b02 a851 0000 000f 8000 010f 8002  .....Q..........

0x1F位置的代码来依照代码中的定义应该不属于luac header,但可以与header中的定义合并到一起处理,暂且也作为header的一部分吧。

dumpByte(&D, f->sizeupvalues);

upvalue涉及lua中变量作用域的概念,可理解为在作用域内的一种局部变量。下面例子的局部变量n即为f2的upvalue(f1的本地局部变量),由于n为f2的upvalue,f2被赋值给g1,n尽管是局部变量但并不会因为f1的执行完成而结束生命周期,而是继续留存在内存空间中。(参加ref3)

function f1(n)
    local function f2()
        print(n)
    end
    n = n + 10
    return f2
end
 
g1 = f1(1979)
g1() -- print out 1989

可以通过luac -l看看各闭包的参数

> luac -l upv.lua    
main <upv.lua:0,0> (10 instructions at 0x55c54d5e7cc0)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 1 function
        1       [1]     VARARGPREP      0
        2       [6]     CLOSURE         0 0     ; 0x55c54d5e7f20
        3       [1]     SETTABUP        0 0 0   ; _ENV "f1"
        4       [7]     GETTABUP        0 0 0   ; _ENV "f1"
        5       [7]     LOADI           1 1979
        6       [7]     CALL            0 2 2   ; 1 in 1 out
        7       [7]     SETTABUP        0 1 0   ; _ENV "g1"
        8       [8]     GETTABUP        0 0 1   ; _ENV "g1"
        9       [8]     CALL            0 1 1   ; 0 in 0 out
        10      [8]     RETURN          0 1 1   ; 0 out

function <upv.lua:1,6> (3 instructions at 0x55c54d5e7f20)
1 param, 2 slots, 1 upvalue, 2 locals, 0 constants, 1 function
        1       [4]     CLOSURE         1 0     ; 0x55c54d5e8240
        2       [5]     RETURN          1 2 0   ; 1 out
        3       [6]     RETURN          2 1 0   ; 0 out

function <upv.lua:2,4> (4 instructions at 0x55c54d5e8240)
0 params, 2 slots, 2 upvalues, 0 locals, 1 constant, 0 functions
        1       [3]     GETTABUP        0 0 0   ; _ENV "print"
        2       [3]     GETUPVAL        1 1     ; n
        3       [3]     CALL            0 2 1   ; 1 in 0 out

Luac Body (Functions)

luaU_dump的调用的最后一个函数即为dumpFunction,它完成了luac文件剩下数据的生成。

static void dumpFunction (DumpState *D, const Proto *f, TString *psource) {
  if (D->strip || f->source == psource)
    dumpString(D, NULL);  /* no debug info or same source as its parent */
  else
    dumpString(D, f->source);
  dumpInt(D, f->linedefined);
  dumpInt(D, f->lastlinedefined);
  dumpByte(D, f->numparams);
  dumpByte(D, f->is_vararg);
  dumpByte(D, f->maxstacksize);
  dumpCode(D, f);
  dumpConstants(D, f);
  dumpUpvalues(D, f);
  dumpProtos(D, f);
  dumpDebug(D, f);
}

Offset Overview

Source File Info

这里调用链为dumpString->dumpSize,简单来说就是将源文件名保存(可以用-s去除),格式为源文件名长度 源文件名。尽管dumpSize设计时支持多字节长度(通过检测字节最高位bit是否为1判断是否为size变量的最后一个字节),这里在处理源文件名时通常都为1个字节,实验环境下的linux我难以用编辑工具创建更长的文件名了。

因此通常情况下可以用size_byte & 0x7f获得源文件名的长度。若要追求更高的鲁棒性就只能老老实实采用上面的loadSize完成。

如果是stripped之后的luac文件,该字节设置为0x80,代表0值,无长度。

Function Prototype Info

首先是两个int变量,各占4字节。且虽说这东西也算某种意义上的debug info, 但luac在处理这个信息时并不会受-s选项控制,而是忠实地记录下行号。

  • linedefined 函数定义起始处的行号
  • lastlinedefined 函数定义结束处的行号

luac文件的开头描述的是main这一对象,因此在开头的linedefined和lastlinedefined值都为0。目前我并不知道有什么技巧可以定义lua中的main,因此我倾向于认为在任何luac文件里最初的这两个值都是0。(另外就是由于dumpInt底层实现为dumpSize,所以在luac文件这两个位置上的字节都是0x80)

  • numparams 代表函数参数的个数,这个很好理解
  • is_vararg 是否为变长参数,类似printf(…)这样的函数。

这两个参量都比较容易理解,开头的main是可变参的,此外大多文件不涉及main故参数个数为0,它们采用的是dumpByte因此luac开头对应位置上的字节分别为0x00和0x01(可变)

  • maxstacksize 记录的是函数所需的最大寄存器数目,这个名字可能是历史遗留。由于0号和1号寄存器永远保持可用,故这个值至少会是2

总结一下,上述信息都能在luac -l xxx.luac输出的结果中找到,其中slot对应的就是maxstacksize

Function Code Info

格式为"code_size + code_data",与前文中所提到的字符串dump方法相同(dumpCode中对size的dump也采用dumpSize实现,对数据的dump则是使用dumpVector)。这些数据都是对应的opcode数据,至于代码到opcode的转换则是在其他地方完成的。

  • constants的dump首先dump保存了constants的数目(依旧是dumpSize,后续相同则省略)。对每个constant都用TAG标记变量类型,因此dump的时候先dump TAG(本质为dumpByte),再调用变量对应的dump函数进行操作。TAG的类型和对应的dump函数一并记上。
    • LUA_VNUMFLT – dumpNuber
    • LUA_VNUMINT – dumpInteger
    • LUA_VSHRSTR – dumpString
    • LUA_VLNGSTR – dumpString
    • LUA_VNIL / LUA_VFALSE / LUA_VTRUE
  • upvalues 则是记录sizeupvalues,再对upvalue的数据进行dump。每个upvalue dump占3个字节,分别为upvalue中的属性:
    • instack
    • idx
    • kind

Protos

这一步首先记录所有的proto数,之后便是遍历所有proto,调用dumpFunction进行处理,递归的过程。

Debug Info – Controled by Strip Option

luac的-s参数控制,若是stripped的版本以下这些信息都会用0x80填充。

  • lineinfo – size + lineinfo_datas (vector) 源代码行数信息
  • abslineinfo – size + abslineinfo_datas 每个data都dump两个属性,absline用于构建源代码位置(line)和指令(pc)的对应关系
    1. pc – dumpInt
    2. line – dumpInt
  • localvars – size + local_var_datas,本地变量,每个data对如下三个属性进行dump
    1. varname – dumpString
    2. startpc – dumpInt 变量第一次激活所对应的指令位置
    3. endpc – dumpInt 变量第一次死亡的指令位置
  • upvalues – size + upvalue_datas,对每个data只dump一个属性
    1. name – dumpString

Luac Opcodes

Opcode Format

opcode的格式等定义于lopcodes.h当中,共计五种类型的指令。

/*===========================================================================
  We assume that instructions are unsigned 32-bit integers.
  All instructions have an opcode in the first 7 bits.
  Instructions can have the following formats:

        3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
        1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
iABC          C(8)     |      B(8)     |k|     A(8)      |   Op(7)     |
iABx                Bx(17)               |     A(8)      |   Op(7)     |
iAsBx              sBx (signed)(17)      |     A(8)      |   Op(7)     |
iAx                           Ax(25)                     |   Op(7)     |
isJ                           sJ(25)                     |   Op(7)     |

  A signed argument is represented in excess K: the represented value is
  the written unsigned value minus K, where K is half the maximum for the
  corresponding unsigned argument.
===========================================================================*/

完整的opcode指令采用enum给出,注释中也标记了所属的指令类型


typedef enum {
/*----------------------------------------------------------------------
  name      args    description
------------------------------------------------------------------------*/
OP_MOVE,/*  A B R[A] := R[B]                    */
OP_LOADI,/* A sBx   R[A] := sBx                 */
OP_LOADF,/* A sBx   R[A] := (lua_Number)sBx             */
OP_LOADK,/* A Bx    R[A] := K[Bx]                   */
OP_LOADKX,/*    A   R[A] := K[extra arg]                */
OP_LOADFALSE,/* A   R[A] := false                   */
OP_LFALSESKIP,/*A   R[A] := false; pc++             */
OP_LOADTRUE,/*  A   R[A] := true                    */
OP_LOADNIL,/*   A B R[A], R[A+1], ..., R[A+B] := nil        */
OP_GETUPVAL,/*  A B R[A] := UpValue[B]              */
OP_SETUPVAL,/*  A B UpValue[B] := R[A]              */

OP_GETTABUP,/*  A B C   R[A] := UpValue[B][K[C]:string]         */
OP_GETTABLE,/*  A B C   R[A] := R[B][R[C]]              */
OP_GETI,/*  A B C   R[A] := R[B][C]                 */
OP_GETFIELD,/*  A B C   R[A] := R[B][K[C]:string]           */

OP_SETTABUP,/*  A B C   UpValue[A][K[B]:string] := RK(C)        */
OP_SETTABLE,/*  A B C   R[A][R[B]] := RK(C)             */
OP_SETI,/*  A B C   R[A][B] := RK(C)                */
OP_SETFIELD,/*  A B C   R[A][K[B]:string] := RK(C)          */

OP_NEWTABLE,/*  A B C k R[A] := {}                  */

OP_SELF,/*  A B C   R[A+1] := R[B]; R[A] := R[B][RK(C):string]  */

OP_ADDI,/*  A B sC  R[A] := R[B] + sC               */

OP_ADDK,/*  A B C   R[A] := R[B] + K[C]             */
OP_SUBK,/*  A B C   R[A] := R[B] - K[C]             */
OP_MULK,/*  A B C   R[A] := R[B] * K[C]             */
OP_MODK,/*  A B C   R[A] := R[B] % K[C]             */
OP_POWK,/*  A B C   R[A] := R[B] ^ K[C]             */
OP_DIVK,/*  A B C   R[A] := R[B] / K[C]             */
OP_IDIVK,/* A B C   R[A] := R[B] // K[C]                */

OP_BANDK,/* A B C   R[A] := R[B] & K[C]:integer         */
OP_BORK,/*  A B C   R[A] := R[B] | K[C]:integer         */
OP_BXORK,/* A B C   R[A] := R[B] ~ K[C]:integer         */

OP_SHRI,/*  A B sC  R[A] := R[B] >> sC              */
OP_SHLI,/*  A B sC  R[A] := sC << R[B]              */

OP_ADD,/*   A B C   R[A] := R[B] + R[C]             */
OP_SUB,/*   A B C   R[A] := R[B] - R[C]             */
OP_MUL,/*   A B C   R[A] := R[B] * R[C]             */
OP_MOD,/*   A B C   R[A] := R[B] % R[C]             */
OP_POW,/*   A B C   R[A] := R[B] ^ R[C]             */
OP_DIV,/*   A B C   R[A] := R[B] / R[C]             */
OP_IDIV,/*  A B C   R[A] := R[B] // R[C]                */

OP_BAND,/*  A B C   R[A] := R[B] & R[C]             */
OP_BOR,/*   A B C   R[A] := R[B] | R[C]             */
OP_BXOR,/*  A B C   R[A] := R[B] ~ R[C]             */
OP_SHL,/*   A B C   R[A] := R[B] << R[C]                */
OP_SHR,/*   A B C   R[A] := R[B] >> R[C]                */

OP_MMBIN,/* A B C   call C metamethod over R[A] and R[B]        */
OP_MMBINI,/*    A sB C k    call C metamethod over R[A] and sB  */
OP_MMBINK,/*    A B C k     call C metamethod over R[A] and K[B]    */

OP_UNM,/*   A B R[A] := -R[B]                   */
OP_BNOT,/*  A B R[A] := ~R[B]                   */
OP_NOT,/*   A B R[A] := not R[B]                */
OP_LEN,/*   A B R[A] := #R[B] (length operator)         */

OP_CONCAT,/*    A B R[A] := R[A].. ... ..R[A + B - 1]       */

OP_CLOSE,/* A   close all upvalues >= R[A]          */
OP_TBC,/*   A   mark variable A "to be closed"          */
OP_JMP,/*   sJ  pc += sJ                    */
OP_EQ,/*    A B k   if ((R[A] == R[B]) ~= k) then pc++      */
OP_LT,/*    A B k   if ((R[A] <  R[B]) ~= k) then pc++      */
OP_LE,/*    A B k   if ((R[A] <= R[B]) ~= k) then pc++      */

OP_EQK,/*   A B k   if ((R[A] == K[B]) ~= k) then pc++      */
OP_EQI,/*   A sB k  if ((R[A] == sB) ~= k) then pc++        */
OP_LTI,/*   A sB k  if ((R[A] < sB) ~= k) then pc++         */
OP_LEI,/*   A sB k  if ((R[A] <= sB) ~= k) then pc++        */
OP_GTI,/*   A sB k  if ((R[A] > sB) ~= k) then pc++         */
OP_GEI,/*   A sB k  if ((R[A] >= sB) ~= k) then pc++        */

OP_TEST,/*  A k if (not R[A] == k) then pc++            */
OP_TESTSET,/*   A B k   if (not R[B] == k) then pc++ else R[A] := R[B]  */

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 k return R[A](R[A+1], ... ,R[A+B-1])      */

OP_RETURN,/*    A B C k return R[A], ... ,R[A+B-2]  (see note)  */
OP_RETURN0,/*       return                      */
OP_RETURN1,/*   A   return R[A]                 */

OP_FORLOOP,/*   A Bx    update counters; if loop continues then pc-=Bx; */
OP_FORPREP,/*   A Bx    <check values and prepare counters>;
                        if not to run then pc+=Bx+1;            */

OP_TFORPREP,/*  A Bx    create upvalue for R[A + 3]; pc+=Bx     */
OP_TFORCALL,/*  A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]);  */
OP_TFORLOOP,/*  A Bx    if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */

OP_SETLIST,/*   A B C k R[A][C+i] := R[A+i], 1 <= i <= B        */

OP_CLOSURE,/*   A Bx    R[A] := closure(KPROTO[Bx])         */

OP_VARARG,/*    A C R[A], R[A+1], ..., R[A+C-2] = vararg        */

OP_VARARGPREP,/*A   (adjust vararg parameters)          */

OP_EXTRAARG/*   Ax  extra (larger) argument for previous opcode */
} OpCode;

Generate Opcode

lua代码到opcode的转换实现在lparser中,此功能的入口为luaY_parser --> mainfunc,先对main进行编译,之后对main中定义的函数进行编译,是一个递归的过程。

luaY_parser负责一些准备工作,处理主要在mainfunc里,新的lua版本出于性能等考虑放弃了yacc实现,采用手写的parser,因此该文件里有相当一部分函数是用于处理代码解析token的。

/*
** compiles the main function, which is a regular vararg function with an
** upvalue named LUA_ENV
*/
static void mainfunc (LexState *ls, FuncState *fs) {
  BlockCnt bl;
  Upvaldesc *env;
  open_func(ls, fs, &bl);
  setvararg(fs, 0);  /* main function is always declared vararg */
  env = allocupvalue(fs);  /* ...set environment upvalue */
  env->instack = 1;
  env->idx = 0;
  env->kind = VDKREG;
  env->name = ls->envn;
  luaC_objbarrier(ls->L, fs->f, env->name);
  luaX_next(ls);  /* read first token */
  statlist(ls);  /* parse main body */
  check(ls, TK_EOS);
  close_func(ls);
}

而从token到instruction的过程则是在statement中完成。statlist内部循环调用statement处理这些token


static void statement (LexState *ls) {
  int line = ls->linenumber;  /* may be needed for error messages */
  enterlevel(ls);
  switch (ls->t.token) {
    case ';': {  /* stat -> ';' (empty statement) */
      luaX_next(ls);  /* skip ';' */
      break;
    }
    case TK_IF: {  /* stat -> ifstat */
      ifstat(ls, line);
      break;
    }
    case TK_WHILE: {  /* stat -> whilestat */
      whilestat(ls, line);
      break;
    }
    case TK_DO: {  /* stat -> DO block END */
      luaX_next(ls);  /* skip DO */
      block(ls);
      check_match(ls, TK_END, TK_DO, line);
      break;
    }
    case TK_FOR: {  /* stat -> forstat */
      forstat(ls, line);
      break;
    }
    case TK_REPEAT: {  /* stat -> repeatstat */
      repeatstat(ls, line);
      break;
    }
    case TK_FUNCTION: {  /* stat -> funcstat */
      funcstat(ls, line);
      break;
    }
    case TK_LOCAL: {  /* stat -> localstat */
      luaX_next(ls);  /* skip LOCAL */
      if (testnext(ls, TK_FUNCTION))  /* local function? */
        localfunc(ls);
      else
        localstat(ls);
      break;
    }
    case TK_DBCOLON: {  /* stat -> label */
      luaX_next(ls);  /* skip double colon */
      labelstat(ls, str_checkname(ls), line);
      break;
    }
    case TK_RETURN: {  /* stat -> retstat */
      luaX_next(ls);  /* skip RETURN */
      retstat(ls);
      break;
    }
    case TK_BREAK: {  /* stat -> breakstat */
      breakstat(ls);
      break;
    }
    case TK_GOTO: {  /* stat -> 'goto' NAME */
      luaX_next(ls);  /* skip 'goto' */
      gotostat(ls);
      break;
    }
    default: {  /* stat -> func | assignment */
      exprstat(ls);
      break;
    }
  }
  lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
             ls->fs->freereg >= luaY_nvarstack(ls->fs));
  ls->fs->freereg = luaY_nvarstack(ls->fs);  /* free registers */
  leavelevel(ls);
}

/* }====================================================================== */

上面就是根据不同的Token调用相应的处理器函数,整个流程类似状态机模型

Refs

1. Upvalues

2. Lua upvalue 的实现

3. Lua 的upvalue和闭包

4. how upvalues are counted

5. lua main function