title: [A crash course in WebAssembly] 創作並使用 WebAssembly 模組
date: 2018-3-22 16:42:00
categories: 翻譯
tags: WebAssembly
source: 原文地址
auther: Lin Clark
[A crash course in WebAssembly] 創作並使用 WebAssembly 模組
這是WebAssembly系列文章的第四部分。如果您還沒有閱讀其他文章,我們建議您從頭開始。
WebAssembly是一種在網頁上執行JavaScript以外的程式語言的方法。在過去,當您想在瀏覽器中執行程式碼以與網頁的不同部分進行互動時,您唯一的選擇就是JavaScript。
所以當人們談論WebAssembly變得越來越快時,比較的物件通常是在說JavaScript。但是,這並不意味著它是一種或者兩種情況————您使用的是WebAssembly,或者使用JavaScript。
事實上,我們預計開發人員將在同一個應用程式中同時使用WebAssembly和JavaScript。即使你自己不寫WebAssembly,你也可以利用它。
WebAssembly定義的模組函式可以讓JavaScript呼叫,未來有一天你可以像從npm上下載loadsh之類的各種模組一樣下載WebAssembly模組並呼叫它們的api。
so,讓我們來看看如何建立一個WebAssembly模組然後在JS中使用。
Where does WebAssembly fits
在上一篇講assembly的文章中,我們談到了編譯器接受講高階程式語言並將其翻譯為機器碼
那麼WebAssembly因該出現在這張圖的什麼位置呢?
你可能會覺得只是增加了一個目標組合語言(與x86/ARM類似)。除了不針對某個架構外,基本上是對的。
當您通過Web在使用者計算機上傳遞程式碼時,您不知道程式碼將在哪個目標架構上執行。
所以WebAssembly與其他型別的assembly有點不同。它是概念機器的機器語言,而不是實際的物理機器。
因此,WebAssembly指令有時被稱為虛擬指令。它們比JavaScript程式碼更直接地對映到機器碼。它們代表了可以在普通流行硬體中高效完成的一種交集。但它們不是直接對映到特定硬體的特定機器碼。
瀏覽器下載WebAssembly。然後,它能夠快速將WebAssembly翻譯為目標機器的彙編程式碼。
Compiling to .wasm
目前對WebAssembly支援最多的編譯器工具鏈稱為LLVM。有許多不同的前端和後端可以插入到LLVM中。
note:大多數WebAssembly模組開發人員將使用C和Rust等語言進行編碼,然後編譯為WebAssembly,但還有其他方法可以建立WebAssembly模組。例如,有一個實驗工具可以幫助您使用TypeScript構建WebAssembly模組,或者您可以直接編寫WebAssembly的文字表示形式。
假設我們想從C到WebAssembly。我們可以使用clang
前端從C到LLVM中間表示。一旦它在LLVM的IR中,LLVM就會理解它,所以LLVM可以執行一些優化。
要從LLVM的IR(中間表示)轉到WebAssembly,我們需要一個後端。目前LLVM專案正在進行中。這個後端重要的部分,會盡快完善。目前來說使用WebAssembly可能會非常棘手。
還有另一個名為Emscripten的工具,目前使用起來更容易一些。它有自己的後端,可以通過編譯到另一個目標(稱為asm.js),然後將其轉換為WebAssembly來生成WebAssembly。不過,它使用LLVM,因此你可以在Emscripten的兩個後端之間切換。
!()[https://hacks.mozilla.org/files/2017/02/04-03-toolchain07-768×631.png]
Emscripten包含許多額外的工具和庫,允許移植整個 C/C++ 程式碼庫,因此它比編譯器更像是軟體開發工具包(SDK)。例如,系統開發人員習慣於擁有可以讀取和寫入的檔案系統,而Emscripten使用IndexedDB模擬檔案系統。
無論您使用的工具鏈如何,最終結果都是以.wasm結尾的檔案。我將在下面詳細解釋.wasm檔案的結構。首先,讓我們看看如何在JS中使用它。
Loading a .wasm module in JavaScript
.wasm檔案就是WebAssembly模組,可以使用JavaScript載入。截至目前,載入過程有點複雜。
function fetchAndInstantiate(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模組的記憶體。
如果你主要使用JavaScript,對直接訪問記憶體不是很熟悉。C/C++和Rust等效能更高的語言往往具有手動記憶體管理功能。 WebAssembly模組的記憶體模擬了您在這些語言的堆(heap)。
為此,它使用JavaScript中稱為ArrayBuffer的東西。陣列緩衝區是一個位元組陣列。陣列的索引則作為記憶體地址。
如果你想在JavaScript和WebAssembly之間傳遞一個字串,你可以將這些字元轉換為它們的字元程式碼。然後你就可以將它寫入陣列緩衝區(memory array)。由於索引是整數,所以可以將索引傳遞給WebAssembly函式。因此,字串的第一個字元的索引可以用作指標。
任何開發WebAssembly模組以供Web開發人員使用的人都可能會為該模組建立一個包裝。作為模組的使用者,您就能夠不需要了解記憶體管理。
如果您想了解更多資訊,請檢視關於使用WebAssembly記憶體的文件。
.wasm 檔案的結構
如果您使用更高階別的語言編寫程式碼,然後將其編譯為WebAssembly,則無需知道WebAssembly模組的結構。但它可以幫助你理解基礎。
如果你還沒有準備好,我們建議閱讀關於裝配的文章(本系列的第3部分)。
這是一個我們將翻譯為WebAssembly的C函式:
int add42(int num) {
return num + 42;
}
複製程式碼
您可以嘗試使用WASM資源管理器來編譯此功能。
如果你開啟.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
複製程式碼
這是“二進位制(binary)”表示的模組。我將引號放在二進位制檔案中,因為它通常以十六進位制表示法顯示,但可以很容易地轉換為二進位制符號或可讀格式。
例如,這裡是num + 42
的樣子:
程式碼如何工作:一個堆疊機器 a stack machine
以防萬一您想知道,那麼這些會說明它們做了什麼
您可能已經注意到,add
操作沒有說明其值應來自哪裡。這是因為WebAssembly就像一個堆疊機器。這意味著在執行操作之前,操作所需的所有值都在堆疊中排隊。
像add
操作知道他們需要多少個值。由於add需要兩個,因此它會從堆疊的頂部取兩個值。這意味著add指令可以很短(一個位元組),因為指令不需要指定源暫存器或目標暫存器。這會減小.wasm檔案的大小,從而減少它的下載時間。
儘管WebAssembly是根據堆疊計算機指定的,但這並不是它在物理機器上的工作方式。當瀏覽器將WebAssembly轉換為正在執行瀏覽器的機器的機器碼時,它將使用暫存器。由於WebAssembly程式碼不指定暫存器,因此可以使瀏覽器更靈活地為該機器分配暫存器,達到最佳的分配方案。
模組部分
除了add42
函式本身,.wasm檔案中還有其他部分。這些被稱為sections。某些sections對於任何模組都是必需的,有些是可選的。
必須的:
-
型別Type
包含此模組中定義的函式的函式簽名和任何匯入的函式。 -
函式Function
為此模組中定義的每個函式提供索引。 -
程式碼Code
此模組中每個函式的實際函式體。
可選的:
-
Export
使函式,記憶體,表和全域性變數對其他WebAssembly模組和JavaScript可用。這允許單獨編譯的模組動態連結在一起。這是Web元件版的.dll。 -
Import
指定要從其他WebAssembly模組或JavaScript匯入的函式,記憶體,表(tables)和全域性變數。 -
Start
載入WebAssembly模組時會自動執行的函式(基本上類似於main函式)。 -
Global
為模組宣告全域性變數。 -
Memory
定義此模組將使用的記憶體。 -
Table
使對映到WebAssembly模組之外的值成為可能,例如JavaScript物件。
這對允許間接函式呼叫特別有用。 -
Data
初始化匯入的或本地的記憶體。 -
Element
初始化匯入的或本地的表。
有關章節的更多資訊,請參閱sections的工作原理。
coming up next
現在您已經知道如何使用WebAssembly模組,讓我們來看看為什麼WebAssembly速度很快。