WebAssembly 常見引擎簡介

1. 前言

在本文中,我們將討論驅動 WebAssembly 程序運行的核心組件——引擎。首先,本文將簡要介紹一個語言的引擎包括哪些主要組成部分,它們如何配合完成工作,嘗試構建一個概念模型。之後,就幾款社區流行的開源引擎,分別介紹各自的特點。

2. 引擎通用架構

在這裏,我們要談論的引擎總是與某種語言綁定,也可稱之爲虛擬機。那麼一個引擎如何驅動一段程序運行,並得到結果呢?

第一,我們需要將程序文件加載到內存中,並解析符號,將其中的符號轉化成具體的、可訪問的內存地址。這一步需要一個加載器和一個鏈接器;

第二,我們需要執行目標程序,完成其中的每一條指令。如果是 JavaScript 這類腳本程序,我們還需要一個編譯組件,將程序文本編譯爲指令序列。面對指令序列,引擎可以解釋執行,也可以進一步編譯爲物理機器的可執行指令後執行。這一步需要一個能夠解釋或者編譯執行的執行器;

第三,目標程序可能是單線程執行,也可能是併發執行,爲了支持併發,要求存在一種調度機制來管理線程。這一步需要一個線程調度器;

第四,一般的程序總是會要求動態內存的分配,用於存儲動態數據,那麼就要有組件負責分配內存。考慮到內存資源的有限性,不能無限制增長,那麼就要有組件負責回收內存。這一步需要一個內存管理器;

最後,目標程序可能要求獲得各種外部資源,如訪問文件系統、監聽網絡端口等,引擎必須提供訪問這些資源的通道。這一步則需要語言擴展或者外部訪問接口。

圖 1. 引擎的概念模型

有了這幾個組件,一個引擎就可以順利驅動程序運行了。接下來,結合 WebAssembly 的特點,我們一起看一看市面上流行的幾款 wasm 引擎。

3. 常見的 WebAssembly 引擎

在這一部分中,我們將着重介紹幾個活躍在 WebAssembly 社區的引擎。這些項目都有着比較廣泛的使用羣體,在不同的場景下爲用戶創造了價值。接下來,我們將從項目背景、顯著特徵、性能表現等方面介紹以下幾款引擎。其中,我們會更加深入地討論 wasmtime 和 wasm3 的結構和設計。

3.1 wasmtime

wasmtime 是非盈利組織——字節碼聯盟(Bytecode Alliance)旗下的 WebAssembly 引擎,使用 Rust 語言編寫開發,是一種高性能編譯型 wasm 引擎。

3.1.1 結構特點

如圖 2 所示,wasmtime 總體上可以分爲編譯、運行時和工具三部分。

圖 2. wasmtime 的總體結構

編譯

這一部分包含 4 個組件,從上到下分別是wasm-environwasm-craneliftwasm-jitwasm-obj,這個順序在一定程度上反映了 wasmtime 完成編譯的時序。

從內部視角來看,wasm-environ是編譯的入口,但實際上wasm-cranelift會負責執行函數級別的即時編譯,爲每一個函數生成 JIT 代碼。得到 JIT 代碼之後,需要存儲在可執行內存區域才能被實際執行,這一步依賴wasm-jit對引擎內部可執行內存的管理。另外,wasm-obj會根據編譯過程中產生的各類信息,生成 ELF(Executable and Linkage Format)映像,其中包括所有的編譯得到的函數以及跳板(Trampoline)函數、用於鏈接階段的重定位記錄等內容。如果 wasm 模塊中存在 DWARF 信息,則會被寫入對應模塊 ELF 映像的.dwarf段。

可見,在 wasmtime 的世界中,一個 wasm 模塊被加載到內存並編譯之後,其內存映像跟一個普通的 ELF 文件非常相似。其中,所有的 wasm 函數都會被當作原生函數來完成鏈接和調用。這也是爲什麼 wasmtime 可以藉助 lldb 調試 wasm 程序(關於 WebAssembly 調試,可以參考第十章)。

運行時

這一部分的主體就是wasm-runtime,它將維護 wasm 模塊在運行時的各類數據結構,爲程序運行提供必要的支持。wasm-runtime組件負責維護兩類重要的實體:StoreInstanceHandle,見表 1。

I74HDY

表 1. Store 和 InstanceHandle

從圖 3 中可以大致瞭解這幾類實體之間的關聯。其中,Engine可認爲是 wasmtime 作爲運行時的實例,可被視作頂級上下文或根上下文,通常情況下單個進程內部只會創建一個 Engine,而每一個Engine內部可以創建多個Store

