WebAssembly最新發展路線圖來了!
WebAssembly 簡介
WebAssembly 開發團隊的描述:
WebAssembly(或 wasm)是一種適用於 Web 的可移植編譯格式,提供更小的檔案尺寸和更快的載入速度。
實際上,WebAssembly 旨在成為高階語言的編譯目標。目前可以使用 C、C++、Rust、Go、Java、C# 編譯器(還有更多)來建立 wasm 模組。
WebAssembly 模組以二進位制的格式傳送到瀏覽器,並在專有虛擬機器上執行。這個虛擬機器與 JavaScript 虛擬機器共享資源,如記憶體和執行緒。WebAssembly 模組總是與 JavaScript 程式碼一起使用,在必要的時候可以執行一些有用的操作。
目前正在進行中的很多提案旨在讓 WebAssembly 成為更好的編譯目標,並減少 JavaScript“膠水”程式碼。

image
WebAssembly 提案
WebAssembly 提案的流程是分階段的,從階段 1(不成熟)到階段 5(完成標準化)。以下是目前所有提案的清單(前 4 個階段),每個提案都將在後面詳細介紹。
第 1 階段(功能提案)包括以文字格式自定義註解、主機繫結、尾呼叫、批量記憶體操作、ECMA 模組整合、垃圾回收、異常處理、固定寬度 SIMD、執行緒。
第 2 階段(規範提議)包括 BigInt 轉換。
第 2 階段(實現)包括引用型別和返回多個值。
第 4 階段(標準化)包括匯出和匯入可變全域性變數和有符號擴充套件操作。
引用型別
目前的 WebAssembly 型別系統還很小,只有四種數字型別。目前,如果要使用複雜型別(例如字串、物件、陣列、結構體),需要將它們序列化為線性記憶體,並提供它們所在位置的引用。這個提案對型別系統進行了擴充套件,添加了一個新的 anyref 型別,模組可以持有對主機環境物件的引用,也就是說,你可以將 JS 物件傳給 wasm 模組。
通過 anyref 引用的物件對於 wasm 模組來說意義不是很大,關鍵在於模組可以持有在 JS 堆上分配的物件的引用,這意味著在 wasm 執行期間需要對這些引用進行跟蹤。該提案被視為垃圾回收提案的墊腳石。
返回多個值
WebAssembly 的虛擬機器是基於棧的,操作物件在函式被呼叫之前是放在棧上的,函式會使用這些物件並替換為返回值。在目前的規範中,函式只能返回單個值。新的提案允許指令、函式和塊返回多個值(例如,整數除法可以返回除數和餘數)。
下面是一個簡單的“swap”函式,它可以返回多個值(result i32 i32)。
(func $swap (param i32 i32) (result i32 i32) (get_local 1) (get_local 0) )

image
匯出和匯入可變全域性變數
目前,wasm 支援全域性變數和區域性變數。全域性變數可以被匯入和匯出,並與 JS 宿主共享,不過必須將它們定義為不可變的。
新的提案將 WebAssembly.Global 建構函式新增到 JS API 中,允許匯出和匯入可變的全域性變數。該特性對於跨多個 wasm 模組共享狀態來說非常有用。
有符號擴充套件操作
符號擴充套件是一種在保留符號的同時增加二進位制數位數的方法。這個提案添加了少量符號擴充套件指令,例如 i32.extend8_s——將有符號的 8 位整數擴充套件為 32 位整數。
FireFox 已經實現了這個特性,並計劃在 9 月釋出。
BigInt 轉換
JavaScript 只有一個數字型別,即 IEEE 754 浮點數——這種表示法存在一定的侷限性。現在有一個新興的標準,即增加對“大整數”的支援,目前處於 TC39 流程的第 3 階段。最終確定後,將為開發人員提供任意精度的整數。
WebAssembly 的四種數字型別之一是 64 位整數。新提案將提供完全的互操作,讓 JS 的 BigInt 和 wasm 的 64 位整數實現雙向轉換。
順便提一下,現在已經有一個用於在 JavaScript 中執行 64 位運算的庫,叫作 long.js。最近,他們移除了基於 JavaScript 的實現,改用更簡單的 WebAssembly!

