LuaJIT,是 Lua 语言的高性能即时编译器,结合了 Lua5.1 的语法和特性,大幅提升了执行效率。LuaJIT 的 JIT 编译器能将 Lua 代码动态编译为机器码,实现接近原生 C/C++ 的运行速度。在项目开发过程中,会有一些需求要对底层的方法做一些修改,实现进一步的性能优化和业务安全。本篇文章记录了修改 LuaJIT 底层方法过程中踩的坑。
1. 问题概述
根据需要,要对 LuaJIT 底层的一些方法做出修改,使设为只读的 table 不可以被修改,因此要对 table.remove
table.insert
等方法做一些改动。但是修改以后发现,使用 table.remove
仍然可以修改 table 中的数据,但是代码本身没有问题,只是检查没有被触发。因此开始排查该问题。
2. 源码分析
考虑到 insert
的修改成功了,但是 remove
的修改没有成功,因此我对比了这两个方法的实现,发现两个方法使用的宏定义不同。table.insert
使用的是 LJLIB_CF
,即 C function ,而table.remove
使用的是LJLIB_LUA
,并且后面的具体实现是被注释掉的。于是去查看了这两个宏究竟被定义成了什么。
1 | LJLIB_CF(table_insert) LJLIB_REC(.) |
可以看到,如下所示,LJLIB_CF
被定义为了一个静态函数,但是 LJLIB_LUA
则是一个空字符,也就是说在编译过程中,这段代码会被替换成空字符串,结合后面代码主体内容是写在注释中的,说明实际编译以后,这段代码根本不生效。
1 | /* Library function declarations. Scanned by buildvm. */ |
但是这显然不符合实际的使用情况,因为在实际使用过程中,可以正常使用 table.remove
这个方法,所以一定是有其他的机制在。进一步探索,对 LJLIB_LUA
进行了全局搜索,发现在 genlibbc.lua 这个文件代码中出现了,并且不是以宏定义的形式,而是出现在正则表达式中,如下所示。
1 | local function find_defs(src) |
简单分析这段代码,不难看出,是在提取文本中以 LJLIB_LUA
开头,后面跟着的 /% … %/ 的中间的内容,这正是我们前面所说的被注释掉的主体代码。可见 LuaJIT 对这个宏定义下的方法采用了另一种形式进行编译。既然找到了相关的代码,那么索性就查看这个文件中整体的代码逻辑。这个文件的最后,可以清晰地看出这个文件就是为了生成一个 header,并且可以通过直接调用的方式生成。
1 | local outfile = parse_arg(arg) |
全局搜索 genlibbc,发现在 Makefile 中有这样几行。结合前文中定义的 LUAJIT_T= luajit
,可以得知,这个 header 文件可以直接通过 luajit host/genlibbc.lua ...
来生成。而在 mvcbuild.bat 文件中则没有相关的内容。这也就说明,如果使用的是适配 VS 的编译脚本,那么即使重新编译了,对 LJLIB_LUA
定义的方法修改也不会生效,因为根本不会重新生成对应的 header 文件。而我使用的恰好是 VS,至此,问题的症结已经很明确了。
1 | libbc: |
3. 解决过程
既然已经发现了问题的症结,那么我尝试直接调用如下指令,尝试重新生成一个 header 文件,在 Windows 和 Linux 上都可以正确运行。但是又出现了新的问题。
1 | luajit.exe host/genlibbc.lua -o host/buildvm_libbc.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c |
结合 genlibbc.lua 文件中的代码可知,这段编译会根据 LuaJIT 32位 或者 64位生成不同的机器码,存储在 buildvm_libbc.h 文件中。然而,buildvm_libbc.h 文件,在最初下载下来,没有任何改动的时候,通过 #if LJ_FR2 #else
,两种类型的机器码都存储了。但是如果直接调用上述的指令,就只会生成一种机器码,对应当前使用的 LuaJIT 版本。为了确保重新生成的 header 文件依旧适配两种版本,就要分别用 32位 和 64位的 LuaJIT 来生成 header,并且分别保存两个版本的机器码,最后手动修改 header 文件,延续之前的双版本支持。
而要如何生成不同版本的 LuaJIT 呢?因为我使用的是 Visual Studio,直接安装了 X86 和 X64 的 Visual Studio Command,分别使用这两个Command 执行上述的命令,手动生成即可。
4. 后记
一开始出现这个问题的时候,我先上网搜索了很久,因为搞不懂为什么编译以后会有代码不生效,但是发现全网都找不到能解决我的问题的文章参考,基本都是一些教你如何编译生成 LuaJIT 的,但也都很浅显,停留于表面。最后还是自己一点点看源码,找到了埋伏在其中的线索。用 Lua 的还是太少啦,何况是用 LuaJIT 的,只能自己建设啦。
- 本文作者: Berry
- 本文链接: https://wuxinberry.github.io/2025/03/22/luajit/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!