圖 3. wasm-runtime 的關鍵數據結構

wasmtime-runtime之外,運行時部分還存在組件wasmtime-fiber用於支持異步功能,主要處理棧的切換。

工具

工具部分包含以下三個子模塊:

由上文可知,wasmtime 引入了 Cranelift 作爲編譯器後端,在將 wasm 代碼轉譯成 Cranelift 中間表示(IR)之後,提前或者在運行時生成對應平臺的可執行指令序列。因此,相對於使用解釋器模式的引擎,其運行速度要更快,wasmtime 在 CoreMark Benchmark 上的實驗性能達到了 wasm3 的 4 倍。

3.1.2 項目現狀

作爲 Bytecode Alliance 推出的拳頭項目之一,wasmtime 對於 WebAssembly 相關標準支持的完整度非常高。它支持了標準的 WASI,實現了標準的 wasm c-api,並緊密跟蹤 WebAssembly 核心特性。截止本文寫作時間,wasmtime 不僅實現了 Fixed-Width SIMD、Reference Types、Bulk Memory operations 等成熟提案,還支持 Tail-Call、Threads 和 Garbage Collection 等還處於標準實現階段的提案。

雖然 wasmtime 使用 Rust 語言開發完成,但是爲了在不同的宿主語言中使用,wasmtime 團隊提供了 C/C++、Python、.NET、Go 以及 Ruby 等語言接口,便於不同偏好與背景的開發者引入 wasmtime.

2022 年 9 月份,wasmtime 1.0 版本正式發佈。截止當時,接入 wasmtime 的組織包括:Shopify、Fastly、DFINITY、InfinyOn Cloud 以及 MicroSoft,主要應用於 Serverless區塊鏈等場景。根據 wasmtime 團隊報告,這些組織引入 WebAssembly 並切換到 wasmtime 之後,均在自身的業務中獲得良好的收益。如電商公司 Shopify 的相關業務就因此獲得平均 50% 的執行性能提升。

3.2 wasm3

wasm3 是一款基於解釋器執行的輕量級 WebAssembly 引擎,使用 C 語言編寫開發,擁有比較完善的 wasm 運行時系統,主要貢獻者包括烏克蘭開發者 Volodymyr Shymanskyy 等人。

3.2.1 結構特點

wasm3 最大特點就是依靠純解釋器執行所有的 WebAssembly 指令,沒有引入 JIT/AOT 編譯。根據 Benchmark 數據,自 wasm3 出現在社區並逐漸成熟之後,在相當一段長的時間內,wasm3 都是運行速度最快的解釋型引擎。

這其中,一個關鍵的設計就是指令線索化(threaded code) 。與平凡的 switch-case 模式不同,線索化的解釋器並不存在一個外層的控制結構。相反地,線索化指令總是會在自身解釋程序的最後位置進行對下一條指令的調用,從而 “自驅動” 地執行所有指令。也正是由於指令解釋函數總是以對下一條指令的調用結束,編譯器可以對大部分的指令操作進行尾調用(tail-call)優化,以減少函數調用棧幀的壓入和彈出操作。

另外一個關鍵設計是寄存器指令轉譯。在此前的課程介紹中,我們瞭解到 WebAssembly 基於棧式機器進行設計。而根據已有的研究結論 [1],基於寄存器的指令運行速度會更快,因此 wasm3 也將 wasm 指令序列轉譯成了更直接和高效的寄存器指令序列(簡稱 M3 指令)。M3 指令的解釋函數,都有一個固定的、共同的函數簽名:

// 其中,mem表示函數所屬 wasm 模塊實例的線性內存,r0用於存放整型參數,fp0則用於存放浮點型參數,
// 其餘的pc、sp都是自解釋的命名,不贅述。
void * Operation_Whatever (pc_t pc, u64 * sp, u8 * mem, reg_t r0, f64 fp0);

wasm3 對一個 wasm 函數進行轉譯之後,生成的 M3 指令序列由指令的解釋函數地址、立即數、元數據(如函數所屬模塊實例指針)等組成。

舉個例子,如圖 4 所示,模塊 x 中存在一個名爲 func 的函數,其中有table.size的指令,這條轉譯之後將變爲函數op_tableSize的地址加上表示模塊指針的立即數組成的 M3 指令。由於 wasm3 目前還只支持單個 table,所以在執行時直接取出模塊實例唯一的 table 的容量值並存入r0寄存器中,然後調用下一條指令的解釋函數。這樣,我們也就大致瞭解了 wasm3 內部指令的執行模式。

