本文介绍在一个较老的环境下编译V8引擎,并全静态地链接到既有的游戏服务上。原因是我们必需一个很新的JS引擎,从而能支持WebAssembly。经过调研,我们认为使用V8是最好的。
我们的游戏服务使用GCC4.4.6,这是一个非常老的版本,甚至不能完整支持C++11标准,而即使是很老版本的V8都需要完整的C++11支持(GCC 4.8+);进一步地最新版本的V8需要C++14标准的支持,这不仅体现在v8.h中出现了诸如std::remove_cv_t
的C++14的标准库函数,还体现在C++14标准编译出来的库也没办法直接和原游戏的目标文件进行链接。因此我们的方案是将游戏中对V8强依赖的模块升级成C++14标准,主要步骤如下:
- 从源码构建GCC和GLIBC
- 从源码构建V8静态库v8_monolith.a
- 将V8静态库、libc++、GLIBC和游戏模块全静态链接
编译GCC和GLIBC
见文章
代码拉取
代码拉取值得单独开一节,因为虽然V8代码使用Git来管理的,但它却并不是传统的git clone的路数,而是需要借助gclient这个工具。
gclient,包括gn等工具都在depot_tools
这个包里面,我们可以通过下面的链接得到这个包
1 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git |
在得到这个包后,我们还需要将它加到环境路径里面以方便访问
1 | export PATH=`pwd`/depot_tools:"$PATH" |
然后,使用下面的命令拉取
1 | fetch v8 |
如果出现gclient拉取问题,可以尝试修改.gclient文件如下所示
1 | solutions = [ |
此外,url还可以尝试取github上的地址
1 | https://github.com/chromium/chromium.git |
编译V8
目前V8默认是使用Clang编译的,确实比GCC快很多,但由于我们最后还要和游戏进行链接,所以需要把工具链换成GCC。在这篇wiki中给出了从简单到复杂的不同的编译的流程。我试验下来直接用gn
是最为方便的。
修改编译器
V8默认的编译工具链是clang,我们要将它变成GCC。首先我们需要设置use_custom_libcxx
,这个项目如果为true,就会走v8自带的编译工具,这样编译出来如果是d8还好,但我们需要静态库,这样肯定不行。
1 | use_custom_libcxx = false |
然后我们需要修改gn args
,添加下面的项目,目的是禁用clang相关的宏。
1 | is_clang = false |
为了防止ld.gold
报错,还需要开启下面的选项。
1 | fatal_linker_warnings = false |
但这么做还不够,需要在入口build/config/BUILDCONFIG.gn
中,修改Linux下,is_clang
为true的情况下,toolchain
为GCC。否则,还是有一些代码会把is_clang
的值变回true。
1 | _default_toolchain = "//build/toolchain/linux:$target_cpu" |
紧接着,我们就要修改原来的gcc toolchain,将里面的配置替换成我们刚编译得到的GCC9,具体涉及下面的修改
toolchain\gcc_toolchain.gni
这是一个模板文件,我们需要修改GCC的cc
、cxx
、alink
和link_command
这几个项目。具体说来,包括:- 设置
cc
、cxx
和ld
到GCC9的bin目录下的对应程序;
这里,ld就指定为g++即可 - 修改
extra_cppflags
添加GCC对应的include文件夹的路径; - 对于
cc
和cxx
两个tool,需要在最后加上-static
; - 对于
alink
和link_command
,需要加上libstdc++.a
。
- 设置
config\c++\BUILD.gn
参照toolchain\gcc_toolchain.gni
修改cflags_cc
和ldflags
。
去掉snapshot
snapshot技术是V8为了提高Context的加载速度引入的优化,将V8启动后的内存布局和JS函数预编译好的二进制对象写到专门的文件shapshot_blob.bin
和natives_blob.bin
中,并在每一次初始化的时候直接加载,以减少重复编译消耗。
修改gn args
,添加
1 | v8_use_external_startup_data = false |
生成v8静态库
首先生成ninja构建文件
1 | gn gen out.gn/x64.debug/ |
修改gn args
,添加
1 | v8_monolithic = true |
然后使用下面的命令生成静态库,注意后面的-j
不能加得太大,因为在编译比如torque的时候GCC会占用比较大的内存,如果并行度很高,可能就编不出来
1 | ninja -C out.gn/x64.debug/ v8_monolith -j2 |
静态链接
通过下面的命令可以静态链接V8的Hello world程序。
1 | $(GCC_ROOT)/bin/g++ samples/bench.cpp -o bench -isystem$(GLIBC_ROOT)/include/ -I. -Iinclude -isystem$(GLIBC) -nodefaultlibs -DV8_COMPRESS_POINTERS -static $(V8_OBJ)/libv8_monolith.a -Wl,--start-group $(GCC_ROOT)/lib64/libstdc++.a $(GLIBC_ROOT)/lib/libpthread.a $(GLIBC_ROOT)/lib/libdl.a $(GLIBC_ROOT)/lib/libm.a $(GLIBC_ROOT)/lib/librt.a $(GCC_GCC)/libgcc.a $(GCC_GCC)/libgcc_eh.a $(GCC_GCC)/libcommon.a $(GLIBC_ROOT)/lib/libc.a -Wl,--end-group |
附注
2558版本
2558版本是一个较老的版本,它是使用Make和GYP来构建的。由于它同样需要移动语义,所以也要C++11的支持。主要涉及下面的修改
build/standalone.gypi
设置$(snapshot)
为off
。
设置clang%
为1。- Hello world链接过程
1
g++ -pthread -fuse-ld=gold -fuse-ld=gold -m64 -m64 -rdynamic -rdynamic -Wl,--threads -Wl,--thread-count=4 -Wl,--threads -Wl,--thread-count=4 -o hello -Wl,--start-group hello-world.o out_libs/libv8_libplatform.a out_libs/libicui18n.a out_libs/libicuuc.a out_libs/libv8_base.a out_libs/libv8_libbase.a out_libs/libicudata.a out_libs/libv8_nosnapshot.a -Wl,--end-group -ldl -lrt