# WebAssembly 入門導覽 ## 關於這篇文章 這是一篇粗淺的 WebAssembly 介紹文,整理了關於 WebAssembly 的各種介紹、討論,許多技術細節還需要研究原文與案例說明。 ## WebAssembly 是什麼 來自 webassembly.org 的解釋: > WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. > Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications. 可以先簡單理解成一種能在網頁高效能運行的程式 ## 如何使用他 WASM 是寫給機器看的,人類難以理解。我們通常無法直接編寫它,而是使用 C/C++, Rust 等等的高階語言編寫程式,再經由其他工具編譯成 WASM。 WASM 長這樣: add.wasm (binary format) ```wasm 0061 736d ; WASM_BINARY_MAGIC 0100 0000 ; WASM_BINARY_VERSION 01 ; section code 00 ; section size 01 ; num types 60 ; func 02 ; num params 7f ; i32 7f ; i32 01 ; num results 7f ; i32 07 ; FIXUP section size 03 ; section code 00 ; section size (guess) 01 ; num functions 00 ; function 0 signature index 02 ; FIXUP section size 07 ; section code 00 ; section size (guess) 01 ; num exports 03 ; string length 6164 64 ; export name "add" 00 ; export kind 00 ; export func index 07 ; FIXUP section size 0a ; section code 00 ; section size 01 ; num functions 00 ; func body size 00 ; local decl count 20 ; local.get 00 ; local index 20 ; local.get 01 ; local index 6a ; i32.add 0b ; end 07 ; FIXUP func body size 09 ; FIXUP section size ``` add.wat (text format) ```wasm (module (func $add (param $lhs i32) (param $rhs i32) (result i32) local.get $lhs local.get $rhs i32.add) (export "add" (func $add)) ) ``` ## WASM 可以、適合做什麼? - 需要繁重計算的時候,例如繪圖、加解密、遊戲、模擬器等等: 1. [Blockchain](https://www.codementor.io/@beber89/webassembly-to-run-blockchain-using-go-yuw6f9u7m) 2. [2D/3D 繪圖計算(Unity)](https://beta.unity3d.com/jonas/AngryBots/) 3. [繪圖計算(Figma)](https://www.figma.com/blog/webassembly-cut-figmas-load-time-by-3x/) 4. [PS 模擬器](https://js-emulators.github.io/wasmpsx/) 5. [Diablo 1](https://d07riv.github.io/diabloweb/) 6. [更多 Use-cases](https://webassembly.org/docs/use-cases/) ## 不行、不適合做什麼? - 直接操作 DOM (這個未來可能可以) - IO - Networking(HTTP/TCP/TLS) - [取代 JavaScript](https://webassembly.org/docs/faq/#is-webassembly-trying-to-replace-javascript) - JS 就能處理的簡單計算 以上那些不行的操作都必須經由 JS import 進 WASM 才可以。 以下例子是將包裝好的 console.log function 匯入 WASM: ```js const runWasm = async () => { // Instantiate our wasm module // And pass in a wasm module const wasmModule = await wasmBrowserInstantiate("./index.wasm", { index: { // Import a function named consoleLog consoleLog: (value) => console.log(value), }, }); }; ``` 部分語言也有包裝各種 Web api 的套件可使用: 1. [Rust 操作 DOM - sledgehammer](https://github.com/Demonthos/sledgehammer) 2. [Rust - wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) 3. [Go - syscall/js](https://pkg.go.dev/syscall/js) WASM 的存在並非用來取代 JS,而是輔助 JS 與網頁突破在效能上的瓶頸,使用之前要思考是否真的有需要使用。 ## 可以用什麼語言開發? - C/C++ - Rust - AssemblyScript (a TypeScript-like syntax) - C# - Dart - Via Flutter (preview) - F# - Go - Kotlin - Swift - D - Pascal - Zig - Grain ## WASM 安全嗎? 網路上的陌生 WASM 模組會不會傷害我的電腦? WASM 運行在一個沙盒環境,基本上它只能乖乖在裡面用我們提供給他的東西做他自己的事,不會影響到你的電腦。 詳細請見: https://webassembly.org/docs/security/ ## 實戰案例 - 使用 GO 編譯一個 WASM-AES 解密模組 1. [Install Golang](https://go.dev/doc/install) 2. 範例檔案結構: ``` .vscode/ setting.json task.json src/ const.go handler.go main.go util.go static/ main.wasm 由指令產生 wasm_exec.js 由指令產生 go.mod index.html ``` 3. vscode setting & task setting.json ```json { "go.toolsEnvVars": { "GOOS": "js", "GOARCH": "wasm" }, "go.buildTags": "js,wasm" } ``` task.json ```json { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "build-wasm", "type": "shell", "windows": { "command": "go build -o .\\static\\main.wasm .\\src\\", "options": { "env": { "GOOS": "js", "GOARCH": "wasm" }, "cwd": "${workspaceFolder}" } }, "group": "build" } ] } ``` 4. 建立 go.mod ``` module my-aes-wasm go 1.18 ``` 5. src main.go ```go package main import ( "fmt" "syscall/js" ) func main() { // 在全域變數設定 decrypt 與 encrypt 兩個 function js.Global().Set("decrypt", js.FuncOf(decrypt)) js.Global().Set("encrypt", js.FuncOf(encrypt)) // 成功讀取後會顯示 WebAssembly is ready! fmt.Println("WebAssembly is ready!") // block the main thread forever select {} } ``` handler.go ```go package main import ( "fmt" "syscall/js" ) func encrypt(this js.Value, args []js.Value) any { plain := args[0].String() encrypted, err := AESEncryptByECB(plain) if err != nil { fmt.Printf("%+v\n", err) return nil } return encrypted } func decrypt(this js.Value, args []js.Value) any { cipher := args[0].String() decrypted, err := AESDecryptByECB(cipher) if err != nil { fmt.Printf("%+v\n", err) return nil } return decrypted } ``` util.go ```go package main import ( "bytes" "crypto/aes" "encoding/base64" "errors" ) func AESEncryptByECB(plain string) (string, error) { key := _Key if len(key) != 32 { return "", errors.New("key len must be 32") } originByte := []byte(plain) keyByte := []byte(key) block, _ := aes.NewCipher(keyByte) blockSize := block.BlockSize() originByte = PKCS7Padding(originByte, blockSize) encryptResult := make([]byte, len(originByte)) for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize { block.Encrypt(encryptResult[bs:be], originByte[bs:be]) } return base64.StdEncoding.EncodeToString(encryptResult), nil } func PKCS7Padding(originByte []byte, blockSize int) []byte { padding := blockSize - len(originByte)%blockSize padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(originByte, padText...) } func AESDecryptByECB(cipher string) (string, error) { key := _Key if len(key) != 32 { return "", errors.New("key len must be 32") } originByte, _ := base64.StdEncoding.DecodeString(cipher) keyByte := []byte(key) block, _ := aes.NewCipher(keyByte) blockSize := block.BlockSize() decrypted := make([]byte, len(originByte)) for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize { block.Decrypt(decrypted[bs:be], originByte[bs:be]) } return string(PKCS7UNPadding(decrypted)), nil } func PKCS7UNPadding(originDataByte []byte) []byte { length := len(originDataByte) unpadding := int(originDataByte[length-1]) return originDataByte[:(length - unpadding)] } ``` const.go ```go package main import ( "crypto/sha256" ) var ( _Secret string = "secret" _Key []byte = SHA256(_Secret) // AES-256 ) // SHA256 returns the SHA256 hash byte slice of the string s. func SHA256(s string) []byte { h := sha256.New() h.Write([]byte(s)) return h.Sum(nil) } ``` 6. 產生 wasm 與 wasm_exec.js - wasm 1. 按下 F1 輸入 Tasks: Run Task 2. 選擇 build-wasm - wasm_exec.js 1. `cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .` 2. 將複製出來的檔案放進 static 裡面 3. 建立 index.html, 測試剛剛產生的 wasm 模組 index.html ```html <html> <head> <meta charset="utf-8" /> <!-- 引入 wasm_exec.js, 將 wasm 與 js 連接起來 --> <script src="static/wasm_exec.js"></script> <script> async function loadWASM() { const go = new Go(); const res = await WebAssembly.instantiateStreaming( fetch("static/main.wasm"), go.importObject ); go.run(res.instance); } loadWASM(); </script> </head> <body></body> </html> ``` 7. 開啟 index.html, 可用[Live server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) 8. 成功的話,開啟 console 會看到 WebAssembly is ready!,並且在全域範圍可以找到 encrypt 與 decrypt 兩個 function ## 個人學習心得 & 困難點 在製作 wasm AES 模組時,我嘗試過 [C](https://hackmd.io/SSWlmo25T7CK3MUkBZoXoA)、[AssemblyScript](https://hackmd.io/A_oxvvTLQIWc0aTNH3iuiA)以及 Rust,來編寫模組,但最後都放棄了。我認為主要的原因來自於語言與工具鍊的不熟悉,耗費大量時間在學習語言與 debug,而這都沒辦法在短時間內完成。 而最後為什麼選擇 GO 呢? 首先它的環境架設非常簡單,而且也內建了編譯 wasm 的工具,不需要安裝額外的工具增加因開發環境導致出錯的機會。 語言本身也不太困難,甚至提供現有的 js glue 檔案(wasm_exec.js)串接 wasm,真的是非常懶人。 ## 網路相關資源? ### 效能實測 [webassembly-is-fast-a-real-world-benchmark-of-webassembly-vs-es6](https://medium.com/@torch2424/webassembly-is-fast-a-real-world-benchmark-of-webassembly-vs-es6-d85a23f8e193) [How We Used WebAssembly To Speed Up Our Web App By 20X (Case Study)](https://www.smashingmagazine.com/2019/04/webassembly-speed-web-app/#:~:text=So%20far%2C%20WebAssembly%20has%20been,flexible%20language%20for%20serverless%20computing.) ### 除了網頁,WASM 也想跨平台執行 - WASI [Standardizing WASI: A system interface to run WebAssembly outside the web](https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/) [Welcome to WASI!](https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-intro.md) [(Video) Bringing WebAssembly outside the web with WASI by Lin Clark](https://www.youtube.com/watch?v=fh9WXPu0hw8) ### 曾經也被認為可以一招打天下的技術 - Flash & Java Applets [Is WebAssembly the return of Java Applets & Flash?](https://steveklabnik.com/writing/is-webassembly-the-return-of-java-applets-flash) [What Was Flash & What Happened to It](https://www.lifewire.com/what-happened-to-flash-2617986) ### 一些應用 [【WebAssembly 應用案例直擊】它們都在用 WebAssembly](https://www.ithome.com.tw/news/120813) [Github WebAssembly demo](https://github.com/topics/webassembly-demo) [madewithwebassembly](https://madewithwebassembly.com/) [Adobe flash player](https://github.com/ruffle-rs/ruffle) [wasm-tetris](https://github.com/liona24/wasm-tetris) [OS In browser](https://dustinbrett.com/) [Figma](https://www.figma.com/) ### 其他 [Github - WebAssembly](https://github.com/WebAssembly) [Reddit - WebAssembly](https://www.reddit.com/r/WebAssembly/) [圖文並茂的 WASM 介紹](https://www.smashingmagazine.com/2017/05/abridged-cartoon-introduction-webassembly/?utm_source=frontendfocus&utm_medium=email) [你可能不需要 WASM - Why I Think WebAssembly is a Bad Idea](https://www.chrislynch.link/tech/why-i-think-webassembly-is-a-bad-idea) [What is WebAssembly? By Some of its Creators](https://www.youtube.com/watch?v=fvkIQfRZ-Y0) [webassembly-guide](https://www.webassembly.guide/webassembly-guide/) ## WebAssembly's Future https://github.com/WebAssembly/proposals https://webassembly.org/docs/high-level-goals/