圖 4. M3 指令形式

3.2.2 項目現狀

由於是純解釋執行, wasm3 可以在 iOS 等設備上運行。而這一點是其它純編譯型引擎無法做到的,這也賦予了它獨特的跨平臺優勢。再考慮到它的輕量特點——移動端二進制產物體積不到 70 KB,作爲移動端引擎,wasm3 可謂表現優異。

在宿主語言支持方面,wasm3 社區也有諸多亮點:開發者不僅能夠使用 C/C++ 低成本接入 wasm3 引擎,而且可以在 Python、Rust、GoLang、Zig、Perl 等編程語言中將 wasm3 作爲庫輕鬆引入。

另外,wasm3 通過支持標準的 WebAssembly System Interface(WASI)提供獲得系統能力的通道,因此能夠以獨立(Standalone)的方式運行 wasm 函數,即使它依賴於如printf之類的接口。比如,通過 CLI 工具執行 wasm 函數:

brew install wasm3
wasm3 --func function_name_to_run your_module.wasm

美中不足的是,目前 wasm3 對於社區標準規範的支持還有待完善,包括已被納入核心規範的多線性內存、引用類型、異常處理等提案。儘管如此,wasm3 的諸多特點,還是吸引了衆多對 WebAssembly 感興趣的項目團隊。目前,wasm3 已經被 wasmcloud、Siemens Opensource 等衆多項目引入,作爲 WebAssembly 的運行時。集團的抖音 APP 也已經接入了這款引擎,以支持相關業務。

相信隨着愈加廣泛的應用,wasm3 也會逐步完善,爲接入它的項目帶來更多的價值。

3.3 WasmEdge

WasmEdge 是一款由 CNCF(Cloud Native Computing Foundation,雲原生計算基金會)託管的 WebAssembly 引擎,其命名也在告訴我們:WasmEdge 主要面向邊緣計算、雲原生和去中心化應用。根據 IEEE 論文數據,WasmEdge 是當時運行性能最高的 WebAssembly 運行時。但根據最新的 2023 wasm runtime benchmark 數據 [2],wasmedge 的性能優勢已經有所削弱。

與 wasmtime 一樣,WasmEdge 也是一種編譯型的 wasm 引擎,可以按照 JIT/AOT 兩種模式對 wasm 指令進行編譯,並最終執行。不同之處在於,WasmEdge 使用 LLVM 作爲編譯器後端,利用了 LLVM 出色的優化編譯能力。因此,相比於使用 Cranelift 的 wasmtime,WasmEdge 生成的指令更優,執行速度更快。

除了卓越的性能表現之外,WasmEdge 的一個最爲顯著的特點就是社區提供豐富的擴展能力。比如,在雲原生的使用場景中,很多開發者使用 JavaScript 語言開發應用。爲此,WasmEdge 以 wasm 模塊形式(名爲 wasmedge_quickjs.wasm) 提供了 QuickJS 引擎能力, 以便在 wasm 環境中執行 JavaScript 代碼。在此基礎上,WasmEdge 社區提供了一攬子基於 JavaScript 的能力擴展:

在雲原生場景中,更多用戶可以無縫切換到 WasmEdge 並運行他們原有的 JavaScript 應用,顯著降低切換成本。

正是因爲 WasmEdge 的優勢,2022 年 11 月,Docker 正式宣佈集成 WasmEdge 以提供運行 WebAssembly 應用的能力。自此,WebAssembly 和 WasmEdge 都將在 Docker 容器化的開發中扮演更加重要的角色。

3.4 wasm-micro-runtime

wasm-micro-runtime 也簡稱爲 WAMR,與 wasmtime 一樣是隸屬於 Bytecode Alliance 的開源 WebAssembly 引擎項目,適用於嵌入式平臺、各類 IoT 設備、智能合約和雲原生等場景。名字中的 micro 也正是它的特點之一:WAMR 的二進制產物很輕量,純 AOT 配置的產物體積只有約 50KB,非常適合資源受限的宿主。