image
執行緒
JavaScript 已經通過 WebWorker 實現了多執行緒,但是在 worker 之間只能使用 postMessage 進行較慢的訊息傳遞。共享記憶體提案已經在 TC39 中定稿,並在 2017 年 2 月成為 ECMAScript 的一部分。共享陣列緩衝區和原子性讓執行緒之間共享資料變得更加容易。
WebAssembly 的這個提案也是允許共享訪問線性記憶體,並提供原子操作。但值得注意的是,提案並沒有引入建立執行緒的機制(引起了很多爭議),而是由宿主提供此功能,也就是我們熟悉的 WebWorker。
我相信會有一些人對這個提案感到失望。不過,WebAssembly 之所以能夠快速發展到 MVP 版本,跟團隊專注於簡單性不無關係。利用成熟的宿主功能(WebWorker)是非常有意義的。
將來可能會新增原生執行緒,但會作為單獨的提案,不過這可能還需要幾年的時間!
固定寬度 SIMD
單指令多資料流(SIMD)是一組支援向量風格處理的指令,例如,將一個向量新增到另一個向量種。所有現代 CPU 都支援這些指令。有一個 SIMD.js TC39 提案,增加了很多 128 位型別,例如 float32 x 4,以及相應的操作(如 add、multiply),但最近刪除了這部分內容,因為 WebAssembly 中已經添加了類似的功能。這樣做是有道理的,因為這些是低階指令,而 WebAssembly 恰好是低階執行時。
WebAssembly 的 SIMD 提案非常簡單,為 wasm 新增一個新的 128 位型別,可以表示四個數字的向量,以及用於建立和操作這些新型別的簡單指令集。這將為某些演算法類別的效能帶來改進。

