WebAssembly 是二進位格式,然而,提供了文字格式,便於人類撰寫與閱讀,在〈從 C 到 WebAssembly〉中看過 C 編譯為 WebAssembly,後者對應的文字格式,如果接觸過 Lisp,可能會覺得有點熟悉,因為 WebAssembly 是基於 S-expression,每個文字格式檔案,就是一個巨大的 S-expression。
在線上工具部份,WasmFiddle 可以將 C 編譯為 WebAssembly,而且會顯示對應的文字格式,然而沒辦法編輯文字格式,另一個線上工具 WasmExplorer,可以編輯 C,也可以編輯 WebAssembly 文字格式(然而只提供編譯結果下載,沒有線上編寫 JavaScript 的介面)。
在上圖中,Wat 窗格是編寫 WebAssembly 文字格式的地方,WebAssembly 文字格式的副檔名是 .wat,因此常簡稱為 Wat,之後的文件就也這麼稱呼好了。
在 Wat 窗格編寫完程式,按下「ASSEMBLE」按鈕,就可以編譯為二進位格式,按下「DOWNLOAD」可以下載 .wasm 檔案(至於最右邊的窗格,來自於 Firefox 的 WebAssembly 引擎產生的 .x86 檔案,為WebAssembly 反組譯後的輸出)。
雖然還沒正式談過 Wat 的撰寫,不過,從 module
、func
、param
、result
、export
等關鍵字,大致上可以看出來,上圖 Wat 窗格中撰寫的程式碼中,定義了一個模組,一個 add
函式並將之匯出:
(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
get_local $lhs
get_local $rhs
i32.add)
(export "add" (func $add))
)
get_local
取得指定參數的值,並放至堆疊之中,因為要進行加法,因此先取得左運算元($lhs
表示 Left Hand Side)置入堆疊,取得右運算元($rhs
表示 Right Hand Side)置入堆疊,它們都是 i32
,也就是 32 位整數,因此進行 i32.add
,也就是 32 位元整數加法,這會從堆疊頂端分別取出兩個值,相加後的結果置回堆疊,作為函式的呼叫結果。
下載 .wasm 檔案之後,想要呼叫這個模組匯出的 add
函式,可以使用〈從 C 到 WebAssembly〉第一個 HTML 中撰寫的 JavaScript:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script>
WebAssembly.instantiateStreaming(fetch('program.wasm'))
.then(prog => {
console.log(prog.instance.exports.add(1, 2));
});
</script>
</body>
</html>
如果不想要在線上撰寫、編譯 Wat,可以使用 The WebAssembly Binary Toolkit,簡稱 wabt,依其中文件完成編譯之後,基本使用方式是,使用 wat2wasm
將 Wat 編譯為 .wasm,或者使用 wasm2wat
將 .wasm 反編譯為 .wat。
如果你使用 Visual Studio Code,可以安裝 Dmitriy Tsvettsikh 的 WebAssembly Toolkit for VSCode 外掛,它提供了編輯器語法著色,可以在 .wat 上按右鍵另存 .wasm 檔案,或者在 .wasm 上按右鍵,另存或顯示文字格式。
上圖中,我使用的是 Node.js 並配合簡單的 http-server
。
如果要在 WebAssembly 文字格式中,匯入、呼叫 JavaScript 環境匯入之函式,可以使用 import
。例如,底下是個等效於〈從 C 到 WebAssembly〉中,匯入 log
的 Wat 寫法:
(module
(import "env" "log" (func $log (param i32)))
(func $add (param $lhs i32) (param $rhs i32) (result i32)
(local $result i32)
get_local $lhs
get_local $rhs
i32.add
tee_local $result
call $log
get_local $result)
(export "add" (func $add))
)
這當中包含了更多 Wat 的細節,之後會再依序說明。
談到 Node.js,它現在也支援 WebAssembly,以 LTS v8.11.4 為例,可以撰寫以下的 .js:
const fs = require('fs');
const buffer = fs.readFileSync(('program.wasm'));
WebAssembly.instantiate(buffer)
.then(prog => {
console.log(prog.instance.exports.add(1, 2));
});
然後執行這個 .js 檔案就會看到 3 的結果輸出。