在過去的文章中,我們討論了很多關(guān)于 V8 的 JavaScript 編譯器。解析器,掃描程序和字節(jié)碼,以及它們的基本原理,內(nèi)核代碼和密鑰結(jié)構(gòu),都是我們介紹的。
在接下來的文章中,我們將逐步介紹編譯器工作流程,并觀察 V8 如何逐步將 JavaScript 代碼轉(zhuǎn)換為字節(jié)碼。
V8從您編寫的 JavaScript 代碼開始,經(jīng)過掃描程序和解析器,最后生成字節(jié)碼。
注意:在本文中,我使用d8.exe而不是v8.exe,因?yàn)閐8.exe可以直接打印到終端,并且d8與v8相比非常輕。
1. 閱讀腳本代碼
以下是我們的測(cè)試用例:
下面是負(fù)責(zé)執(zhí)行 JavaScript 代碼的執(zhí)行()。很簡(jiǎn)單,它首先從文件中讀取JavaScript代碼,然后編譯并執(zhí)行代碼。
在第 5 行中,新取自 8 獲取文件名,這就是我們的情況。在第 8 行中,讀取文件獲取文件內(nèi)容,這實(shí)際上是我們的情況。
第 8 行和第 10 行返回 JavaScript 代碼,具體取決于類型是外部一字節(jié)或 UTF8。在這里,我們的例子是UTF8,關(guān)于字符串::外部一字節(jié)字符串資源,我們將在將來討論它。
返回到源代碼組::執(zhí)行(),并在第 14 行單步執(zhí)行::執(zhí)行字符串,其源代碼如下:
在第 13 行中,我們的 JavaScript 被包裝到一個(gè)變量script_source其中包括行和列偏移量。由于 V8 只編譯完全不是完整 JavaScript 代碼執(zhí)行的 JavaScript 函數(shù),因此變量script_source幫助編譯器記錄編譯信息。
在第 14 行中,開始編譯腳本。
2. 解析器初始化
在工作流中,第一部分是掃描儀,第二部分是解析器。實(shí)際上,掃描程序是被動(dòng)的,解析器是主動(dòng)的,這意味著解析器主動(dòng)地從編譯緩存中取出一個(gè)令牌,一旦緩存未命中,掃描程序就會(huì)被解析器喚醒,并生成令牌并填充緩存。
下面是由腳本編譯器調(diào)用的編譯不受約束的內(nèi)部():編譯在第14行的上面。
上面的函數(shù)生成了未綁定的內(nèi)部?jī)?nèi)容,第 11 行告訴我們,這些內(nèi)容實(shí)際上只是一個(gè)共享函數(shù)。但是,什么是綁定?共享函數(shù)不能直接執(zhí)行,V8 需要將上下文與共享函數(shù)匹配,“匹配”只是必應(yīng)。
讓我們進(jìn)入獲取共享功能信息腳本。
第7行正在查找我在上一篇文章中提到的編譯緩存。
第 13 行,parse_info是解析器的包裝器,就像前面提到的變量script_source樣。
第15行,Parser_info初始化,代碼如下:
從第 6 行到第 12 行,將我們的情況中的 JavaScript 代碼包裝到變量腳本中,然后初始化line_offset并column_offset。就像我說的script_source樣。
返回編譯器::獲取共享功能信息腳本(),然后在第 23 行單步執(zhí)行編譯頂級(jí) ()。
在第 5 行中,文本() 返回抽象語法樹 (AST),如果為空,則 V8 啟動(dòng)編譯器以生成 AST。
第一次,AST 為空,并單步執(zhí)行解析程序。
在第 5 行中,創(chuàng)建解析器,即解析器初始化。
從第8行到第18行,取出編譯信息,如掃描儀和編譯模式。
從第9行到第23行,重要的東西是can_compile_lazily。
第 25 行啟用以 % 開頭的本機(jī)命令。
總結(jié)
V8 使用 UTF16 對(duì)腳本源代碼進(jìn)行編碼;
V8 使用 v8::內(nèi)部::源代碼來管理我們的腳本代碼;
首先,查找編譯緩存,如果緩存未命中,則啟動(dòng)編譯器;
掃描程序是被動(dòng)的,分析器是主動(dòng)的。