JavaScript 引擎深入剖析(一):JSValue 的內部實現

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們 "},{"type":"text","marks":[{"type":"strong"}],"text":"Hummer 跨端技術框架"},{"type":"text","text":" 的研發過程中,不可避免會對 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎有所探索和研究。只有深入瞭解了 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 的工作原理,才能在跨端研發的諸多細節上避免踩坑,並且做出更好地調優工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於很多前端同學來說,"},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎就像一個難以觸及的黑盒,既熟悉又陌生,因爲它被內置在了瀏覽器內核中。即使在平時開發過程中天天和 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎打交道,但大多也只是知道 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎可以解釋執行 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 代碼,對於其內部實現原理並不是特別瞭解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們接下來會專門花幾個專題,來深入剖析一下 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎的世界,逐步揭開它的神祕面紗。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一期我們主要講一下 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎中的 "},{"type":"text","marks":[{"type":"strong"}],"text":"“JSValue 的內部實現”。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"許多現代編程語言都具有稱之爲"},{"type":"text","marks":[{"type":"strong"}],"text":"動態類型"},{"type":"text","text":"的功能。動態類型語言和靜態類型語言之間的主要區別在於,大多數類型檢查是在運行時執行的,而不是在編譯時執行的。類型不再與變量關聯,而是與內部存儲的基礎值關聯,本文將以 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 爲例進行分析。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"實現方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎的第一步是實現值的表示形式,這其實有一定的難度,因爲 "},{"type":"codeinline","content":[{"type":"text","text":"JS值"}]},{"type":"text","text":" 可以是幾種不同的類型中的任何一種:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"undefined"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"null"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"boolean"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"number (double)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"reference (string, Symbol, Object, etc)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要實現 "},{"type":"codeinline","content":[{"type":"text","text":"動態類型"}]},{"type":"text","text":" 就需要一種能夠表示上面所有類型的數據結構。實現這樣的值類型主要有以下幾種方式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tagged 方式"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tagged unions("},{"type":"codeinline","content":[{"type":"text","text":"QuickJS"}]},{"type":"text","text":")"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"tagged pointer("},{"type":"codeinline","content":[{"type":"text","text":"V8"}]},{"type":"text","text":")"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"boxing 方式"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"nan-boxing("},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":")"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"nun-boxing & pun-boxing("},{"type":"codeinline","content":[{"type":"text","text":"SpiderMonkey"}]},{"type":"text","text":")"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面分別來詳細介紹下這些實現方式,以及這些方式對應的落地 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 引擎。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. tagged unions"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來看下 "},{"type":"codeinline","content":[{"type":"text","text":"QuickJS"}]},{"type":"text","text":" 中比較直接的一種實現方式:"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"QuickJS"}]},{"type":"codeblock","attrs":{"lang":"c"},"content":[{"type":"text","text":"#else \/* !JS_NAN_BOXING *\/\n\ntypedef union JSValueUnion {\n int32_t int32;\n double float64;\n void *ptr;\n} JSValueUnion;\n\ntypedef struct JSValue {\n JSValueUnion u;\n int64_t tag;\n} JSValue;\n\n#define JSValueConst JSValue\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這其實是 "},{"type":"codeinline","content":[{"type":"text","text":"tag"}]},{"type":"text","text":" + "},{"type":"codeinline","content":[{"type":"text","text":"struct"}]},{"type":"text","text":" 的改進版。使用 "},{"type":"codeinline","content":[{"type":"text","text":"union"}]},{"type":"text","text":" 可減少一定的內存使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但缺點是不論 "},{"type":"codeinline","content":[{"type":"text","text":"JSValue"}]},{"type":"text","text":" 表示 "},{"type":"codeinline","content":[{"type":"text","text":"int32"}]},{"type":"text","text":" 還是 "},{"type":"codeinline","content":[{"type":"text","text":"指針"}]},{"type":"text","text":" 類型。都需要 16 個字節(以在雙精度浮點數或 64 位指針或 int64 上保持 8 字節對齊)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼是否有更好的 "},{"type":"codeinline","content":[{"type":"text","text":"JSValue"}]},{"type":"text","text":" 表示方法呢?能否壓縮到只用8字節呢?接下來我們先來看 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 的實現。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. nan-boxing"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開始之前,我們需要一些準備知識。"},{"type":"link","attrs":{"href":"http:\/\/eng.umb.edu\/~cuckov\/classes\/engin341\/Reference\/IEEE754.pdf","title":"","type":null},"content":[{"type":"text","text":"IEEE 754"}]},{"type":"text","text":" 標準。在下文所提標準中,如無特殊說明,均爲 IEEE 754,且以 64 架構爲例。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"double"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 的定義可以根據 "},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/IEEE_754","title":"","type":null},"content":[{"type":"text","text":"維基百科的相關鏈接"}]},{"type":"text","text":" 查看。這裏我們主要摘錄其格式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/2d\/2dfc8667da1a2224f4d3eeb30bd078fa.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"sign: 表示正負,0爲正,1爲負"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"exponent: 指數位"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fraction: 尾數"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以 "},{"type":"text","marks":[{"type":"strong"}],"text":"0.3"},{"type":"text","text":" 爲例:二進制格式: 0b0011111111010011001100110011001100110011001100110011001100110011"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"NaN"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣,根據標準,"},{"type":"codeinline","content":[{"type":"text","text":"NaN"}]},{"type":"text","text":"(Not a Number)的定義和種類 ("},{"type":"codeinline","content":[{"type":"text","text":"NaN"}]},{"type":"text","text":" 同樣分爲兩種類型:"},{"type":"codeinline","content":[{"type":"text","text":"qNaN"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"sNaN"}]},{"type":"text","text":",具體請看"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/NaN","title":"","type":null},"content":[{"type":"text","text":"這裏"}]},{"type":"text","text":") 如圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/1d\/1d813756a449f5c951f4575e8577d6cb.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏簡單說明下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 exponent 全部設置爲 1,則表示爲 NaN。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"剩餘的 fraction(Mantissa) 的最左邊 1 位,代表 NaN 的類型。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,一個 "},{"type":"codeinline","content":[{"type":"text","text":"NaN"}]},{"type":"text","text":" 值,是有 51(64 - 11 + 1 + 1) 位未使用的。而 "},{"type":"codeinline","content":[{"type":"text","text":"指針"}]},{"type":"text","text":" 真正也只是使用("},{"type":"link","attrs":{"href":"https:\/\/www.viva64.com\/en\/k\/0042\/","title":"","type":null},"content":[{"type":"text","text":"限制"}]},{"type":"text","text":")了 64 位中的 48 位。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們對超過 "},{"type":"codeinline","content":[{"type":"text","text":"0x0000 7fff ffff ffff"}]},{"type":"text","text":" 的地址進行尋址時,會收到一個 "},{"type":"codeinline","content":[{"type":"text","text":"EXC_I386_GPFLT"}]},{"type":"text","text":" 錯誤。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我們可以在剩餘的 51 位中,按照一定的 "},{"type":"codeinline","content":[{"type":"text","text":"規則"}]},{"type":"text","text":" 寫入(encode)一些自定義的數據(payload),再按照同樣的規則讀取(decode)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們先來看下 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 的實現。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 使用了 "},{"type":"codeinline","content":[{"type":"text","text":"qNaN"}]},{"type":"text","text":" 標準來表示,因此有 "},{"type":"codeinline","content":[{"type":"text","text":"51bit"}]},{"type":"text","text":" 來對剩餘的 "},{"type":"codeinline","content":[{"type":"text","text":"payload"}]},{"type":"text","text":" 進行編碼\/解碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" Pointer { 0000:PPPP:PPPP:PPPP\n \/ 0002:****:****:****\n Double { ...\n \\ FFFC:****:****:****\n Integer { FFFE:0000:IIII:IIII\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代碼表示了 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 中不同值類型的範圍。但是我們可以發現,這和 "},{"type":"codeinline","content":[{"type":"text","text":"IEEE-754"}]},{"type":"text","text":" 定義的標準存在偏差。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回過頭來再來看 "},{"type":"codeinline","content":[{"type":"text","text":"IEEE-754"}]},{"type":"text","text":" 中定義的 "},{"type":"codeinline","content":[{"type":"text","text":"qNaN"}]},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/46\/469ae73737c19efc20d93dd0380656c8.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據上圖,我們可以得知 "},{"type":"codeinline","content":[{"type":"text","text":"NaN"}]},{"type":"text","text":" 的範圍(16進製表示)如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"0xfff8 xxxx xxxx xxxx  ~  0xffff xxxx xxxx xxxx"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 的範圍實際爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"0x0000 xxxx xxxx xxxx  ~  0xfff7 xxxx xxxx xxxx"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 範圍 (0x0002x ~ 0xFFFCx) 明顯存在偏差。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這麼做的原因是 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 更偏向對指針的操作。如果完全採用 "},{"type":"codeinline","content":[{"type":"text","text":"IEEE-754"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"qNaN"}]},{"type":"text","text":" 定義,則指針可能是下面這形式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/5c\/5cd1e2c84cb59275431a6aa49a508806.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣我們在使用時,就需要進行 "},{"type":"codeinline","content":[{"type":"text","text":"mask"}]},{"type":"text","text":" 操作,來讀取真正的 "},{"type":"codeinline","content":[{"type":"text","text":"指針"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 的這種做法,使得指針的操作變得簡單高效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 的問題如何處理呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"The scheme we have implemented encodes double precision values by performing a 64-bit integer addition of the value 2^49 to the number. After this manipulation no encoded double-precision value will begin with the pattern 0x0000 or 0xFFFE. Values must be decoded by reversing this operation before subsequent floating point operations may be peformed."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 的範圍從 "},{"type":"codeinline","content":[{"type":"text","text":"0x0002x"}]},{"type":"text","text":" 起,因此需要進行修正 (減去 2^49)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/85\/8550783d014d2ac17949e301b948f20b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源碼位置如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"ALWAYS_INLINE JSValue::JSValue(EncodeAsDoubleTag, double d)\n{\n ASSERT(!isImpureNaN(d));\n u.asInt64 = reinterpretDoubleToInt64(d) + JSValue::DoubleEncodeOffset;\n}\n\ninline double JSValue::asDouble() const\n{\n ASSERT(isDouble());\n return reinterpretInt64ToDouble(u.asInt64 - JSValue::DoubleEncodeOffset);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 中所有的類型位模式設計如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"embedcomp","attrs":{"type":"table","data":{"content":"
類型encode pattern
ValEmpty0x0000 0000 0000 0000
Null0x0000 0000 0000 0002
Wasm0x0000 0000 0000 0003
ValueDeleted0x0000 0000 0000 0004
false0x0000 0000 0000 0006
true0x0000 0000 0000 0007
Undefined0x0000 0000 0000 000a
pointer0x0000 PPPP PPPP PPPP
double0x0002 xxxx xxxx xxxx
double0xFFFC xxxx xxxx xxxx
Integer0xFFFE 0000 IIII IIII"}}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以發現這裏的 "},{"type":"codeinline","content":[{"type":"text","text":"not a number"}]},{"type":"text","text":" 更想表達的是 "},{"type":"codeinline","content":[{"type":"text","text":"not a double"}]},{"type":"text","text":"!"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. nun-boxing & pun-boxing"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScriptCore"}]},{"type":"text","text":" 可以選擇保留對指針的直接操作,而對 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 特殊處理,那麼相反,我們也可以保留 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":" 的原來標準,對指針進行編碼。"},{"type":"link","attrs":{"href":"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Projects\/SpiderMonkey\/Internals","title":"","type":null},"content":[{"type":"text","text":"Mozilla’s SpiderMonkey"}]},{"type":"text","text":" 採用了這種方式,可以參考 "},{"type":"codeinline","content":[{"type":"text","text":"SpiderMonkey"}]},{"type":"text","text":" 中對 "},{"type":"link","attrs":{"href":"https:\/\/searchfox.org\/mozilla-central\/source\/js\/public\/Value.h#342","title":"","type":null},"content":[{"type":"text","text":"JSValue"}]},{"type":"text","text":" 的定義。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"SpiderMonkey"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在32位設備平臺中,"},{"type":"codeinline","content":[{"type":"text","text":"SpiderMonkey"}]},{"type":"text","text":" 使用 "},{"type":"codeinline","content":[{"type":"text","text":"nun-boxing"}]},{"type":"text","text":" 。其中 "},{"type":"codeinline","content":[{"type":"text","text":"u"}]},{"type":"text","text":" 代表 "},{"type":"codeinline","content":[{"type":"text","text":"unboxed"}]},{"type":"text","text":" 。因爲非 double 類型的值,直接使用 32(tag) + 32(payload) 的方式,即:payload 的部分是 "},{"type":"codeinline","content":[{"type":"text","text":"unboxed"}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 x64 和類似的 64 位平臺上,指針的長度超過 32 位,因此不能使用 "},{"type":"codeinline","content":[{"type":"text","text":"nun-boxing"}]},{"type":"text","text":" 格式。取而代之的是使用 "},{"type":"codeinline","content":[{"type":"text","text":"pun-boxing"}]},{"type":"text","text":",17(tag) + 47(payload)。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4. tagged pointer"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲一名 iOS 開發,提起 "},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Tagged_pointer","title":"","type":null},"content":[{"type":"text","text":"Tagged Pointer"}]},{"type":"text","text":",應該是比較熟悉的。下面先以 iOS 中的 "},{"type":"codeinline","content":[{"type":"text","text":"Tagged Pointer"}]},{"type":"text","text":" 爲例簡單介紹下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 64 位架構中,一個指針爲 8 字節(64 位),但是通常不會真正使用到所有這些位,且由於內存對齊要求的存在,低位始終爲0。高位也始終爲0 (內存訪問限制)。實際上我們只是用中間這一部分的位。下面圖片均來源於 "},{"type":"link","attrs":{"href":"https:\/\/developer.apple.com\/videos\/play\/wwdc2020\/10163\/","title":"","type":null},"content":[{"type":"text","text":"WWDC"}]},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/08\/08e48ce36ba4a6743c3a1475cc2dab61.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我們可以使用其餘的部分進行標記存儲,根據標記讀取 payload 中數據的具體類型:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/31\/31c271f8f37f4e8aa358f44ee5fcbf10.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是 Objective-C 中的標記類型:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"c"},"content":[{"type":"text","text":"OBJC_TAG_NSAtom = 0, \nOBJC_TAG_1 = 1, \nOBJC_TAG_NSString = 2, \nOBJC_TAG_NSNumber = 3, \nOBJC_TAG_NSIndexPath = 4, \nOBJC_TAG_NSManagedObjectID = 5, \nOBJC_TAG_NSDate = 6, \nOBJC_TAG_7 = 7\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再來看一下 V8。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"V8"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 V8 中 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 的對象、數組、數字或者字符串都是用對象表示的,分配在 V8 堆區。這使得可以用一個指向對象的指針表示任何值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而爲了避免整數的堆內存佔用,V8 使用了 "},{"type":"codeinline","content":[{"type":"text","text":"Tagged Pointer"}]},{"type":"text","text":" 來表示其他數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 32 位架構中,表示如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"c"},"content":[{"type":"text","text":" |----- 32 bits -----|\nPointer: |_____address_____w1|\nSmi: |___int31_value____0|\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標記位(tag bits)有雙重作用:用於指示位於 V8 堆中對象的強\/弱指針或一個小整數的信號。因此,整數能夠直接存儲在標記值中,而不必爲其分配額外的存儲空間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 64 位架構中,表示如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"c"},"content":[{"type":"text","text":" |----- 32 bits -----|----- 32 bits -----|\nPointer: |________________address______________w1|\nSmi: |____int32_value____|0000000000000000000|\n"}]},{"type":"heading","attrs":{"align":null,"level":4}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"指針壓縮"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從 32 位切換到 64 位。這個變化帶給了 Chrome 更好的安全性、穩定性和性能,但同時也帶來了更多內存消耗,因爲之前每個指針佔用 4 個字節而現在佔用是 8 個字節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"V8 的堆區包含如下:浮點值(floating point values)、字符串字符(string characters)、解析器字節碼(interpreter bytecode)和標記值(tagged values)。而在檢查堆區時發現,標記值佔了 V8 堆區的70%!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了減少內存佔用,V8 使用基於基地址的 32 位偏移量,代替直接存儲 64 位指針。具體見 "},{"type":"link","attrs":{"href":"https:\/\/v8.dev\/blog\/pointer-compression","title":"","type":null},"content":[{"type":"text","text":"Pointer Compression in V8"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓縮前的內存佈局如下:(圖片來源 "},{"type":"link","attrs":{"href":"https:\/\/www.youtube.com\/watch?v=XsgUEUXP9no&feature=youtu.be&t=589","title":"","type":null},"content":[{"type":"text","text":"What's happening in V8? - Benedikt Meurer"}]},{"type":"text","text":")"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/c9\/c9c96169b9c2f208d11ac00fbd940a18.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓縮後的內存佈局如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b7\/b770c95f1f5fa3e72aec2231321e6071.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該項技術使用也較爲廣泛,如最近的 2020 WWDC 上 "},{"type":"link","attrs":{"href":"https:\/\/developer.apple.com\/videos\/play\/wwdc2020\/10163","title":"","type":null},"content":[{"type":"text","text":"Advancements in the Objective-C runtime"}]},{"type":"text","text":",也使用了該技術。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以發現類 "},{"type":"codeinline","content":[{"type":"text","text":"nan-boxing"}]},{"type":"text","text":" 的方案具有明顯的優勢,即不會在堆上分配 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":",大大減少了緩存壓力和 GC 壓力等。這就是 Moz 和 JSC 選擇它的原因。同時如果在 32 位架構上,Moz 和 JSC 也會分配 64 位內存來實現裝箱。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而 V8 雖然會在堆上分配 "},{"type":"codeinline","content":[{"type":"text","text":"double"}]},{"type":"text","text":",但也針對一些常見的場景進行了優化,如 Smi(small integer),且無論在 32 位還是 64 位架構上,V8 都只需要 32 位來表示指針。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"參考鏈接"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http:\/\/wingolog.org\/archives\/2011\/05\/18\/value-representation-in-javascript-implementations","title":"","type":null},"content":[{"type":"text","text":"value representation in javascript implementations"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/leonardschuetz.ch\/blog\/nan-boxing\/","title":"","type":null},"content":[{"type":"text","text":"Dynamic Typing and NaN Boxing"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/anniecherkaev.com\/the-secret-life-of-nan","title":"","type":null},"content":[{"type":"text","text":"the secret life of NaN"}]},{"type":"link","attrs":{"href":"https:\/\/steve.hollasch.net\/cgindex\/coding\/ieeefloat.html","title":"","type":null},"content":[{"type":"text","text":"IEEE Standard 754 Floating Point Numbers"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/firefox-source-docs.mozilla.org\/js\/index.html","title":"","type":null},"content":[{"type":"text","text":"SpiderMonkey"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.youtube.com\/watch?v=XsgUEUXP9no&feature=youtu.be&t=589","title":"","type":null},"content":[{"type":"text","text":"What's happening in V8? - Benedikt Meurer"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/v8.dev\/blog\/pointer-compression","title":"","type":null},"content":[{"type":"text","text":"Pointer Compression in V8"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/developer.apple.com\/videos\/play\/wwdc2020\/10163\/","title":"","type":null},"content":[{"type":"text","text":"Advancements in the Objective-C runtime"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"史廣遠:"},{"type":"text","text":"Hummer 核心成員,主要負責 Hummer 框架的 iOS 端研發工作,對 JavaScript 引擎有着非常深入的理解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"關於我們"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Hummer 官網:"},{"type":"link","attrs":{"href":"https:\/\/hummer.didi.cn\/","title":"","type":null},"content":[{"type":"text","text":"https:\/\/hummer.didi.cn"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Hummer GitHub:"},{"type":"link","attrs":{"href":"https:\/\/github.com\/didi\/Hummer","title":"","type":null},"content":[{"type":"text","text":"https:\/\/github.com\/didi\/Hummer"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Hummer 郵箱:"},{"type":"link","attrs":{"href":"mailto:[email protected]","title":"","type":null},"content":[{"type":"text","text":"[email protected]"}]},{"type":"text","text":"*"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"延伸閱讀"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/GjajcAFKnSvEatnhsGhpdA","title":"","type":null},"content":[{"type":"text","text":"《滴滴開源輕量級跨端開發框架:Hummer》"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/CyNnbEQrtryHF-glRHvngQ","title":"","type":null},"content":[{"type":"text","text":"《揭祕 Hummer —— 爲何選擇 Hummer ?》"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章