以 JavaScript 建立 WebAssembly 模組的實體
WebAssembly 是在 Web 上執行程式碼的新方法。透過 WebAssembly,你可使用 C 或 C++ 等語言寫好模組後再用瀏覽器執行。
雖然這樣的模組還現階段無法自行執行,不過,等瀏覽器開始提供 ES 模組支援後,便可自動執行。一旦成真後,WebAssembly 模組應可如其他 ES 模組般載入,如:<script type=”module”>。
但以現在而言,你得使用 JavaScript 才能啟動 WebAssembly 模組 (module),這麼做會建立出一個模組實體(instance),之後 JavaScript 碼便可呼叫在該 WebAssembly 模組上的實體函式。
譬如,來看看 React 如何可以產生 WebAssembly 模組的 instance 吧(你也可觀賞這個影片,以了解 React 如何使用 WebAssembly)。
當使用者載入頁面,它會以同樣的方式啟動。
此時,瀏覽器會下載 JavaScript 檔,另外還會抓取一個 .wasm 檔案,其中含有二進位的 WebAssembly 程式碼。
我們需載入這些檔案內的程式碼才能執行 WebAssembly 模組。首先抓到的是 .js 檔,它會載入 React 內 JavaScript 的部分。此 JavaScript 則將建立一個 WebAssembly 模組的實體,即 reconciler。
它會呼叫 WebAssembly.instantiate. 來做這事。
進一步瞧瞧。我們發給 WebAssembly. Instantiate 的是從 .wasm 檔抓到的二元碼。
方法是先把二進位程式碼擷取到緩衝區內,再提供給 WebAssembly. Instantiate。
引擎便會把模組程式碼編譯為適合負責執行的機器的格式。
但最好不要在主執行緒上做。之前我曾說過,主執行緒得處理 JavaScript、DOM 和佈局,手上的事已經很多了,還是不要為了編譯模組而阻礙主執行緒的工作。因此,WebAssembly.instantiate 回傳的會是一個 promise 物件。
這種作法讓主執行緒可快快回去做手上的工作,而且也能讓主執行緒,等編譯器完成模組的編譯後,便會透過 promise 通知它。到時,此 promise 將發出一個實體。
但編譯好的模組並不是產生該實體所需的唯一要件。以我自己來說,我把這個模組想成一本說明書。
該實體則像是一個想參考那本書來製作物品的人。為了要做出物品,他還需要取得原料。
這就是我們還需要第二個 WebAssembly.instantiate 向量的原因,也就是匯入(imports)物件。
我把匯入物件看成一個原料盒,就像 IKEA 家具的組裝零件一樣。我們的instance 會按照說明書的引導,使用這些原料(imports)來製作物品。一般的說明手冊都會提出對於特定原料的要求,同樣的,每個模組也會需要特定的 imports 才能運作。
當你建立模組的實體時,你會傳給它帶有那些個別必備 import 的一個 imports 物件。這些 import 可能是以下四種之一:
- 值(values)
- 函式閉包(function closures)
- 記憶體(memory)
- 表格(tables)
值
Imports 物件可含 values。Values 基本上是全域變數(global variables)。因為 WebAssembly 目前只支持 integer 和 float 兩種值,故涵蓋在 imports 內的值須為此兩者之一。待 WebAssembly 加入對更多值的支援後,這限制也將隨之改變。
函式閉包
Imports 物件可含函式封包,也就是說,你可送入 JavaScript 函式,好讓WebAssembly 呼叫。
因為目前這個版本的 WebAssembly 還不允許直接呼叫 DOM 方法(methods),所以此功能特別管用。WebAssembly 已將直接存取 DOM 納入後續發展藍圖上,只是現階段還不支援。
目前的替代性方法是,送入一個能以你需要的方式與 DOM 互動的 JavaScript 函式,再讓 WebAssembly 呼叫該 JavaScript 函式。
記憶體
另一種 import 則是記憶體物件。透過此物件,WebAssembly 碼才能模擬人工記憶體管理。因為記憶體物件的概念不太容易理解,所以我會在下一篇文章提供更深入的說明。
表格
最後一種 import 是表格,也和安全有關。藉由表格,你可使用到函式指標(function pointer)。這同樣也有點複雜,我會用另一篇文章說明。
這些就是你可用來建立實體的四種 imports。
為了回傳實例,WebAssembly.instantiate 所傳回的 promise 必先解決。其中包含兩個各自獨立的元素:實例及已編譯的模組。
此編譯模組的一大優勢是,你可用同一模組快速產生其他的實體。你唯一要做的事就是把該模組當成源參數(source parameter)提交出去。模組本身不具任何狀態(state),狀態會附於實體後。換言之,不同實體可分享同一編譯模組的程式碼。
到此,你的 instance 已經準備就緒。不但有了說明手冊,該手冊也具編譯好的程式碼,以及所有必備的 imports。你現在可呼叫其方法了。
我在另外兩篇文章中將進一步討論記憶體匯入(memory import)和表格匯入(table import)。
