Day00:V8 bytecode 系列文介紹


自我介紹

相關背景請參考個人部落格中的關於我頁面,參賽動機下面會寫到,目前規劃這個系列文會有八篇:

  1. Day00:V8 bytecode 系列文介紹(基本知識、產生 bytecode)
  2. Day01:從變數看 bytecode
  3. Day02:從判斷式看 bytecode
  4. Day03:從迴圈看 bytecode
  5. Day04:從函式看 bytecode
  6. Day05:從 class 看 bytecode
  7. Day06:從經典案例看 bytecode
  8. Day07:V8 bytecode 系列文總結

前言

我理解 bytecode 的啟蒙是這一篇文章:Understanding V8’s Bytecode,中譯版:理解 V8 的字節碼,這篇文章裡面有提到 V8 在解析以及運行程式碼時的流程,大致上是這樣的:

  1. 解析 JavaScript 程式碼,產生 AST(Abstract Syntax Tree)
  2. 透過 Ignition 產生 bytecode
  3. 透過 TurboFan 從 bytecode 產生 machine code

上文中的這張圖片,很清楚地說明了 bytecode 的定位:

那會看 bytecode 有什麼好處呢?你可以更接近底層一點,知道 V8 如何去處理某一段程式碼,有時候你會發現,跟你想像中的不太一樣,這就是看 bytecode 的好玩之處!

舉例來說,我以前寫過的三篇文章都有看 bytecode 的段落,透過 bytecode 去看一些底層的東西:

  1. 我知道你懂 hoisting,可是你了解到多深?
  2. 所有的函式都是閉包:談 JS 中的作用域與 Closure
  3. 從 V8 bytecode 探討 let 與 var 的效能問題

在看這些 bytecode 時有一個問題,那就是參考資料太少,中文的不用說,連英文的都不多,而且有些文章並不是那麼好懂,深度有點太深。因此,才有了這個系列文的想法,希望能以一些簡單的範例帶著跟我一樣對 bytecode 有興趣的人來入門,然後來看懂 bytecode。

產生 bytecode

想要看 bytecode,第一步就是要先產生 bytecode。

產生 bytecode 的方法很簡單,透過 Node.js 搭配 --print-bytecode 這個 flag 就行了。但要注意的是 Node.js 內建的東西不少,所以會產生一大堆的 bytecode,這邊推薦的方法是把你想看的程式碼用一個特別的函式名稱包住,搭配 --print-bytecode-filter 來篩選函式名稱,就會容易很多,例如說以下程式碼:

function find_me_test() {
  var a = 1
  console.log(a)
}

find_me_test() //  V8 很智慧,記得要呼叫 function,否則不會產生程式碼

存檔成a.js,然後執行:node --print-bytecode --print-bytecode-filter="find_me*" a.js > byte_code.txt,接著打開byte_code.txt,就可以看到產生的內容:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   83 E> 0x26e2ec856652 @    0 : a0                StackCheck 
  301 S> 0x26e2ec856653 @    1 : 0c 01             LdaSmi [1]
         0x26e2ec856655 @    3 : 26 fb             Star r0
  305 S> 0x26e2ec856657 @    5 : 13 00 00          LdaGlobal [0], [0]
         0x26e2ec85665a @    8 : 26 f9             Star r2
  313 E> 0x26e2ec85665c @   10 : 28 f9 01 02       LdaNamedProperty r2, [1], [2]
         0x26e2ec856660 @   14 : 26 fa             Star r1
  313 E> 0x26e2ec856662 @   16 : 57 fa f9 fb 04    CallProperty1 r1, r2, r0, [4]
         0x26e2ec856667 @   21 : 0d                LdaUndefined 
  320 S> 0x26e2ec856668 @   22 : a4                Return 
Constant pool (size = 2)
Handler Table (size = 0)
1

以上程式碼其實沒有那麼好懂,那是因為少了一個關鍵的資訊:constant pool,有一些常數會放到這個裡面,例如說 console.logconsolelog 你會發現在程式碼裡面都沒有出現,因為這兩個關鍵字都在 constant pool 裡面。

在一般的 Node.js 裡面,你只會看到:Constant pool (size = 2),卻看不到裡面的內容。

想要看到裡面的內容的話,必須要自己 build debug 版本的 Node.js,或是直接去 build debug 版本的 V8,因為我想說反正也沒編譯過 V8,就決定來編譯看看了。

(參考資料:Node.js Bytecode Constant Pool Output

編譯 V8

想要編譯 V8,官方文件提供了滿完整的說明,以下是一些相關文件:

  1. Checking out the V8 source code
  2. depot_tools

但基本上步驟滿簡單的,我的作業系統是 macOS Mojave 10.14.4,步驟如下:

  1. 安裝 depot_tools,步驟如下
  2. 下載 depot_tools:git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
  3. 設置環境變數:export PATH=$PATH:/path/to/depot_tools
  4. 下載 v8:fetch v8
  5. 接著我在編譯時碰到問題,猜測是需要新版的作業系統,因此我編譯的是比較舊版的 V8:git checkout -b 7.3 -t branch-heads/7.3
  6. 安裝必要套件:gclient sync
  7. build debug version:tools/dev/gm.py x64.debug

編譯跟下載套件都需要一段時間,需要耐心等待。編譯完成以後,就可以在 out/x64.debug 底下找到一個執行檔 d8,以後都用這個 d8 即可。

我們用同樣的指令,只是執行檔改成 d8,去編譯上面那一段程式碼看看:./d8 --print-bytecode --print-bytecode-filter="find_me*" a.js > byte_code.txt,結果為:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   21 E> 0x10715a29dcaa @    0 : a5                StackCheck 
  239 S> 0x10715a29dcab @    1 : 0c 01             LdaSmi [1]
         0x10715a29dcad @    3 : 26 fb             Star r0
  243 S> 0x10715a29dcaf @    5 : 13 00 00          LdaGlobal [0], [0]
         0x10715a29dcb2 @    8 : 26 f9             Star r2
  251 E> 0x10715a29dcb4 @   10 : 28 f9 01 02       LdaNamedProperty r2, [1], [2]
         0x10715a29dcb8 @   14 : 26 fa             Star r1
  251 E> 0x10715a29dcba @   16 : 59 fa f9 fb 04    CallProperty1 r1, r2, r0, [4]
         0x10715a29dcbf @   21 : 0d                LdaUndefined 
  258 S> 0x10715a29dcc0 @   22 : a9                Return 
Constant pool (size = 2)
0x10715a29dc31: [FixedArray] in OldSpace
 - map: 0x1071768807b1 <Map>
 - length: 2
           0: 0x1071ec1100e9 <String[#7]: console>
           1: 0x1071ec10fbe9 <String[#3]: log>
Handler Table (size = 0)
1

會發現 constant pool 的內容顯示出來了。至此,前置作業已經都準備完成。

結語

在這篇文章中簡單介紹了 bytecode 的定位以及系列文未來的方向,在之後的文章裡面會慢慢補齊一些基礎知識,也會從各個基礎的語法來學習 bytecode。

#javascript







你可能感興趣的文章

簡明 Vim 文字編輯器操作入門教學

簡明 Vim 文字編輯器操作入門教學

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

MTR04_0702

MTR04_0702






留言討論