我们来看看lua vm在解析下面源码并生成bytecode时的整个过程:

 foo = "bar"
local a, b = "a", "b"
foo = a

首先我们先使用ChunkySpy这个工具来看看vm最终会具体生成什么样的vm instructions

在这里,开头为[数字]的行是vm真正生成的字节码,我们看到一共生成了六行字节码。首先loadk将常量表中下标为1的常量即"bar"赋给寄存器0;然后setglobal将寄存器0的内容赋给全局变量表中下标为0的全局变量即foo;loadk再将"a"和"b"分别赋值给了寄存器0、1,在这里寄存器0和1分别表示当前函数的local变量即变量a和b;最后setglobal将变量a的值赋给了全局变量foo;最后一个return01是vm在每一个chunk最后都会生成了,并没有什么用。现在应该比较清除的了解了lua vm生成的字节码的含义了,接下来我们看看vm是怎样且为什么生成这些个字节码的。

当我们用luaL_dofile函数执行这个lua脚本源码时会有两个阶段,第一个是将脚本加载进内存,分词解析并生成字节码并将其整个包裹为main chunk放于lua stack栈顶,第二是调用lua_pcall执行这个chunk,这里我们只会分析第一个过程。

前面几篇文章说了,当dofile时会跑到一个叫做luaY_parser的函数中,

 Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
struct LexState lexstate;
struct FuncState funcstate;
-- ... ...
funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */
luaX_next(&lexstate); /* read first token */
chunk(&lexstate);
-- ... ...
return funcstate.f;
}

函数luaY_parser前面两行定义了LexState和FuncState结构体变量,其中LexState不仅用于保存当前的词法分析状态信息,而且也保存了整个编译系统的全局状态,FuncState结构体来保存当前函数编译的状态数据。在lua源码中都会有一个全局的函数执行体,即为main func,在开始解析的时候当前的函数必然是main func函数,此时第三行的funcstate表示了这个函数的状态,由于lua规定这个函数必然会接收不定参数因此第五行将is_vararg标识设为VARARG_ISVARARG。接着第六行luaX_next解析文件流分离出第一个token,将其保存在lexstate的t成员中,此时t为“foo”全局变量。接着调用了chunk函数,这里开始了递归下降解析的全部过程:

 static void chunk (LexState *ls) {
/* chunk -> { stat [`;'] } */
int islast = ;
enterlevel(ls);
while (!islast && !block_follow(ls->t.token)) {
islast = statement(ls);//递归下降点
testnext(ls, ';');
lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
ls->fs->freereg >= ls->fs->nactvar);
ls->fs->freereg = ls->fs->nactvar; /* free registers */
}
leavelevel(ls);
}

lua是有作用域层次概念的,因此当进入一个层次时会调用enterlevel函数,离开当前层次则会调用leavelevel函数。首先进入while循环,当前token为“foo”,这既不是终结标志也不是一个block开始的词素,因此会进入statement函数,statement函数主体是一个长长的switch...case...代码结构,根据第一个token进入不同的调用解析分支。在我们这个例子中会进入default分支:

 static int statement (LexState *ls) {
-- ... ...
switch (ls->t.token) {
case TK_IF: { /* stat -> ifstat */
ifstat(ls, line);
return ;
}
case TK_WHILE: { /* stat -> whilestat */
whilestat(ls, line);
return ;
}
-- ... ...
default: {
exprstat(ls);
return ; /* to avoid warnings */
}
}
}

进入exprstate函数:

 static void exprstat (LexState *ls) {
/* stat -> func | assignment */
FuncState *fs = ls->fs;
struct LHS_assign v;
primaryexp(ls, &v.v);
if (v.v.k == VCALL) /* stat -> func */
SETARG_C(getcode(fs, &v.v), ); /* call statement uses no results */
else { /* stat -> assignment */
v.prev = NULL;
assignment(ls, &v, );
}
}

第四行的LHS_assign结构体是为了处理多变量赋值的情况的,例如a,b,c = ...。在LHS_assign中成员v类型为expdesc描述了等号左边的变量,详情可见上篇文章里对expdesc的介绍。接下来进入primaryexp,来获取并填充“foo”变量的expdesc信息,这会接着进入prefixexp函数中

 static void prefixexp (LexState *ls, expdesc *v) {
/* prefixexp -> NAME | '(' expr ')' */
switch (ls->t.token) {
case '(': {
int line = ls->linenumber;
luaX_next(ls);
expr(ls, v);
check_match(ls, ')', '(', line);
luaK_dischargevars(ls->fs, v);
return;
}
case TK_NAME: {
singlevar(ls, v);
return;
}
default: {
luaX_syntaxerror(ls, "unexpected symbol");
return;
}
}
}