image
異常處理
程式會在出現異常時中斷控制流,異常會順著呼叫棧向上傳播,直到遇到合適的“catch”塊。異常處理是大多數現代程式語言的共同特徵,儘管 Swift 在早期版本中並不支援它們。
WebAssembly MVP 在當前控制流指令中沒有任何類似於異常處理的東西。因此不得不使用 Emscripten 這樣的工具來模擬這個功能,但這是以犧牲效能為代價——目前,預設情況下捕獲 C++ 異常是關閉的,其他語言也面臨類似的問題。
異常處理提案概述了構建一個與宿主環境“良好配合”的概念所涉及的大量複雜性。有趣的是,這是第二次嘗試建立這個提案,可見他們目前面臨的挑戰有多嚴峻!
該提案不僅要向 WebAssembly 新增異常處理,更是要引入一種更通用的事件概念,看起來很像中斷。當事件發生時,執行被暫停,在 events 處安插一個恰當的處理程式。
除了事件,可能還會新增標準的 try/catch 指令:
try block_type instruction* catch instruction* end
這將減少 WebAssembly 編譯器的一些複雜性。
垃圾回收
大多數現代程式語言(不包括系統級語言)使用垃圾回收器進行記憶體管理。簡而言之,垃圾回收器(GC)讓開發人員無需過多考慮記憶體管理,他們可以建立物件、傳遞物件、在函式 / 變數之間共享物件,並且在不再使用這些物件時依靠 GC 來清理它們。
WebAssembly 沒有垃圾回收器。事實上,它沒有任何可用於記憶體管理的工具,它只是為你提供了一塊“記憶體”。不使用 GC 的程式語言仍然需要某種機制來管理記憶體分配,例如 Rust 使用了一個小型的 WebAssembly 優化分配器。
目前,需要垃圾回收器的程式語言沒有其他選擇,只能將 GC 編譯為 wasm,並將其作為二進位制檔案的一部分,例如 AssemblyScript 就在二進位制檔案中包含了一個“makeshift GC”。但這樣會增加二進位制檔案的大小,同時 GC 演算法的效率也會受到影響。缺少 GC 是 Scala 和 Elm 等語言還不支援編譯成 WebAssembly 的原因。
這個提案將 GC 功能帶到 WebAssembly 中。有趣的是,它不會有自己的 GC,而是與宿主環境的 GC 整合。還有其它各種其他提案(宿主繫結、引用型別)旨在改進與宿主的互操作性,從而更容易共享狀態和呼叫 API。使用單個 GC 來管理記憶體會讓這些變得更容易。
這個提案是一個重大變更,wasm 的型別系統因此添加了很多新的東西,包括簡單的元組、結構體和陣列。還有一些討論是關於新增字串型別的。
在 WebAssembly 中使用 GC 是可選的,這樣 Rust/C++ 就可以使用記憶體分配器和線性記憶體。新型別將在新的 WebAssembly 堆上進行分配,儘管提案中未明確說明。我猜宿主堆也可以使用,但可能會帶來很大的開銷。
這個提案增加了很多新的指令,這是結構體的一個例子:
;; structures with fields (type $point (struct (field $x f64) (field $y f64) (field $z f64))) ;; allocated with new (call $g (new $point (i32.const 1) (i32.const 2) (i32.const 3))) ;; field accessors - type checked when validated (load_field $point $x (get_local $p)

image
ECMA 模組整合
ECMAScript 模組(ESM)是一個相對較新的規範,所有主流瀏覽器現在都已支援。
目前,wasm 模組是通過 HTTP 進行載入的,然後使用 JS API 進行例項化:
const req = fetch("./myModule.wasm"); const instance = await WebAssembly.instantiateStreaming(req) instance.exports.foo()
這個提案引入了一種機制,可以通過 ESM 匯入的方式來載入 wasm 模組:
import {foo} from "./myModule.wasm"; foo()
這讓例項化 wasm 模組變得更簡單,而更大的好處是它們成為 JS 模組圖的一部分,也可以進行搖樹優化(tree shaking)、捆綁、程式碼分割和 ESM 支援的其他優化。
批量記憶體操作
該提案增加了複製 / 填充線性記憶體區域的新操作。它們將為某些場景帶來更好的效能。
尾呼叫
遞迴函式呼叫可能會導致很深的呼叫棧,會帶來各種問題(效能、記憶體消耗、堆疊溢位的可能性)。通過尾呼叫優化,遞迴函式呼叫將被替換為迭代。這種技術對於函式式語言來說非常重要。為此,該提案引入了新的 return_call 指令。
宿主繫結
基於多種因素(wasm 型別系統太過簡單、缺少引用型別等等),WebAssembly 與 JavaScript 宿主之間的當前介面非常有限。如果你想要編寫一個操作 DOM 或使用其他瀏覽器 API 的 wasm 模組,必須編寫大量“膠水”程式碼。
這個提案允許 WebAssembly 模組建立、傳遞、呼叫和操作 JavaScript/DOM 物件。它添加了一部分與宿主繫結相關的內容,其中包括用於描述繫結機制或介面的註解。
Rust 已經有了一個工具,叫作 wasm-bindgen,它的作用與該提案很相似。使用 wasm-bindgen,你就可以輕鬆地跨越 wasm 和 JS 傳遞字串等物件。該工具將繫結元資料新增到 wasm 模組中,並生成所需的 JS 膠水程式碼。

image
以文字格式自定義註解
wasm 二進位制格式支援將元資料新增到模組中。這個提案為文字格式也添加了類似的功能,這對宿主繫結來說非常有用。舉個例子:
(module (func (export "f") (param i32 (@js unsigned)) ...) )
@js unsigned 註解添加了用於生成宿主繫結的其他元資料。
結論
希望這些能讓你對 WebAssembly 未來的發展方向有所瞭解。這些提案中的一些小改進可能很快就能完成,但大的改進可能需要幾年時間才能完全實現。

image
最後,給大家推薦一個 前端學習進階內推交流群685910553 (前端資料分享),不管你在地球哪個方位,
不管你參加工作幾年都歡迎你的入駐!(群內會定期免費提供一些群主收藏的免費學習書籍資料以及整理好的面試題和答案文件!)
如果您對這個文章有任何異議,那麼請在文章評論處寫上你的評論。
如果您覺得這個文章有意思,那麼請分享並轉發,或者也可以關注一下表示您對我們文章的認可與鼓勵。
願大家都能在程式設計這條路,越走越遠。