【Lua 5.3源码】虚拟机指令分析(二)

1. Proto函数原型

任何一个函数,一个 lua 文件, 经过编译之后都会对应变成Lua的一个LClosure(闭包)对象,一个闭包包含一个函数原型(Proto)和一个upvalues列表。

typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];  /* list of upvalues */
} LClosure;

比较重要的就是生成的 Proto 这个数据结构, 这个结构的定义在 lobject.h 中。

/*
** Function Prototypes
*/
typedef struct Proto {
  CommonHeader;
  lu_byte numparams;  /* 固定参数个数 */
  lu_byte is_vararg;  /* 是否是可变参数列表 */
  lu_byte maxstacksize;  /* 这个函数最多需要寄存器的数量(就是函数栈) */
  int sizeupvalues;  /* upvalues的个数 */
  int sizek;  /* 常量的个数 */
  int sizecode; /* 指令的个数 */
  int sizelineinfo;  /* 字段表示当前函数所有的行总数目 */
  int sizep;  /* 定义的函数(proto)的个数 */
  int sizelocvars; /* 局部变量的个数 */
  int linedefined;  /* 定义的首行(Debug信息)  */
  int lastlinedefined;  /* 定义的尾行(Debug信息)  */
  TValue *k;  /* 常量表 */
  Instruction *code;  /* 指令表 */
  struct Proto **p;  /* 定义的内部函数 */
  int *lineinfo;  /* 每个指令对应的行数 (Debug信息) */
  LocVar *locvars;  /* 局部变量的信息 (Debug信息) */
  Upvaldesc *upvalues;  /* Upvalue的信息 */
  struct LClosure *cache;  /* last-created closure with this prototype */
  TString  *source;  /* lua完整文件路径名(Debug信息) */
  GCObject *gclist;
} Proto;



2. Upvalue

完整的函数是包括Proto和Upvalues,在介绍Proto之前,先介绍Upvalue的概念。在Lua中,函数视为一种对象,那么我们可以在函数内部定义新的函数,如下:

function fun1()
	local a = 100
	local test_fun = function(num)
		if num > a then
			print("true")
		else
			print("false")
		end
	end
	test_fun(50)   -- output : false
	test_fun(200)  -- output : true
end

如上,内部函数test_fun函数在执行过程中可以访问外部函数的局部变量a,那么a变量对于函数test_fun而言就是一个Upvalue。

在Proto的结构中放了Upvalue 的名称(用于调试),是否在父函数栈上,索引位置等信息:

/*  Description of an upvalue for function prototypes */
typedef struct Upvaldesc {
  TString *name;  /*  名字(Debug信息) */
  lu_byte instack;  /* 是否在栈中 */
  lu_byte idx;  /* 索引(可能是外部函寄存器索引,也可能是外部函数的upvalue索引) */
} Upvaldesc;

Lua编译器在编译阶段是如何认定一个变量是Upvalue呢?

  1. 会先在当前函数中找,找到则标记为 Local 变量,否则递归的查找父函数,找到则为 Upvalue, 否则为 Global
  2. 对当前函数的 Upvalue来说,有两种情况:是父函数的 Upvalue;是父函数的 Local;
    当是父函数的 Upvalue时,将 instack 标记置为 0
    否则是父函数的 Local,将 instack 标记置为 1

对于在父函数栈中的Upvalue,值为L->base + idx,否则就是父函数的Upvalue,值为Upvalue[idx]



3. 指令的生成

这篇文章不重点讲述指令的生成流程,只是简单介绍指令产生的地方。

#define CREATE_ABC(o,a,b,c)	((cast(Instruction, o)<<POS_OP) \
			| (cast(Instruction, a)<<POS_A) \
			| (cast(Instruction, b)<<POS_B) \
			| (cast(Instruction, c)<<POS_C))

#define CREATE_ABx(o,a,bc)	((cast(Instruction, o)<<POS_OP) \
			| (cast(Instruction, a)<<POS_A) \
			| (cast(Instruction, bc)<<POS_Bx))