由于当前token是“foo”,因此进入TK_NAME分支,调用singlevar。

 static void singlevar (LexState *ls, expdesc *var) {
TString *varname = str_checkname(ls);
FuncState *fs = ls->fs;
if (singlevaraux(fs, varname, var, ) == VGLOBAL)
var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */
}
static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
if (fs == NULL) { /* no more levels? */
init_exp(var, VGLOBAL, NO_REG); /* default is global variable */
return VGLOBAL;
}
else {
int v = searchvar(fs, n); /* look up at current level */
if (v >= ) {
init_exp(var, VLOCAL, v);
if (!base)
markupval(fs, v); /* local will be used as an upval */
return VLOCAL;
}
else { /* not found at current level; try upper one */
if (singlevaraux(fs->prev, n, var, ) == VGLOBAL)
return VGLOBAL;
var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */
var->k = VUPVAL; /* upvalue in this level */
return VUPVAL;
}
}

在singlevaraux函数中会判断变量是local、upvalue还是global的。如果fs为null了则说明变量为全局的,否则进入searchvar在当前的函数局部变量数组中查找,否则根据fs的prev成员取得其父函数的FuncState并传入singlevaraux中递归查找,如果前面的都没满足则变量为upvlaue。此例中进入第21行中,由于fs已经指向了main func因此其prev为null,“foo”判定为global并返回到exprstate函数中。在取得了“foo”的信息后,因为“foo”不是函数调用,因此接着进入assignment函数中

 primaryexp(ls, &v.v);
if (v.v.k == VCALL) /* stat -> func */
SETARG_C(getcode(fs, &v.v), ); /* call statement uses no results */
else { /* stat -> assignment */
v.prev = NULL;
assignment(ls, &v, );
}

在assignment函数中首先判断下一个token是否为“,",此例中不是则说明是单变量的赋值,接着check下一个token为”=“,成立,接着调用explist1判断等号右边有几个值,此例为1个,然后会判断左边的变量数是否等于右边的值数,不等于则进入adjust_assign函数进行调整,此例是相等的因此依次进入luaK_setoneret和luaK_storevar函数。在luaK_storevar中首先进入int e = luaK_exp2anyreg(fs, ex);函数luaK_exp2anyreg的K代表了此函数是字节码相关的函数,ex为值”bar“,这个函数又调用了discharge2reg,根据ex的类型来生成不同的字节码:

 static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
luaK_dischargevars(fs, e);
switch (e->k) {
case VNIL: {
luaK_nil(fs, reg, );
break;
}
case VFALSE: case VTRUE: {
luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, );
break;
}
case VK: {
luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info);
break;
}
//... ...
}

由于”bar“是常量因此调用luaK_codeABx函数生成loadk字节码。reg为保存载入的常量值的寄存器号,e->u.s.info根据不同类型值代表不同含义,根据注释我们知道此时info为常量数组的下标。

typedef enum {
//... ...
VK, /* info = index of constant in `k' */
VKNUM, /* nval = numerical value */
VLOCAL, /* info = local register */
VGLOBAL, /* info = index of table; aux = index of global name in `k' */
//... ...
} expkind;

生成了loadk后返回到上面的函数中接着进入luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info);其中e为luaK_exp2anyreg的返回值表示常量保存在的寄存器标号,info根据注释当为global类型时表示global table的相应下标,因此luaK_codeABx函数将生成setglobal字节码,将刚刚用loadk将常量加载到寄存器中的值保存到global table相应的位置上。因此foo = "bar"语句就完整的生成了相应的字节码了。

接下来将生成local a,b = "a","b"语句的字节码了。过程大致相同,不同的是a,b是local变量且这个赋值语句是多变量赋值语句,因此前面的函数会用LHS_assign链表将a,b变量连接起来。如图所示:

