# 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/