去年底,各主流瀏覽器都支援WebAssembly(Wasm)之後,WASM近來又有不少花邊話題,像是VIM、Windows 2000被編譯為WebAssembly,可在瀏覽器執行,而新出爐的Go 1.11也可將Go程式碼編譯為WebAssembly,種種現象都暗示著WebAssembly的生態系逐漸成形?

WebAssembly的崛起

自2008年瀏覽器紛紛加上JIT(Just-in-time),令執行效率獲得大幅提升,JavaScript應用越來越廣,然而,對於效率的要求並沒有停止,像是遊戲,就因為效能問題,無法很好地站上瀏覽器這個舞臺。

2012年Mozilla的Alon Zakai建立了Emscripten專案,可以將C/C++程式碼編譯為asm.js,這是個經過調整的JavaScript子集,基於JavaScript既有語法提供了兩種型態宣告,若JavaScript引擎發現是asm.js撰寫的程式碼,會調用WebGL透過GPU來執行,以獲得速度上的提升(可參考https://goo.gl/8eQ2Tc)。

談到創造JavaScript語言的替代品,各大廠都不遺餘力,像是Google的Dart,微軟的TypeScript等,各瀏覽器廠商後來認為,在效率上各自提出自己的解決方案並無好處,於是Chrome、Edge和Firefox等廠商,以及JavaScript創建者Brendan Eich,在2015年發起了W3C WebAssembly小組,共同推動新網頁格式標準WebAssembly,蘋果亦將之納入WebKit開發工作清單,一直到了2017年底,主流瀏覽器都已支援WebAssembly(參考https://goo.gl/LK9Fs5)。

官方推薦從C/C++編譯為WebAssembly,但WebAssembly規範可位元組碼(bytecode)格式,只要語言可編譯為WebAssembly位元組碼,就能在瀏覽器執行,Go語言就是例子,此外,還有Kotlin、Rust等語言,在〈Awesome WebAssembly Languages〉(https://goo.gl/5S2NuW)有個清單,列出可編譯為WebAssembly,或虛擬機支援WebAssembly的語言。

對於前端開發者而言,或許可注意AssemblyScript(https://goo.gl/HmwsLS),它的語法與TypeScript一致,雖然有更嚴格的型態限制,然而對於熟悉JavaScript的開發者而言,仍然是較易入門與編寫程式碼,並編譯為WebAssembly的語言選項。

顯然WebAssembly已形成工具生態系,就算在傳統前端開發也有發展,例如,Webpack獲得Mozilla Open Source Support(MOSS)贊助,實作WebAssembly支援,在〈WebAssembly現狀与實戰〉(https://goo.gl/t4AbFD)就談到,如何在Webpack設定,以便將AssemblyScript編譯為WebAssembly。

體驗WebAssembly

既然WebAssembly已經有其工具生態系,想認識它的方式就是實際體驗,流程上,具體來說就是:使用某個語言撰寫程式、將原始碼編譯並儲存為wasm檔案、在瀏覽器中載入wasm檔案、使用WebAssembly JS API實例化模組。

先前談過,Emscripten可將C/C++編譯為asm.js,現在也能支援編譯為WebAssembly;若不想自行建構Emscripten,有些網站,像是WasmFiddle(https://goo.gl/RdUhmK)、WasmExplorer(https://goo.gl/nTAyNx)等,可讓開發者在網頁撰寫C/C++,經由線上編譯,提供wasm檔案的下載。

在瀏覽器中載入wasm檔案這方面,未來預計透過<script src="program.wasm" type="module" />來載入,在還沒有實現這項特性之前,則可以透過Fetch API取得wasm檔案;接下來是實例化模組,常見的例子是將取得的wasm檔案轉換為ArrayBuffer,再透過WebAssembly.compile編譯、WebAssembly.instantiate實例化等動作,而另一種方式,則是將取得的wasm檔案,直接傳入WebAssembly.instantiateStreaming。

例如,我們可以在WasmFiddle上,編譯底下這個C函式,下載WebAssembly存為add.wasm檔案,就可以使用mdn上的add.html(https://goo.gl/stF4hk)來執行add函式呼叫,取得結果並顯示在主控臺:

int add(int a, int b) {
return a + b;
}

想將JavaScript的函式匯入C/C++中呼叫,也是可行的,例如,以上範例當中,我們可加入void consoleLog(int n);宣告,add函式加入consoleLog(a + b);,在編譯為WebAssembly後,可在網頁中執行:

let importObj = {
env: {consoleLog: n => console.log(n)}
};
WebAssembly.instantiateStreaming(fetch('add.wasm'), importObj)
.then(obj => console.log(obj.instance.exports.add(1, 2)));

理解WebAssembly文字格式

wasm是以二進位格式儲存,閱讀或編寫都不容易,為此WebAssembly提供了易於閱讀、編寫的文字格式,例如,一個執行相加的add函式如下:

(module
(func (param $a i32) (param $b i32) (result i32)
get_local $a
get_local $b
i32.add))

WebAssembly用S表達式,可存為*.wat檔案,能透過The WebAssembly Binary Toolkit(WABT,https://goo.gl/1BS5AL)的wat2wasm編譯為二進位格式,而二進位格式也可用wasm2wat轉為文字。

我們可以將WebAssembly文字格式看成語言,典範則是堆疊導向(Stack-oriented),也就是各種操作都是在堆疊上進行,事實上,這也是我對WebAssembly投以興趣的原因,因為可以透過WebAssembly文字格式來熟悉堆疊導向的設計思路,以便未來還有機會實作語言的話,能以位元碼儲存編譯結果,無論是屆時是使用WebAssembly,或者是為了從土炮中獲得經驗而自創的位元碼。

就WebAssembly本身的應用,認識WebAssembly文字格式有實務的價值,亦即易於瞭解編譯出來的二進位格式做了哪些操作,必要時也可轉換為文字格式,部份修改內容後再重新編譯為二進位格式;方才談到的WasmFiddle、WasmExplorer,在編譯後,都會顯示對應的文字格式,而我們從WasmExplorer當中,還可直接修改文字格式的內容。

若要入門WebAssembly文字格式,有篇〈理解WebAssembly文本格式〉(https://goo.gl/q6uF1w)可參考,包含WebAssembly JS API的基本說明。

速度不是唯一的考量

起初WebAssembly是以速度為亮點,被認為是asm.js接班人,在2015年推出,相關工具鏈未成形之際,有不少媒體或評論自行腦補,以JavaScript殺手來稱呼,實際上,格式標準與語言並不能混為一談。

前些日子的話題,Windows 2000可執行於瀏覽器之中,著實令人驚訝,但是,就速度而言尚不理想,想展現JavaScript與WebAssembly的效能差異,也不是那麼簡單的一件事(甚至還會輸給JavaScript的情況https://goo.gl/bCdnDZ)。

就我個人而言,切入WebAssembly是為了熟悉堆疊導向,就前端而言,WebAssembly生態系確實是在蓬勃發展之中,更重要的是,各主流瀏覽器願意遵守規範,而關於各主流語言都加入支援,也許是速度之外更重要的考量,因為,這是個難得的景象,也表示主流語言生態系的開發者,未來都有機會參與瀏覽器這個平臺,這才是現階段WebAssembly,值得開發者投以關注的重要因素。

專欄作者

熱門新聞

Advertisement