- 原文地址:Creating and working with WebAssembly modules
- 原文做者:本文已獲做者 Lin Clark 受權
- 譯文出自:掘金翻譯計劃
- 譯者: xilihuasi
- 校對者:Tina92、zhouzihanntu
這是 WebAssembly 系列文章的第四部分。若是你還沒閱讀過前面的文章,咱們建議你從頭開始。javascript
WebAssembly 是一種不一樣於 JavaScript 的在 web 頁面上運行程序語言的方式。之前當你想在瀏覽器上運行代碼來實現 web 頁面不一樣部分的交互時,你惟一的選擇就是 JavaScript。前端
所以當人們談論 WebAssembly 運行迅速時,合理的比較對象就是 JavaScript。但這並不意味着你必須在 WebAssembly 和 JavaScript 兩者中選擇一個使用。java
事實上咱們但願開發者在同一應用中同時使用 WebAssembly 和 JavaScript。即便你不親自寫 WebAssembly 代碼,你也可使用它。react
WebAssembly 組件定義的函數能夠在 JavaScript 中使用。所以,就像如今你能夠從 npm 上下載一個 lodash 這樣的組件而且根據它的 API 調用方法同樣,在將來你一樣能夠下載 WebAssembly 組件。android
那麼讓咱們看看如何建立 WebAssembly 組件,以及如何在 JavaScript 中使用這些組件吧。webpack
在上一篇關於彙編的文章裏,我談到過編譯器怎麼提取高級程序語言而且把它們翻譯成機器碼。ios
WebAssembly 對應這張圖片的哪一個部分?git
你可能認爲它只不過是又一個目標彙編語言。某種程度上是對的,不一樣之處在於那些語言(x86,ARM)中每一個都對應一個特定的機器架構。github
當你經過 web 向用戶的機器上發送要執行的代碼時,你並不知道你的代碼將要在哪一種目標架構上運行。web
因此 WebAssembly 和其餘的彙編有些細微的差異。它是概念機的機器語言,而非真實的物理機。
正因如此,WebAssembly 指令有時也被稱爲虛擬指令。它們比 JavaScript 源碼有更直接的機器碼映射。它們表明一類能夠在常見的流行硬件上高效執行的指令集合。可是它們並不直接映射某一具體硬件的特定機器碼。
瀏覽器下載 WebAssembly 後,它就能從 WebAssembly 轉成目標機器的彙編碼。
LLVM 是當前對 WebAssembly 支持最好的編譯工具鏈。不少先後端編譯工具均可以嵌入 LLVM 中。
注:大部分 WebAssembly 組件開發者用 C 和 Rust 這樣的語言編寫代碼,而後編譯成 WebAssembly,但仍有其餘的方法來建立 WebAssembly 組件。好比,有一個實驗性的工具幫你使用 TypeScript 構建 WebAssembly 組件,或者你能夠直接在 WebAssembly 的文本表示上編碼。
好比說咱們想把 C 編譯成 WebAssembly。咱們可使用 clang 編譯器前端把 C 編譯成 LLVM 中介碼。一旦它處於 LLVM 的中間層,LLVM 編譯它,LLVM 就能夠展示一些性能優化。
要把 LLVM IR(中介碼)編譯成 WebAssembly,咱們須要一個後端支持。在 LLVM 項目中有一個這類後端正在開發中。這個後端項目已經接近完成而且應該很快就會定稿。然而,如今使用它還會有很多問題。
目前有一個稍微容易使用的工具叫 Emscripten。他有本身的後端,能夠經過編譯成其餘對象(稱爲 asm.js)而後再轉換成 WebAssembly 的方式來產生 WebAssembly。好像它底層仍舊使用 LLVM,所以你能夠在 Emscripten 中切換這兩種後端。
Emscripten 包含了許多附加工具和庫來支持移植整個 C/C++ 代碼庫,所以它更像一個 SDK 而非編譯器。舉個例子,系統開發人員習慣於有一個文件系統用來讀寫,因此 Emscripten 可使用 IndexedDB 模擬一個文件系統。
忽略你已經使用的工具鏈,最後獲得的結果就是一個後綴名爲 .wasm 的文件。下面我將着重解釋 .wasm 文件的結構。首先,咱們先看看怎樣在JS中使用 .wasm 文件。
這個 .wasm 文件是一個 WebAssembly 組件,它能夠在 JavaScript 中載入。在此情景下,載入過程稍微有些複雜。
functionfetchAndInstantiate(url, importObject) {
return fetch(url).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results =>
results.instance
);
}複製代碼
你能夠在咱們的文檔中深刻了解這部份內容。
咱們致力於讓這個過程變得更容易。咱們指望改進工具鏈,整合已存在的像 webpack 這樣的模塊打包工具以及相似 SystemJS 的動態加載器。咱們相信載入 WebAssembly 組件能夠像載入 JavaScript 組件同樣簡單。
不過,WebAssembly 組件和 JS 組件有一個顯著的區別。目前,WebAssembly 函數只能使用數字(整型或浮點型數字)做爲參數和返回值。
對於更加複雜的數據類型,如字符串,你必須使用 WebAssembly 組件存儲器。
像 C,C++,和 Rust 這些更高性能的語言傾向於手動管理內存。若是你大部分時間都在使用 JavaScript,也許對直接訪問存儲器的操做不熟悉。WebAssembly 組件存儲器模擬了你在這些語言中會看到的堆。
爲了實現這個功能,它使用了 JavaScript 中的類型化數組(ArrayBuffer)。類型化數組是存放字節的數組。數組的索引就是對應的存儲器地址。
若是想要在 JavaScript 和 WebAssembly 中傳遞字符串,你須要把這些字符轉換成他們的字符碼常量。而後把這些寫入存儲器陣列。既然索引是整數,那麼單個索引值就能夠傳入 WebAssembly 函數中。這樣字符串中第一個字符的索引就能夠被當成一個指針使用。
幾乎全部想要開發供 web 開發者使用的 WebAssembly 組件的開發者,都會爲組件建立一個包裝器。這樣以來,你做爲組件的消費者並不須要瞭解內存管理。
若是想了解更多的話,查看咱們關於使用 WebAssembly 內存的文檔。
若是你使用高級語言來編寫代碼而後把它編譯成 WebAssembly,你沒必要知道 WebAssembly 組件的結構。可是它能夠幫助你理解其基本原理。
若是你以前沒有了解這些基本原理,咱們建議你先閱讀 彙編文章 (part 3 of the series)。
下面是一個 C 函數,咱們將把它轉成 WebAssembly:
int add42(int num) {
return num + 42;
}複製代碼
你可使用 WASM Explorer 來編譯這個函數。
若是你打開 .wasm 文件(假設你的編輯器支持顯示),你將看到相似這樣的內容:
00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B複製代碼
這是組件的「二進制」表示法。我把二進制加上引號是由於它一般顯示的是十六進制符號,但這很容易轉換成二進制符號,或者人類可讀的格式。
舉個例子,下圖是 num + 42
的幾種表現形式。
若是你想知道的話,下圖是執行的一些指令說明。
你可能注意到了 add
操做並無說明他的值應該從哪裏來。這是由於 WebAssembly 是堆棧機的一個範例。這意味着一個操做所需的全部值在操做執行以前都在棧中排隊。
例如 add
這類的操做指導它們須要多少值。若是 add
須要兩個值,它將從棧頂取出兩個值。這意味着 add
指令能夠很短(單個字節),由於指令不須要指定源或者目的寄存器。這減小了 .wasm 文件的大小,也意味着下載的耗時更短。
即便 WebAssembly 就堆棧機而言是特定的,但那不是其在物理機上的工做方式。當瀏覽器把 WebAssembly 轉化成其運行機器上對應的機器碼時,將會用到寄存器。由於 WebAssembly 代碼不指定寄存器,因此瀏覽器在當前機器上能更靈活的去使用最佳寄存器分配。
除了 add42
函數自身,.wasm 文件還有其餘部分。那就是 sections。一些 sections 對任何組件都是必需的,而有一些是可選的。
必選項:
可選項:
更多關於 sections 的闡釋,這有一篇深度好文解釋這些 sections 如何運行。
如今你知道怎樣使用 WebAssembly 組件了,讓咱們看看爲何 WebAssembly 這麼快。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。