热门标签 | 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

推荐阅读
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 深入解析C语言中结构体的内存对齐机制及其优化方法
    为了提高CPU访问效率,C语言中的结构体成员在内存中遵循特定的对齐规则。本文详细解析了这些对齐机制,并探讨了如何通过合理的布局和编译器选项来优化结构体的内存使用,从而提升程序性能。 ... [详细]
  • 在C#编程中,数值结果的格式化展示是提高代码可读性和用户体验的重要手段。本文探讨了多种格式化方法和技巧,如使用格式说明符、自定义格式字符串等,以实现对数值结果的精确控制。通过实例演示,展示了如何灵活运用这些技术来满足不同的展示需求。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 字节流(InputStream和OutputStream),字节流读写文件,字节流的缓冲区,字节缓冲流
    字节流抽象类InputStream和OutputStream是字节流的顶级父类所有的字节输入流都继承自InputStream,所有的输出流都继承子OutputStreamInput ... [详细]
  • 基于Linux开源VOIP系统LinPhone[四]
    ****************************************************************************************** ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 本文详细解析了客户端与服务器之间的交互过程,重点介绍了Socket通信机制。IP地址由32位的4个8位二进制数组成,分为网络地址和主机地址两部分。通过使用 `ipconfig /all` 命令,用户可以查看详细的IP配置信息。此外,文章还介绍了如何使用 `ping` 命令测试网络连通性,例如 `ping 127.0.0.1` 可以检测本机网络是否正常。这些技术细节对于理解网络通信的基本原理具有重要意义。 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 本文深入探讨了MDK链接脚本的应用与优化技巧。首先,文章介绍了链接脚本的基本概念及其在嵌入式系统开发中的重要性。接着,通过具体实例详细分析了链接脚本的结构和功能,特别是在程序在FLASH中运行时,如何优化链接脚本以提高系统性能。此外,文章还讨论了无需将程序加载到SRAM中的技术细节,为开发者提供了实用的参考和指导。 ... [详细]
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社区 版权所有