lua代码动态编译详解

lua字节码:提高加载速度,他是以二进制方式直接加载到内存中,不需要转码,而lua源码是utf8的编码
正常情况在内存中,文件的编码都是固定长度,这是为了提高识别速度,但是将文件保存到硬盘,这个就没必要固定长度了,因为在硬盘上的文件是不需要频繁使用的,固定长度会浪费空间,不定长会节约资源,所以一般文件存到硬盘都有它自己的编码,但是读到内存,会将其变为固定长度的编码,比如unicode编码
在这里插入图片描述
程序运行通常有两种方式:静态编译和动态解释,即时编译混合了二者。即时编译是动态编译的一种形式,是一种优化虚拟机运行的技术。

静态编译:如C++和c,必须先编译为机器指令,然后才可以执行,但是载入内存就可以直接执行,速度快
动态编译:如lua,载入内存可以一段一段解释编译,再执行,因为要解释成机器指令,所以执行速度慢
即时编译:如luajit,运行动态编译,考虑到性能,可以考虑对一些代码预先编译成机器码,然后执行这些代码的时候,不需要动态编译了,而直接执行。
注意:lua的源码通常会被编译为字节码,这个可以提高lua的载入内存的速度,但对于lua的执行速度却没有说明提高,因为lua的源码需要被翻译成字节码,而程序真正执行的时候是用的机器码,这个字节码任然需要被翻译成机器码,最后才可以被执行
luaL_loadfile:加载lua源文件

一个简单的例子 require(“xxx”)

//上面这句代码无论放在哪个文件里,当虚拟机执行到这句代码的时候,都会生成三条机器指令
/*
三条指令
1:GETGLOBAL 寻找全局函数require 放在栈顶L->top-1
2:LOADK     加载一个常量字符串 放在栈顶L->top-1
3:CALL      执行函数
OP_CALL
-----》luaD_precall(L, ra, nresults)
*/

//这个函数主要是用来处理执行函数的
//可以处理lua函数
//可以处理c函数
int luaD_precall (lua_State *L, StkId func, int nresults) {
    LClosure *cl;
  ptrdiff_t funcr;
  if (!ttisfunction(func)) /* `func' is not a function? */
    func = tryfuncTM(L, func);  /* check the `function' tag method */
  funcr = savestack(L, func);
  cl = &clvalue(func)->l;
  L->ci->savedpc = L->savedpc;
  if (!cl->isC) {  /* Lua function? prepare its call */
     //..............
  }
  else
  {
     //执行我们事先注册到全局变量的c函数
      n = (*curr_func(L)->c.f)(L);  /* do the actual call */
     //...............
  }
}

//执行到这个位置,按照上面的例子,它在lua里执行的是require函数,所以此处执行的是我们事先注册到全局表中的ll_require
static int ll_require (lua_State *L) {
    //......加载某个文件前的一些准备工作
    for (i=1; ; i++) {
    lua_rawgeti(L, -2, i);  /* get a loader */
    if (lua_isnil(L, -1))
      luaL_error(L, "module " LUA_QS " not found:%s",
                    name, lua_tostring(L, -2));
    lua_pushstring(L, name);
    //这是去加载文件,去生成一个closure放在栈顶
    //这里面包含了语法分析,词法分析,最终会生成机器指令
    lua_call(L, 1, 1);  /* call it */
    if (lua_isfunction(L, -1))  /* did it find module? */
      break;  /* module loaded successfully */
    else if (lua_isstring(L, -1))  /* loader returned error message? */
      lua_concat(L, 2);  /* accumulate it */
    else
      lua_pop(L, 1);
  }
  //执行到此处说明文件已经被加载到内存了也已经被解析机器指令好了
  //开始真正执行了
    //.......lua_call
}

/*
nargs:函数传参个数
nresults:函数的返回值个数
*/
LUA_API void lua_call (lua_State *L, int nargs, int nresults) {
  StkId func;
  lua_lock(L);
  api_checknelems(L, nargs+1);
  checkresults(L, nargs, nresults);
  func = L->top - (nargs+1);
  luaD_call(L, func, nresults);
  adjustresults(L, nresults);
  lua_unlock(L);
}
/*
** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function.
** When returns, all the results are on the stack, starting at the original
** function position.
*/ 
void luaD_call (lua_State *L, StkId func, int nResults) {
  if (++L->nCcalls >= LUAI_MAXCCALLS) {
    if (L->nCcalls == LUAI_MAXCCALLS)
      luaG_runerror(L, "C stack overflow");
    else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
      luaD_throw(L, LUA_ERRERR);  /* error while handing stack error */
  }
  if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
	//最后在luaD_call中调用了luaV_execute来执行closure中的opcode
    luaV_execute(L, 1);  /* call it */
  L->nCcalls--;
  luaC_checkGC(L);
}

初始化 lua.c:

pmain(){
     ......
     luaL_openlibs(L);  /* open libraries */
     ......
     if (lua_stdin_is_tty()) {
	  //我们是否在交互式地运行lua
	  //在控制台直接输入lua代码 点击回车运行
      print_version();//打印lua版本号
      dotty(L);
    }
    else dofile(L, NULL);  /* executes stdin as a file */
}

lua代码的执行步骤

去执行一个函数:
第一步:lvm.c

OP_CALL:
......
switch (luaD_precall(L, ra, nresults))
......

第二步:ldo.c

luaD_precall(){
........
n = (*curr_func(L)->c.f)(L);  /* do the actual call */
........
}

第三步:loadlib.c

ll_require(){
......
lua_call(L, 1, 1);  /* call it */
......
}