#define CREATE_Ax(o,a)		((cast(Instruction, o)<<POS_OP) \
			| (cast(Instruction, a)<<POS_Ax))

Lua是通过上面三个宏来生成对应的指令,并通过luaK_code添加到对应的FuncState中。(在 LexState 中,成员 struct FuncState *fs, 记录了当前正在解析 FuncState,它相当于一系列被编译的函数栈的栈顶。 每当新解析一个函数时,就会新建一个 FuncState 来记录当前函数编译信息。)



4. 虚拟机的执行

虚拟机相关的代码是在lvm.clvm.h两个文件中。

最核心的函数就是void luaV_execute (lua_State L) ,参数lua_State实际上是每一个LUA线程创建独立的函数栈和线程栈,以及线程执行过程中需要用到的内存管理、字符串管理、gc等信息。

/*
** 'per thread' state
** Lua 主线程 栈 数据结构
** 作用:管理整个栈和当前函数使用的栈的情况,最主要的功能就是函数调用以及和c的通信
*/
struct lua_State {
  CommonHeader;
  lu_byte status; /* 解析容器的用于记录中间状态*/
 
  /* 全局状态机 */
  global_State *l_G;
 
  /* 调用栈:调用栈信息管理(CallInfo 为双向链表结构) */
  unsigned short nci;  /* number of items in 'ci' list - 存储一共多少个CallInfo */
  CallInfo base_ci;  /* CallInfo for first level (C calling Lua) - 调用栈的头部指针 */
  CallInfo *ci;  /* call info for current function - 当前运行函数信息 */
 
  /* 数据栈:栈指针地址管理  StkId = TValue 的数组 */
  StkId top;  /* first free slot in the stack - 线程栈的栈顶指针 */
  StkId stack_last;  /* last free slot in the stack  - 线程栈的最后一个位置 */
  StkId stack;  /* stack base - 栈的指针,当前执行的位置*/
 
 
  const Instruction *oldpc;  /* last pc traced 在当前thread 的解释执行指令的过程中,指向最后一次执行的指令的指针 */
  UpVal *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist; /* GC列表 */
  struct lua_State *twups;  /* list of threads with open upvalues */
  struct lua_longjmp *errorJmp;  /* current error recover point */
 
  /* Hook 相关管理 - 服务于debug模块 */
  volatile lua_Hook hook;
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
  int stacksize;
  int basehookcount;
  int hookcount;
  l_signalT hookmask;
  lu_byte allowhook;
 
  /* 跟C语言通信 管理*/
  unsigned short nCcalls;  /* number of nested C calls */
  unsigned short nny;  /* number of non-yieldable calls in stack */
};

可以看出lua_State里面存储了当前运行的函数信息,以及函数栈、数据栈等等;而luaV_execute也是从lua_State里面取数据和指令进行运行。

而在luaV_execute函数中,可以看到有一个无限循环,从ci中不断的取出指令,然后通过switch (GET_OPCODE(i))来根据不同的opcode进行不同的过程:

void luaV_execute (lua_State *L) {
  CallInfo *ci = L->ci;
  LClosure *cl;
  TValue *k;
  StkId base;
  ci->callstatus |= CIST_FRESH;  /* fresh invocation of 'luaV_execute" */
 newframe:  /* reentry point when frame changes (call/return) */
  lua_assert(ci == L->ci);
  cl = clLvalue(ci->func);  /* local reference to function's closure */
  k = cl->p->k;  /* local reference to function's constant table */
  base = ci->u.l.base;  /* local copy of function's base */
  /* main loop of interpreter */
  for (;;) {
  	Instruction i;
    StkId ra;
    vmfetch();   //获取指令,并且准备其执行的相关数据初始化
    vmdispatch (GET_OPCODE(i)) {
    	vmcase(...) {   //对于不同的指令处理
		...
        vmbreak;
      }
  	}
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值