为什么要看V8源码?

过年时候看了本书——《JavaScript设计模式与开发实践》。设计模式总结的挺好的,但是示例代码比较粗糙。尤其是在说某些模式的性能问题时,感觉并没有依据,所以对于作者的说法有些存疑。手上有本《高性能JavaScript》,不得不说这本书确实严谨,所讲的问题都有列出对应的依据(一般是实际测试结果)。但是年代有些久远,不知道在现代JavaScript引擎下还有哪些适用。

所以,我希望通过对V8源码的研究,搞清楚JavaScript中的某些特性究竟是如何实现的。(然后在吹牛逼的时候才比较有底气,嗯)

V8是C++写的,然而我的C++基本已经还给老师了…所以不可能去彻底的读通V8,我的策略是带着问题通过debug V8,去代码里面寻找答案。

这一篇的内容关于编译V8。Let the hunt begin!

下载、编译V8代码

V8在github上有镜像仓库,可以很简单找到。我需要的是这:wiki

首先,clone代码,这里不能简单地git clone,不然据说会不能编译…

Google用了一些自己的工具来管理dependency,所以还是老老实实地装depot_tools吧,很简单不说了。

装好之后:

1
2
gclient
fetch v8

短短的两行命令,我执行了2个小时吧…这基本是编译V8的第一道坎了

漫长地等待之后,V8的source code终于被我下载到本地了!开始编译:

V8提供了两种方法生成编译文件,gn和gyp,但是gyp已经被标记成deprecated了,所以用gn。

很简单,到V8目录下面运行一个python脚本就好了:tools/dev/v8gen.py x64.release,后面跟的x64.release是build target,也可以通过tools/dev/v8gen.py list查看有哪些target。

然后就是编译了:

1
ninja -C out.gn/x64.release

指定了编译后文件生成的目录在out.gn/x64.release下面。这个时候就可以去倒杯咖啡(也可能是两杯)等着了。有1400+文件要编译加上编译过程CPU占用巨高,老老实实等着吧。

终于编译完了!这时候我们得到了一堆可执行文件,那到底从哪入手呢?进入out.gn/x64.release文件夹下面,看到有一堆可执行文件,有一个v8_hello_world文件很符合我们hello world的要求,

World!```。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

其实wiki里面有写,这个hello world程序其实是samples/hello-world.cc文件编译出来的结果,打开这个文件看看里面

```c++
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);

// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(isolate, "'Hello' + ', World!'",
NewStringType::kNormal).ToLocalChecked();

// Compile the source code.
Local<Script> script = Script::Compile(context, source).ToLocalChecked();

// Run the script to get the result.
Local<Value> result = script->Run(context).ToLocalChecked();

// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(result);
printf("%s\n", *utf8);

看起来这段应该是写了一句javascript:

1
'Hello' + ', World'

(字符串连接),然后在v8里面执行了这句代码,最后用printf把结果输出了。

(在编译完的文件中有一个叫d8的文件,实际上这就是一个v8 debug shell,类似于node repl的存在。可以./d8启动debug8)

到这里V8算是编译完了,但是怎么debug啊…V8项目里面没有包含工程项目,XCode貌似用不了,这可咋整。

怎么Debug V8

最后想,实在不行的话我就往代码里加printf,然后重新编译运行吧。

试验下这个办法行不行得通:

首先ninja可以列出当前编译的targets:

1
cd out.gn/x64.releases && ninja -t targets all

然后在列出的targets里面能看到v8_hello_world这个target,很明显这就是刚才运行的hello_world.cc对应的target。改一下hello_world.cc文件:

1
2
3
auto func = '(function(a, b){ return a + b })(20, 30)'; 
String::NewFromUtf8(isolate, func,
NewStringType::kNormal).ToLocalChecked();

把script换成一个IIFE,替换掉原本的字符串连接。然后删除原来的v8_hello_world文件,重新编译这个target:

1
2
rm out.gn/x64.release/v8_hello_world 
ninja -C out.gn/x64.release :v8_hello_world

编译后运行新的文件./v8_hello_world,发现输出了50!

发现这个办法可行后,我又试着在parser.ccInitialize的方法里面加了一些printf,然后重新编译obj/v8_base/parser这个target,然后执行v8_hello_world,这些printf都能正常输出。

这次尝试就到这里,如果真的不会debug,起码也能有办法看下去。

当然,找到真正debug的方法才是坠吼的!