V8 的巧妙編碼技巧用於類型追蹤

V8's neat encoding trick for type tracking

感謝 v8:兩個編譯器的故事 我發現了一個關於 Google 的 V8 JavaScript 編譯器在執行時類型追蹤過程中編碼類型的巧妙技巧。今天我想把它寫下來,以確保自己真的理解了。

一個運行時編譯器加速動態語言的方式之一是生成專門的程式碼,只對特定類型的物件進行操作,並使用越來越多的本機值和程式碼;如果你的迴圈實際上只處理整數,編譯器就會想把它編譯成本機整數上的整數運算。為了做到這一點,編譯器必須在執行時追蹤你的程式碼處理的實際物件類型;這通常被稱為… 輸入反饋

作為此過程的一部分,V8 追蹤一個類型層次結構,或者説是一系列的類型區分(預計是基於它能在生成代碼中進行的特化)。這些區分包括:非原始類型與原始類型,然後是字符串與數字,雙精度浮點數與整數,最後是全尺寸整數或小整數。如果你的程式碼始終處理具有相同類型的值,你將保持在層次結構中的相應位置;但如果值的類型發生變化,V8 必須將舊的類型資訊與新的類型結合,以決定你在層次結構中的新位置。

為了將這些資訊緊湊地儲存並高效地執行結合操作,V8 開發了一個巧妙的編碼技巧。機械上,V8 將這類型資訊表示為 8 位元位圖,並透過將新型別進行 AND 操作到目前的型別資訊中來結合新的型別資訊。嗯,差不多。其實它真正做的是使用 有選擇性地從型別資訊中移除位元,隨著所涉及的型別越來越不具體。讓我向你展示實際的編碼,寫成位元(閲讀這些是我如何理解發生了什麼以及它如何運作的方式):

未初始化 1111111
非原始 一百萬
字串 零一一零零零零
0011001
小整數 0010111
整數 0010011
數字 零零一零零零一
原始 0010000
未知 0000000

(取自 V8 的 type-info.h . )

我們在未初始化狀態時,所有位元都被設為 1。隨著我們向上移動越來越高層次,對所涉及的具體類型瞭解越來越少,編碼就會越來越多地移除位元;最終,我們會得到一個沒有任何位元被設為 1 的編碼(當程式碼的特定位置同時處理原始類型和非原始類型時)。以這種方式寫出來,你可以看到隨著細節越來越少,移除的位元如何層層疊疊(以及層次結構中相關類型如何共享位元),以及位元模式是如何被精心挑選以使其運作。

(這種編碼有許多具體的變化,顯而易見,因為重要的不是各類型在位圖中出現的位置,而是它們之間共有多少共同的位元。我假設 Google 選擇這個特定編碼,是因為它在某種程度上更方便。)

我們可以倒推來推斷特定類型資訊的位元設置數量,根據它在階層中的深度。原始類型和非原始類型相距一步,因此各有一個位元設置。字串和數字相距兩步,因此它們有兩個位元設置(一個來自 Primitive,另加一個位元來區分它們)。以此類推,直到最遠一步的小整數,其位元設置為四個。這不一定有助於理解 V8,但如果你想在其他情境中使用相同技巧並需要自行制定編碼,這可能會有所幫助。

(我相信但尚未證明你需要的總位元數與型別階層中的步驟數相同;在這裡 V8 有七個步驟(不計算「未初始化」或「未知」),並使用七個位元。)