第四步:lapi.c

lua_call(){
......
luaD_call(L, func, nresults);
......
}

第五步:ldo.c

luaD_call(){
......
 if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
	--最后在luaD_call中调用了luaV_execute来执行closure中的opcode
    luaV_execute(L, 1);  /* call it */
......
    }
luaD_precall(){
......
--神奇的地方
n = (*curr_func(L)->c.f)(L);  /* do the actual call */
......
}

第六步:loadlib.c

loader_Lua(){
......
if (luaL_loadfile(L, filename) != 0)
......
}

第七步:lauxlib.c

luaL_loadfile(){
.....
if (c == LUA_SIGNATURE[0] && filename) {  /* binary file? */
    lf.f = freopen(filename, "rb", lf.f);  /* reopen in binary mode */
    if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
    /* skip eventual `#!...' */
   while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ;
    lf.extraline = 0;
  }
  ungetc(c, lf.f);
  status = lua_load(L, getF, &lf, lua_tostring(L, -1));
......
}

第八步:lapi.c

lua_load(){
......
  luaZ_init(L, &z, reader, data);
  status = luaD_protectedparser(L, &z, chunkname);
......
}

第九步:ldo.c 对于加载到内存的数据进行解析

luaD_protectedparser(){
......
 luaZ_initbuffer(L, &p.buff);
  status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
  luaZ_freebuffer(L, &p.buff);
......
}
--特别注意-------**************
--下面这个函数是对加载进来的函数进行如何解析
--
static void f_parser (lua_State *L, void *ud) {
  int i;
  Proto *tf;
  Closure *cl;
  struct SParser *p = cast(struct SParser *, ud);
  int c = luaZ_lookahead(p->z);
  luaC_checkGC(L);
  --如果是字节码 走luaU_undump 这个函数
  --如果是普通源码,走luaY_parser这个函数
  tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
                                                             &p->buff, p->name);
  --特别提醒,经过上面一步解析完以后,会生成一个closure*************
  --这个closure非常重要,我们对代码的解析就是为了生成它
  cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
  cl->l.p = tf;
  for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */
    cl->l.upvals[i] = luaF_newupval(L);
  --将closure放在栈的L->top-1位置*********************
  setclvalue(L, L->top, cl);
  incr_top(L);
}
--加载字节码
/*
** load precompiled chunk
*/
Proto* luaU_undump (lua_State* L, ZIO* Z, Mbuffer* buff, const char* name)
{
 LoadState S;
 if (*name=='@' || *name=='=')
  S.name=name+1;
 else if (*name==LUA_SIGNATURE[0])
  S.name="binary string";
 else
  S.name=name;
 S.L=L;
 S.Z=Z;
 S.b=buff;
 LoadHeader(&S);
 return LoadFunction(&S,luaS_newliteral(L,"=?"));
}
--加载普通源码
Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
  struct LexState lexstate;
  struct FuncState funcstate;
  lexstate.buff = buff;
  luaX_setinput(L, &lexstate, z, luaS_new(L, name));
  open_func(&lexstate, &funcstate);
  funcstate.f->is_vararg = VARARG_ISVARARG;  /* main func. is always vararg */
  luaX_next(&lexstate);  /* read first token */
  chunk(&lexstate);
  check(&lexstate, TK_EOS);
  close_func(&lexstate);
  lua_assert(funcstate.prev == NULL);
  lua_assert(funcstate.f->nups == 0);
  lua_assert(lexstate.fs == NULL);
  return funcstate.f;
}

第十步:ldo.c

luaD_pcall(){
......
luaD_rawrunprotected
......
}

第十一步:ldo.c

luaD_rawrunprotected(){
........
 struct lua_longjmp lj;
  lj.status = 0;
  lj.previous = L->errorJmp;  /* chain new error handler */
  L->errorJmp = &lj;
  LUAI_TRY(L, &lj,
    (*f)(L, ud);
  );
  L->errorJmp = lj.previous;  /* restore old error handler */
........
}

第十二步:

luaV_execute(L, 1)
{
   //执行机器指令
}

总结:

当游戏启动的时候,会把入口main.lua文件加载到内存
这个有很多种方式

虚拟机会生成三条指令
1:GETGLOBAL 寻找全局函数require 放在栈顶L->top-1
2:LOADK 加载一个常量字符串 放在栈顶L->top-1
3:CALL 执行函数
OP_CALL

把main.lua想象成一棵树的根节点
在这个文件里每一条可以执行的代码都是一个节点
这个节点是第一次可以被执行的节点,这些节点就是你加载一个lua文件,如果你啥都不做,这个文件会去执行的代码
比如它会去执行这个文件里暴露在函数外面的变量 形如下面:
main.lua

require("xxxA")  --执行节点
require("XXXB")  --执行节点
local a = 10     --执行节点
function B()     --不是执行节点
end
function A()     --不是执行节点
end
function C()     --不是执行节点
end
C()              --执行节点

虚拟机拿到这些节点以后,会为每个节点生成机器指令,生成完以后,就等于说这颗树的根节点机器指令生成完毕,这个时候开始执行,执行的每条指令,就是去执行它的节点,根据节点的情况,如果节点是一个函数或者又是一个文件,又会以当前节点为父节点,生成这个节点的孩子机器指令,然后去执行,就这样一层一层的往下执行
简单的理解:main.lua是根节点,生成它的所有可以执行的孩子节点,也就是机器指令,然后逐条去执行,执行到每个节点的时候,又开始以当前节点根据需要再生成一批可以被执行的机器指令,如此反复,遍生成了一可执行树

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值