與上述三款引擎只支持解釋執行或編譯後執行不同,WAMR 同步支持解釋與編譯兩種方式執行 wasm 程序,因此兼有兩種執行方式低冷啓延遲、高峯值性能的優點。使用編譯模式時,宿主可以選擇使用 JIT 或 AOT 方式執行目標程序。與 WasmEdge 相同,WAMR 的編譯器也基於 LLVM 構建。根據官方數據,JIT 或 AOT 的執行方式可以得到接近原生的速度,表現十分亮眼。而援引最新的 2023 年 wasm runtime 的性能測試數據,wamr 是運行速度最快的引擎。可見,wamr 在 2022 年度完成了行之有效的優化。

作爲一款成熟的、可用於生產環境的 WebAssembly 引擎,WAMR 對社區標準支持的完整度也非常高:除了 MVP 中的特性全部支持之外,對 Post-MVP 中的 Fixed-Width SIMD、引用類型、共享線性內存、線程等提案的實現也都已正式上線。不過,對於 JS-API 中的一些接口,如 Memory.grow、Table.grow 等接口還未支持。

但是,這並不阻礙 WAMR 爲非 JavaScript 環境的 WebAssembly 應用服務,這也是它主要面向的場景。目前,WAMR 已經被 Hyperledger Private Data Objects、Inclavare Containers、Fassm、Waft 等項目使用。

3.5 V8

以上介紹的都是純粹的 WebAssembly 引擎,專用於 wasm 程序的執行,通常在 Standalone 的場景中使用。但考慮到 WebAssembly 由四大瀏覽器廠商聯合推出支持,最初在 Web 環境中使用,JavaScript 引擎是 wasm 最早的執行引擎,因此特地介紹 V8 的一些特點。

V8 執行 WebAssembly 也是使用編譯後執行的方式: 通過自身的 TurboFan 或者 LiftOff 編譯後端,進行 JIT 編譯後執行。根據一份 2021 年的測試數據 [3],nodejs 在 Benchmark 上的性能領先於 wasmtime,可見 V8 的 WebAssembly 性能足以媲美多數專門的 wasm 引擎。

在標準支持方面,考慮到 V8 一般在 Web 或者 nodejs 環境中運行 wasm 程序,所以 V8 並不支持 WASI 標準。但是 Chrome&V8 團隊作爲 wasm 標準制定的主要參與者之一,V8 引擎對 WebAssembly 的 JS API、MVP 以及 Post-MVP 等核心提案都完整支持,並且實驗性地支持各類處在探索階段的提案。因此,如果想要嘗試 WebAssembly 的最新提案實現,V8 引擎是一個不錯的選擇。

而且,通過 nodejs 的命令行工具,我們可以非常方便地使用 V8 運行包裹 wasm 模塊的 JavaScript 程序。另外,nodejs 也提供了 WASI 擴展,即使是依賴 WASI 的 wasm 模塊也可以藉助 nodejs 運行。

4. 總結

在這篇文章中,我們先簡要介紹了一門程序語言的引擎需要哪些組件,構建了一個引擎的概念模型。之後,我們重點討論了當前社區常見的 wasmtime、wasm3、WasmEdge、wasm-micro-runtime 以及 V8 這五款引擎,分析了它們的執行方式、性能表現、標準完整程度與應用場景。除了文中所提及,還有 wasmer、wavm、fizzy 等諸多 wasm 引擎,限於篇幅,不再一一介紹。

本課程《自己動手實現一個 WebAssembly 解釋器》一章將帶領大家一起從零開始實現一個 WebAssembly 解釋器,對引擎內部的具體實現想進一步深入瞭解的同學歡迎前往閱讀學習。

附表

qYkKIR

表 2. 常見 WebAssembly 引擎總結

5. 參考文獻

[1]. Virtual Machine Showdown: Stack Versus Registers: https://www.usenix.org/events%2Fvee05%2Ffull_papers/p153-yunhe.pdf
[2]. Performance of WebAssembly runtimes in 2023: https://00f.net/2023/01/04/webassembly-benchmark-2023/
[3]. Benchmark of WebAssembly runtimes - 2021 Q1: https://00f.net/2021/02/22/webassembly-runtimes-benchmarks/
[4]. Runtime Structure: https://webassembly.github.io/spec/core/exec/runtime.html
[5]. Threaded code: https://en.wikipedia.org/wiki/Threaded_code
[6]. M3 Interpreter: https://github.com/wasm3/wasm3/blob/main/docs/Interpreter.md
[7]. Architecture of Wasmtime: https://docs.wasmtime.dev/contributing-architecture.html#architecture-of-wasmtime
[8]. WebAssembly runtimes compared: https://blog.logrocket.com/webassembly-runtimes-compared/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uQk_UQCiveoCvRoSTKszWA