热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

根据源码梳理LuaJIT解释器模式(一)

LuaJIT有两种执行模式,解释模式和即时编译(JIT)模式。这里暂且抛开JIT模式,主要介绍解释器模式的技术实现。解释器模

LuaJIT有两种执行模式,解释模式和即时编译(JIT)模式。这里暂且抛开JIT模式,主要介绍解释器模式的技术实现。解释器模式的主要模块关系如下图所示:
在这里插入图片描述
解释器模式基本上可以分为两个阶段:第一个阶段词法分析器提取分析Lua source字符流构成token(这一过程在src/lj_lex.c中),语法分析器再使用token流进行语法分析、语义分析,最后生成可供LuaJIT虚拟机识别的Bytecode中间码(src/lj_parse.c)。第二个阶段解释器对Bytecode一条条的按字节码逻辑进行解析执行(src/vm_loongarch64.dasc)。根据对源码的梳理,做出以下时序图:
在这里插入图片描述

LuaJIT开始于main函数,在main中调用lua_open()创建虚拟机(VM),然后在lua_cpcall中调用lj_vm_cpcall(L, func, ud, cpcall)函数进入VM,该函数对应这汇编代码中的vm_cpcall。参数func是函数pmain的指针,加载source文件至VM解释执行就是其发起的,参数cpcall是函数cacall的指针,cpcall函数主要为执行pmain做了一个准备工作。

pmain中,首先检查执行命令中的参数,根据参数做相应动作,如./luajit -v,会输出luajit的版本信息。luaL_openlibs(L)会打开所有的Lua标准库,调用handle_script发起加载source文件解释执行。

lua_loadx中,再次通过lj_vm_cpcall执行函数cpparser,cpparser则是真正的开始为source code -> bytecode这一过程做准备。这里有三个类型需注意:


  • LexState是词法解析的上下文,通过该类型的对象可以获取所有词法相关的信息,包括Current token及其位置、token value、Current character等;
  • GCproto是函数的原型,存放函数paser完生成的bytecode和中间结构的信息,包括常量数组、bytecode指令等;
  • GCfuncunion,封装了Lua函数和C函数,也就是一个该类型的对象只能表示为一个Lua函数或一个C函数。

区分GCproto(原型)和GCfunc(函数)的区别,GCproto中存放的是函数生成的bytecode指令集及其相关的中间结构信息,会以此去创建闭包,将创建的闭包信息存放到GCfunc中,从而让VM执行Lua或C函数。bc ? lj_bcread(ls) : lj_parse(ls)根据bc的值选择是parse Lua source还是直接执行bytecode,bc的值根据脚本文件中的前几个字符确定。

lj_parse进入parse阶段,在parse时会创建FuncState对象。FuncState是当前正在parse的函数上下文信息,如*prev指向父函数或者外部函数,nactvar表示当前局部变量在stack上的位置,freereg表示第一个可用的stack slot,vbase是当前函数stack的base等。首先bcemit_AD(&fs, BC_FUNCV, 0, 0)生成一条BC_FUNCV指令,这条指令并不是最终的指令,如果是JIT模式,后面会被BC_IFUNCV执行替换。从这里已经可以看出来,从source->bytecode这一过程是直译的,没有生成AST或其他中间码。

parse_chunk被用于block级别的parse,这个block可以是函数或者复合语句(if、for等)的block,如:

function fname() block
endif cond then block
end

parse_chunk中循环调用parse_stmt做语句级别的parse,直到遇到return语句或者标志某条复合语句block结束的token后(如else、elseif和end标志着if then后面的block结束)结束。

parse完成后,会执行fs_finish函数,将parse阶段生成的Bytecode和中间结构,以及收集到的函数context信息重新组织为一个新的数组结构,放到紧跟在GCproto之后的位置,并将这些数组结构的基地址和size存放到GCprotolua_State对象中,供VM解释器做解释计算时使用。下图为重新组织的数组结构图,其中ptGCproto,fsFuncState:
在这里插入图片描述
至此parse过程已经完成,如果source没有错误,会返回GCproto*。执行函数lj_func_newL_empty(L, pt, tabref(L->env))创建一个GCfunc,并将其存放到Stack的第一个free slot上(L->top所指位置),供VM解释器执行使用。待cpparser函数执行完,PC(程序计数器,别跟VM的PC混淆)返回到VM的函数vm_cpcallor BASE, CRET1, r0指令,此时寄存器CRET1的值为0,vm_cpcall会返回到进入它的C函数lua_loadx,再lua_loadx由一直返回到handle_script函数中。返回值status为0(LUA_OK = 0),所以会按照执行路径docall(L, narg, 0)lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base)lj_vm_pcall(L, api_call_base(L, nargs), nresults+1, ef)进入VM开始对生成的Bytecode解释执行。

首先执行vm_pcall,其中参数 TValue *base 指针指向VM Stack的base地址,L->top指向VM Stack的第一个free slot,所以即将要执行的函数的参数个数可通过L->top - base获得。该函数的主要作用是将进入VM之前的C函数的保存寄存器(callee寄存器)的值保存在运行时栈上,等VM执行结束返回时再取出恢复C的运行环境。

接着执行vm_call,这部分主要准备VM函数的运行环境,如为寄存器BASEPCDISPATCH赋相应的值,当前要运行的Lua函数的闭包存放在FRAME_FUNC(BASE)中,这里将其存放在了寄存器RB中,在ins_call中会使用,跳到要执行的函数Bytecode相关指令对应的函数中执行。具体如何执行,会在根据源码梳理LuaJIT解释器模式(二) 中详细介绍。

待Bytecodeui对应的函数执行完之后,会Return handling相关的函数回到C函数中,最终回到main中。返回到C函数中后,VM Stack的结构如下:

----------------------------
| ret1 | ... | retn | |
----------------------------base n-1 L->top

推荐阅读
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
author-avatar
海蓝透了我的心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有