V8這個概念大家都不陌生了辩诞,那么你動手編譯過V8源碼嗎唇兑?編譯后有嘗試去了解V8背后的一些概念嗎酒朵?如果沒有,那么也不用心慌扎附,下文將跟大家一一解釋這些東西蔫耽。在編譯V8之前我們先要了解一個東西-構(gòu)建系統(tǒng)
1、構(gòu)建系統(tǒng)
1.1留夜、構(gòu)建系統(tǒng)是啥匙铡?
寫慣前端的童鞋可能不是很明白這個東西是干啥用的?但是其實平時你都會接觸到碍粥,只是概念不同而已鳖眼。前端我們一般稱其為打包構(gòu)建,類似工具諸如webpack嚼摩、parcel做的事情钦讳。其實最后的目標都是想得到一些目標性的文件。這里可以簡單地提及一下軟件工程中的構(gòu)建系統(tǒng)的歷史低斋。
構(gòu)建系統(tǒng)的需求是隨著軟件規(guī)模的增大而提出的。如果只是做簡單的demo匪凡,通常代碼量比較小膊畴,編寫的源代碼只有幾個文件。比如你編寫了一段代碼放入helloworld.cpp文件中病游,要編譯這段代碼唇跨,只需要執(zhí)行以下命令:
g++ helloworld.c -o helloworld
當軟件規(guī)模逐漸增加稠通,這時可能有幾十個源代碼文件,而且有了模塊劃分买猖,有的要編譯成靜態(tài)庫改橘,有的要編譯成動態(tài)庫,最后鏈接成可執(zhí)行代碼玉控,這時命令行方式就捉襟見肘飞主,需要一個構(gòu)建系統(tǒng)。常見的構(gòu)建系統(tǒng)有GNU Make高诺。需要注意的是碌识,構(gòu)建系統(tǒng)并不是取代gcc這樣的工具鏈,而是定義編譯規(guī)則虱而,最終還是會調(diào)用工具鏈編譯代碼筏餐。
當軟件規(guī)模進一步擴大,特別是有多平臺支持需求的時候牡拇,編寫GNU Makefile將是一件繁瑣和乏味的事情魁瞪,而且極容易出錯。這時就出現(xiàn)了生成Makefile的工具惠呼,比如Cmake
导俘、AutoMake
等等,這種構(gòu)建系統(tǒng)稱作元構(gòu)建系統(tǒng)(meta build system)罢杉。在Linux上軟件倉庫的概念還沒有普及的時候趟畏,通常我們安裝軟件的步驟是:
./configure
make
make install
第一步就是調(diào)用一些自動化工具,根據(jù)系統(tǒng)環(huán)境(系統(tǒng)的版本眾多滩租,軟件安裝情況也不一樣)赋秀,生成GNU Makefile。然后第二步才使用gcc或者g++命令去編譯所有文件律想,最后一步便是將所有文件鏈接起來成可執(zhí)行命令并安裝到系統(tǒng)的某個指定目錄猎莲。
一般后兩個步驟都是比較固化的,能提高工作效率的也就是在第一步了技即。于是V8團隊針對自己的項目特點著洼,擼了一個叫做GYP(Generate Your Projects)的構(gòu)建系統(tǒng),后面你要是看到node-gyp
其實就是基于這個做的js版本而叼。不過后面GYP被v8團隊廢棄掉身笤,改用GN(Generate Ninja)構(gòu)建系統(tǒng)。二者的區(qū)別不是本文重點葵陵,有興趣的童鞋可以查看這篇文章: chromium中的GN構(gòu)建系統(tǒng)液荸。
有意思的是盡管v8徹底廢棄掉了GYP,但是nodejs仍然在使用GYP脱篙,這個R大在創(chuàng)建deno項目的時候有提及到:Design Mistakes in Node娇钱。
1.1.1伤柄、GN構(gòu)建系統(tǒng)簡介
GN(Generate Ninja)是chromium project用來取代GYP的新工具,由于GN是用C++編寫文搂,比起用 python寫的GYP快了很多适刀,GN新的DSL的語法也被認為是比較好寫以及維護的。
在v8項目的根目錄下有個.gn
文件煤蹭,內(nèi)容如下(去掉所有注釋了):
import("http://build/dotfile_settings.gni")
buildconfig = "http://build/config/BUILDCONFIG.gn"
check_targets = []
exec_script_whitelist = build_dotfile_settings.exec_script_whitelist + []
我們關(guān)注buildconfig
這個配置笔喉。.gn
所在的目錄會被GN工具認定是項目的根目錄,.gn
的內(nèi)容基本就是用buildconfig
來指定build config的位置疯兼,其中//build//config/BUILDCONFIG.gn
是相對于項目根目錄下路徑的配置文件然遏。
但是你會發(fā)現(xiàn)現(xiàn)在v8源碼目錄下并沒有叫做build的目錄,這個目錄要咋生成呢吧彪?這些知識我們會在稍后的編譯v8代碼中提及待侵。
假設(shè)現(xiàn)在你有build目錄了,我們找到BUILDCONFIG.gn
文件姨裸,文件里面會根據(jù)系統(tǒng)和平臺設(shè)置對應的編譯工具鏈:
... ...
if (custom_toolchain != "") {
set_default_toolchain(custom_toolchain)
} else if (_default_toolchain != "") {
set_default_toolchain(_default_toolchain)
}
... ...
比如得到的_default_toolchain
值為:_default_toolchain = "http://build/toolchain/linux:clang_x86
秧倾,那么你在build/toolchain/linux
目錄下的BUILD.gn
可以找到這么一個配置:
clang_toolchain("clang_x86") {
# Output linker map files for binary size analysis.
enable_linker_map = true
toolchain_args = {
current_cpu = "x86"
current_os = "linux"
}
}
因為GN沒有內(nèi)建的toolchain
規(guī)則,toolchain
里的各種tool
例如 cc,cxx,link等必須自己指定傀缩,指定的文件是build/toolchain/gcc_toolchain.gni
文件那先,在文件中我們可以看到GN給定義的一些動作:
tool("cc") {
depfile = "{{output}}.d"
precompiled_header_type = "gcc"
command = "$cc -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}${extra_cppflags}${extra_cflags} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC {{output}}"
outputs = [
"$object_subdir/{{source_name_part}}.o",
]
}
最后項目根目錄下會有一個BUILD.gn
的文件,指定生成可執(zhí)行文件的指令赡艰,比如:
v8_executable("v8_hello_world") {
sources = [
"samples/hello-world.cc",
]
configs = [
# Note: don't use :internal_config here because this target will get
# the :external_config applied to it by virtue of depending on :v8, and
# you can't have both applied to the same target.
":internal_config_base",
]
deps = [
":v8",
":v8_libbase",
":v8_libplatform",
"http://build/win:default_exe_manifest",
]
}
這樣一套完整的GN構(gòu)建系統(tǒng)便完成了售淡。
1.1.2、Ninja構(gòu)建系統(tǒng)
有了GN慷垮,為啥還要Ninja呢揖闸?剛才我們知道GN的英文意思是Generator Ninja,可見GN生成的東西并不是我們最終GNU Makefile形式料身。而Ninja才是最后生成Makefile的終極法器汤纸。Ninja 作為一個新型的編譯工具,小巧而又高效芹血,據(jù)谷歌官方的說法是速度有了好幾倍的提升贮泞。
這個時候我們還沒有生成任何的Ninja文件,需要我們使用GN命令去生成:
gn args out/foo
這下子你在out/foo
下就可以看到好多ninja文件:
Ninja使用build.ninja
文件來定義構(gòu)建規(guī)則幔烛,和Makefile
里的元編程不同啃擦,build.ninja
幾乎是完全靜態(tài)的,動態(tài)生成依賴其他工具饿悬,如gn或者CMake令蛉。
build.ninja
build.niinja相當于ninja的makefile,一個簡單的build.ninja文件如下乡恕,分為rule和dependency兩部分言询。
phony: 可以創(chuàng)建其他target的別名。
default: 如果沒有在命令行中指定target傲宜,可以使用default來指定默認的target运杭。
pools: 為了支持并發(fā)作業(yè),Ninja還支持pool的機制函卒,和用-j并行模式一樣辆憔。
Make vs Ninja Performance Comparison將Ninja和Make進行了測試對比。
2报嵌、編譯并測試V8代碼
接下來我們開始進行v8代碼的編譯操作虱咧。官網(wǎng)的文檔給的已經(jīng)很齊全了,這里只是再簡單說一下锚国,并提及一些官網(wǎng)沒有給出的基本知識腕巡。
2.1、下載v8代碼
這一步注意了血筑,不要直接從v8倉庫使用git clone命令下載代碼绘沉,這樣下載下來的代碼是無效的,會缺失很多東西豺总,要使用官方提供的工具depot_tools
整個步驟匯總?cè)缦拢?/p>
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=$PATH:/path/to/depot_tools
gclient config https://chromium.googlesource.com/v8/v8
gclient sync
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
2.2车伞、編譯v8代碼
編譯v8代碼官網(wǎng)同樣給的很詳細:傳送門,這里總結(jié)一下而已喻喳,有兩種編譯方式
2.2.1另玖、超便捷方式
使用gm
這個集成所有為一體的python腳本可以幾個命令就搞定:
alias gm=/path/to/v8/tools/dev/gm.py
gm x64.release
gm x64.release.check
2.2.2、手動編譯方式
按照我們之前說的流程表伦,我們需要使用GN去生成ninja文件谦去,再生成makefile,最后才是編譯绑榴,因此:
可以使用gn args out/foo
或者gn gen out/foo --args='is_debug=false target_cpu="x64" v8_target_cpu="arm64" use_goma=true'
來生成ninja文件哪轿。
這一行命令官網(wǎng)沒有詳細解釋,我在這里解釋一下:
gn args out/foo => 通過參數(shù)形式指定輸出目錄翔怎,這個命令會彈出文本讓你配置參數(shù)
gn gen out/foo => 指定GN構(gòu)建輸出的目錄, 可以指定參數(shù): --args='is_debug=false target_cpu="x64" v8_target_cpu="arm64" use_goma=true'窃诉,這個命令不會彈出文本窗讓你配置
gn args out/foo --list => 查看這個構(gòu)建輸出目錄當時配置的參數(shù)
如果嫌上面的方式麻煩,那么v8還提供了另外一個腳本來集成這些步驟:v8gen
赤套,命令如下:
alias v8gen=/path/to/v8/tools/dev/v8gen.py
v8gen -b 'V8 Linux64 - debug builder' -m client.v8 foo
v8gen的原理是借助mb_config.pyl
文件飘痛。根據(jù)master配置(-m
)和builder配置(-b
)來生成編譯文件,我們在mb_config.pyl
找到對應的配置:
最后一個參數(shù)foo
是指定生成的二級目錄容握,默認一級目錄是out.gn
宣脉,如下:
你也可以使用默認配置,直接v8gen foo
接下去使用ninja來編譯:
ninja -C out/foo
如果想要指定生成指定目標則:
ninja -C out/foo d8
上述編譯正常會報錯:goma/gomacc: No such file or directory
剔氏。因為我們本地沒有安裝goma塑猖,所以想要正常編譯下去竹祷,還需要安裝一下goma,goma是什么東西呢羊苟?從官網(wǎng)上看塑陵,它是一個輔助編譯加速的工具,詳細可以參考:goma
3蜡励、編譯單個引用到v8庫的C++文件
除了上述整體v8工程編譯令花,如果你想利用v8編譯單個文件的話,比如在官網(wǎng)提到的編譯Hello.cc
中使用到了g++
命令凉倚,對于g++
命令有些參數(shù)是你必須了解的兼都,這里整理了一份,請參考:
g++ -I. -Iinclude samples/hello-world.cc -o hello_world -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++0x
G++命令解釋如下:
-std=
決定使用的語言標準稽寒,當編譯C和C++的時候該選擇支持配置扮碧。
上述命令中的`c++0x`表示:
語言標準使用即將發(fā)布的ISO c++ 0x標準的工作草案。此選項支持可能包含在c++ 0x中的實驗性特性杏糙。工作草案在不斷地變化芬萍,如果GCC的未來版本不屬于c++ 0x標準,那么由這個標志啟用的任何特性都可能被刪除搔啊。
更多標準請參考:[g++](https://linux.die.net/man/1/g++)
-pthread
使用POSIX線程庫添加對多線程的支持柬祠。此選項為預處理器和鏈接器設(shè)置標志。它不影響編譯器生成的目標代碼的線程安全性负芋,也不影響與其提供的庫的線程安全性漫蛔。這些是特定于HP-UX的標志。
-I dir
將目錄dir添加到要搜索頭文件的目錄列表中旧蛾。在系統(tǒng)標準包含目錄之前莽龟,搜索由**-I**指定的目錄。如果目錄*dir*是標準的系統(tǒng)包含目錄锨天,則忽略該選項毯盈,以確保不會破壞系統(tǒng)目錄的默認搜索順序和對系統(tǒng)頭文件的特殊處理。如果*dir*以"="開頭病袄,則"="將被sysroot前綴替換搂赋。
-o file
指定輸出文件。這與將file指定為cpp的第二個非選項參數(shù)相同益缠。gcc 對第二個非選項參數(shù)的有另一種解釋脑奠,因此必須使用-o指定輸出文件
-llibrary
-l library
鏈接時搜索名為library的庫。(第二種指定庫文件的方式僅適用于POSIX遵從性幅慌,不建議使用宋欺。)
在命令中編寫這個選項的位置會有所不同;鏈接器按照指定的順序搜索和處理庫和目標文件。因此,`foo.o -lz bar.o`是在文件foo.o之后搜索庫z。但在bar.o之前齿诞。如果bar.o是引用到了z庫中的函數(shù)酸休,這些函數(shù)是不能被加載。
鏈接器搜索庫的標準目錄列表祷杈,實際上是一個名為`liblibrary.a`的文件雨席。然后鏈接器使用這個文件,就好像它是通過名稱精確指定的一樣吠式。
搜索的目錄包括幾個標準系統(tǒng)目錄,以及您使用-L指定的任何目錄抽米。
通常以這種方式找到的文件是庫文件——其成員是目標文件的歸檔文件特占。鏈接器通過掃描成員來處理存檔文件,這些成員定義了到目前為止已經(jīng)引用但尚未定義的符號云茸。但是是目,如果找到的文件是一個普通的對象文件,則以通常的方式鏈接它标捺。
-Ldir
添加`dir`目錄到搜索目錄列表中去供`-l`使用
這樣上述命令想必一目了然了吧
4懊纳、v8引擎基本概念簡述
在[譯文]V8學習的高級進階完整詳細地介紹了很多概念,這里只是再把這些概念簡化掉亡容,讓大家的記憶更加深刻嗤疯。
4.1、isolate
這個概念在[譯文]V8學習的高級進階沒有提及到闺兢,它表示的一個獨立的V8虛擬機茂缚,擁有自己的堆棧。所以才取名isolate屋谭,意為“隔離”脚囊。在v8中使用以下語法進行初始化:
Isolate* isolate = Isolate::New(create_params);
4.2、handle
handle是指向?qū)ο蟮闹羔樛┐牛赩8中悔耘,所有的對象都通過handle來引用,handle主要用于V8的垃圾回收機制我擂。在 V8 中衬以,handle 分為兩種:持久化 (Persistent)handle 和本地 (Local)handle,持久化 handle 存放在堆上校摩,而本地 handle 存放在棧上泄鹏。比如我要使用本地句柄,句柄指向的內(nèi)容是一個string秧耗,那么你要這么定義:
Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World'", NewStringType::kNormal).ToLocalChecked();
鑒于一個個釋放Handle比較麻煩备籽,v8又提供了HandleScope
來批量處理,你可以在handle之前聲明好:
HandleScope handle_scope(isolate);
4.3、context
context 是一個執(zhí)行器環(huán)境车猬,使用 context 可以將相互分離的 JavaScript 腳本在同一個 V8 實例中運行霉猛,而互不干涉。在運行 JavaScript 腳本是珠闰,需要顯式的指定 context 對象惜浅。創(chuàng)建上下文,需要這樣:
// 創(chuàng)建一個上下文
Local<Context> context = Context::New(isolate);
// 進入上下文編譯和運行腳本
Context::Scope context_scope(context);
4.4伏嗜、V8的數(shù)據(jù)類型
由于 C++ 原生數(shù)據(jù)類型與 JavaScript 中數(shù)據(jù)類型有很大差異坛悉,因此 V8 提供了 Data 類,從 JavaScript 到 C++承绸,從 C++ 到 JavaScrpt 都會用到這個類及其子類裸影,比如:
String::NewFromUtf8(info.GetIsolate(), "version").ToLocalChecked()
這里的String便是V8的數(shù)據(jù)類型。再比如:
v8::Integer::New(info.GetIsolate(), 10);
4.5军熏、對象模板和函數(shù)模板
這兩個模板類用以定義 JavaScript 對象和 JavaScript 函數(shù)轩猩。我們在后續(xù)的小節(jié)部分將會接觸到模板類的實例。通過使用 ObjectTemplate荡澎,可以將 C++ 中的對象暴露給腳本環(huán)境均践,類似的,F(xiàn)unctionTemplate 用以將 C++ 函數(shù)暴露給腳本環(huán)境摩幔,以供腳本使用彤委。
最后
就此,對于v8的了解應該有了一定的雛形了或衡,v8里面有很多重要的概念葫慎,想要繼續(xù)深入的可以參考另外一篇v8的實際應用文章了:如何正確地使用v8嵌入到我們的C++應用中