lua解析赋值类型代码的过程的更多相关文章

  1. lua解析脚本过程中的关键数据结构介绍

    在这一篇文章中我先来介绍一下lua解析一个脚本文件时要用到的一些关键的数据结构,为将来的一系列代码分析打下一个良好的基础.在整个过程中,比较重要的几个源码文件分别是:llex.h,lparse.h.l ...

  2. <转>lua解析脚本过程中的关键数据结构介绍

    在这一篇文章中我先来介绍一下lua解析一个脚本文件时要用到的一些关键的数据结构,为将来的一系列代码分析打下一个良好的基础.在整个过程中,比较重要的几个源码文件分别是:llex.h,lparse.h.l ...

  3. JS-预解析(提升)与代码执行过程

    1,预解析的过程. 2,代码的执行过程. 预解析:程序在执行过程,会先将代码读取到内存中检查,会将所有的声明在此处进行标记,所谓的标记就是让js解析器知道这个名字,后面在使用这个名字的时候,不会出现未 ...

  4. C# 获取与解析枚举类型的 DescriptionAttribute

    原文:C# 获取与解析枚举类型的 DescriptionAttribute System.ComponentModel.DescriptionAttribute 这个 Attribute,经常被用来为 ...

  5. Lua中的类型与值

    [基础介绍] Lua是一种动态类型的语言.在语言中没有类型定义的语法,每个值都带有其自身的类型信息.在Lua中有8中基本类型,分别是: nil(空)类型 boolean(布尔)类型 number(数字 ...

  6. LUA整合进MFC代码

    这几天研究了一下lua,主要关注的是lua和vc之间的整合,把代码都写好放在VC宿主程序里,然后在lua里调用宿主程序的这些代码(或者叫接口.组件,随便你怎么叫),希望能用脚本来控制主程序的行为.这实 ...

  7. lua学习之类型与值篇

    类型与值 lua 是动态类型的语言 在语言中没有类型定义的语法 每个值都携带有它的类型信息 8种基础类型 用 type 可以返回这个值的类型的名称 将一个变量用于不同类型,通常会导致混乱的代码 但合理 ...

  8. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  9. 出售一套Unity + Lua热更新框架代码

    出售一套Unity + Lua的客户端框架代码,功能有资源管理.网络通信.配置文件解析.热更新.文件读写.Lua加密揭秘.UI框架.打包工具.编辑器工具等,已经在多个实际项目(已上线)中使用.代码优雅 ...

随机推荐

  1. 搞个这样的APP要多久?

    这是一个“如有雷同,纯属巧合”的故事,外加一些废话,大家请勿对号入座.开始了…… 我有些尴尬地拿着水杯,正对面坐着来访的王总,他是在别处打拼的人,这几年据说收获颇丰,见移动互联网如火如荼,自然也想着要 ...

  2. C++ std::set

    std::set template < class T, // set::key_type/value_type class Compare = less<T>, // set::k ...

  3. AngularJs之九(ending......)

    今天继续angularJs,但也是最后一篇关于它的了,基础部分差不多也就这些,后续有机会再写它的提升部分. 今天要写的也是一个基础的选择列表: 一:使用ng-options,数组进行循环. <d ...

  4. .NET Core的日志[3]:将日志写入Debug窗口

    定义在NuGet包"Microsoft.Extensions.Logging.Debug"中的DebugLogger会直接调用Debug的WriteLine方法来写入分发给它的日志 ...

  5. 将 instance 部署到 OVS Local Network - 每天5分钟玩转 OpenStack(130)

    上一节创建了 OVS 本地网络 first_local_net,今天我们会部署一个 instance 到该网络并分析网络结构.launch 一个 instance,选择 first_local_net ...

  6. [C#] C# 知识回顾 - 学会处理异常

    学会处理异常 你可以使用 try 块来对你觉得可能会出现异常的代码进行分区. 其中,与之关联的 catch 块可用于处理任何异常情况. 一个包含代码的 finally 块,无论 try 块中是否在运行 ...

  7. JS继承之寄生类继承

    原型式继承 其原理就是借助原型,可以基于已有的对象创建新对象.节省了创建自定义类型这一步(虽然觉得这样没什么意义). 模型 function object(o){ function W(){ } W. ...

  8. 搭建属于自己的VIP积分系统(1)

    很久没写博客了,如果有写得不好的地方,还请多多见谅. 架构设计 需求分析 这篇文章主要是介绍此VIP系统的基础架构.说实在的,我其实对 架构方面也不是很懂,我这套框架 还是拿别人的东西改过来的,并不是 ...

  9. IT持续集成之质量管理

    研发工具生态 质量相关工作 一次编译产出测试包与上线包 !从源头保证版本的⼀一致性!代码质量控制! 全⽅方位的⾃自动化测试体系保证! 提测冒烟效率! 全⾃自动上线流程杜绝⼈人⼯工犯错! 生产环境应⽤用 ...

  10. 易用BPM时代,软件开发者缘何选择H3?

    近年来,企业级软件开发市场暗流汹涌,呈现出多种态势.软件开发团队规模趋于小型化,工作方式趋于快捷化,超过半数的软件开发者在工作中会选择使用易用的软件开发工具.随着流程管理越来越受到企业的重视,流程开发 ...