四年前第一次听说luajit(Just-In-Time Compiler for Lua.),所谓个Just-In-Time也就是运行时编译器,说白了就是一个lua的高效版本,据说执行效率是lua的数十倍,并且全面兼容lua5.1版本。由于项目内核的lua版本刚好是5.14,所以当即决定把源码下载下来研究一下,无奈四年前项目进度太赶了,下载了之后连编译都编译不过(不通过luajit自带的msvbuild.bat脚本编译),更别提将它嵌入到项目中去了,再加上网上相关技术帖少之又少,也没有一些成功上线项目案例,所以就没继续往下研究了。这个月初,通过一个朋友了解到,网易的超级大作《天域》项目服务端逻辑层完全是由lua实现的,采用的就是luajit,这再一次激起了我的好奇心,我决定这一次一定要把luajit集成到项目中去(逻辑层全面lua话的好处实在太多了)。网上的所有帖子几乎全是在介绍自动化编译luajit脚本,但自动化编译无法帮助我们理解luajit的项目结构,本文主要就是介绍Luajit源码的在vs ide下手动编译流程。
luajit所有的源码都在src目录下, src目录包含一个msvbuild.bat自动编译脚本,如果只是想直接使用luajit,那运行一下这个脚本,就能自动生成luajit.exe的可执行文件、lua51.dll的动态链接库以及lua51.lib的静态链接库。然而我们的目标是要手动编译luajit,并且能够将源码嵌入到项目中去,这样才能针对luajit源码做定制化修改。
首先我们来分析下msvbuild.bat到底做了哪些工作。
编译的前半段主要是做了一些脚本本地环境变量的设置,其中红框部分的编译选项需要注意一下,优化级别O2,并且一定要添加 _CRT_SECURE_NO_DEPRECATE或者_CRT_SECURE_NO_WARNINGS宏,否则会编译报错。DASM是什么不在本文进行讨论,感兴趣的可以看一下官方介绍以及相关技术帖子,这里直贴两个链接:
http://www.cppblog.com/pwq1989/archive/2013/11/30/204508.aspx
我们暂时可以将dynasm理解成生成中间文件的工具。 其中32位环境需要将DASC变量设置vm_x86.dasc,64位环境需要DASC变量设置dysm_x64.lua。LJDLLNAME和LJLIBNAME分别是静态库和动态库名。ALL_LIB是生成临时文件时所需要的源文件。
这一步很简单,就是将host下minilua.c文件编译并链接成可执行文件minlua.exe。于是我们首先要建立一个工程,这个工程只包含minilua.c这个文件,编译连接生成minilua.exe.
minilua是一个工具,作用是为了生成另外一些中间文件。而这个中间文件是啥呢,
没错,就是buildvm_arch.h这个头文件了。当然了,这个头文件也不是luajit相关的头文件,而是为了生成buildvm这个中间工具所需要的头文件。
于是我们需要再建立一个buildvm工程,将src下所有buildvm开头的源文件都纳入到该工程中。
编译buildvm前需要通过minilua生成buildvm_arch.h,因此需要在buildvm工程设置生成前事件:
$(OutDir)\MiniLua.exe $(SolutionDir)dynasm/dynasm.lua -LN -D WIN -D JIT -D FFI -o $(SolutionDir)Buildvm/src/buildvm_arch.h archdasc/vm_x86.dasc
编译后链接生成buildvm.exe。
这还没完,我们发现脚本中利用buildvm生成了很多头文件,因此我们还需要对buildvm工程设置链接后事件:
以下是脚本的翻译版本,将生成的头文件放入到src目录下。
@setlocal
cd /d ..\src
@set ALL_LIB=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
$(OutDir)\buildvm.exe -m peobj -o $(OutDir)\lj_vm.obj
$(OutDir)\buildvm.exe -m bcdef -o $(SolutionDir)src\lj_bcdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m ffdef -o $(SolutionDir)src\lj_ffdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m libdef -o $(SolutionDir)src\lj_libdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m recdef -o $(SolutionDir)src\lj_recdef.h %ALL_LIB%
$(OutDir)\buildvm.exe -m vmdef -o $(SolutionDir)src\jit\vmdef.lua %ALL_LIB%
$(OutDir)\buildvm.exe -m folddef -o $(SolutionDir)src\lj_folddef.h lj_opt_fold.c
@endlocal
接下来就要上主菜了,生成luajit.exe以及luajit的动态链接库与静态链接库。我们首先建立两个工程,一个工程用来生成luajit.exe,一个工程用来生成luajit.dll与luajit.lib,将src所有以l开头的文件(包括刚才自动生成的文件)纳入到工程去(ljamalg.c除外,另外生成库文件的工程不需要包含luajit.c这个main函数入口文件)。
尝试直接编译发现,编译可以通过,但是链接出了问题,仔细一看才发现原来buildvm刚才还生成了一个lj_vm.obj,一些接口的实现时放在这个obj文件里的,因此我们需要设置一下链接输入文件,将lj_vm.obj设置进去。
最后进行编译链接,就会得到我们想要的可执行文件以及库文件了。
下一步的目标是在linux环境下通过CMake编译,难度要比Windows端大很多,不过程序员的目标不就是要不断地走出舒适圈吗。
最后给一个简单的结论吧,我将luajit成功地嵌入到了项目中去,在项目写了一个lua测试函数对两个版本(lua版本与luajit版本)进行lua性能测试,结论果然是,Luajit的执行效率真的要快过lua30倍,当然了,这个测试函数很简单,luajit在性能上也有很多踩坑点,这需要一点点地去探索,这个探索的过程往往也是需要交学费的。