V8引擎编译

本文介绍在一个较老的环境下编译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标准,主要步骤如下:

  1. 从源码构建GCC和GLIBC
  2. 从源码构建V8静态库v8_monolith.a
  3. 将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
2
fetch v8
glient sync

如果出现gclient拉取问题,可以尝试修改.gclient文件如下所示

1
2
3
4
5
6
7
8
9
10
solutions = [
{
"managed": False,
"name": "src",
"url": "https://chromium.googlesource.com/v8/v8.git",
"custom_deps": {},
"deps_file": ".DEPS.git",
"safesync_url": "",
},
]

此外,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
2
is_clang = false
use_sysroot = 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,具体涉及下面的修改

  1. toolchain\gcc_toolchain.gni
    这是一个模板文件,我们需要修改GCC的cccxxalinklink_command这几个项目。具体说来,包括:
    1. 设置cccxxld到GCC9的bin目录下的对应程序;
      这里,ld就指定为g++即可
    2. 修改extra_cppflags添加GCC对应的include文件夹的路径;
    3. 对于cccxx两个tool,需要在最后加上-static
    4. 对于alinklink_command,需要加上libstdc++.a
  2. config\c++\BUILD.gn
    参照toolchain\gcc_toolchain.gni修改cflags_ccldflags

去掉snapshot

snapshot技术是V8为了提高Context的加载速度引入的优化,将V8启动后的内存布局和JS函数预编译好的二进制对象写到专门的文件shapshot_blob.binnatives_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的支持。主要涉及下面的修改

  1. build/standalone.gypi
    设置$(snapshot)off
    设置clang%为1。
  2. 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