區塊鏈、硬件與面向服務的架構,WASM 即將迎來大爆發?
在上一篇文章中我們探討了WASM在服務端的巨大潛力。這篇文章將從技術角度出發,以將 Rust 程序、C 程序編譯成 WASM 的實例來深入解讀 WebAssembly(Wasm),並探討了 WASM 在區塊鏈、硬件以及面向服務的架構(SOA)的實現。
本文作者: Second State 的研究員、開源核心開發 Tim McCallum。
以下爲正文:
本文不僅僅是對 Wasm 的技術探討,還在更廣義的範圍內討論 了Wasm 未來的潛力。
技術示例1:把一個簡單的 Rust 程序編譯成Wasm,並部署到一個獨立的 Wasm 虛擬機(稱爲WAVM)上。
技術示例2:編寫一個 C 程序,然後將其編譯爲 Wasm 並部署在 x86_64 硬件(macOS Catalina)上。在這個示例中,我們將使用 Fastly 的本地 WebAssembly 編譯器和稱爲 Lucet 的運行時來執行。
本文還將討論:
- Wasm在區塊鏈(全球去中心化計算)中的應用實現
- 硬件實現(可移植binary )
- 面向服務的架構(SOA) 實現
什麼是 Wasm?
WASM 是一種接近機器的、獨立於平臺的、低級的、類似於彙編的語言(Reiser and Bläser,2017)。 Wasm 讓 Web 有了安全、快速、可移植的低級代碼(Rossberg等,2018)。
Wasm 計算模型基於堆棧機器(譯者注:一種計算模型),指令通過隱式的操作數棧控制值,使用(出棧)參數值併產生或返回(入棧)結果值(webassembly.github.io,2019)。
Wasm 得到了極大的發展
下圖是過去幾年“ WebAssembly”學術論文的數量。
可以看出, 與“ WebAssembly” 相關的學術論文急劇增加,同時包含關鍵詞“ WebAssembly”和“ Blockchain”兩個詞的論文數量也呈上升趨勢。
本文將分別討論瀏覽器內 Wasm 的實現和區塊鏈中的 Wasm 實現。
瀏覽器內 Wasm 實現
WASM 的設計實現了漸進式 Web 開發(Webassembly.org,2019)。 Wasm 在瀏覽器中有許多讓人眼前一亮的實現。
案例之一:在線 Wasm 迷宮遊戲。
在編譯後,這個網頁版遊戲的大小不超過2048字節!
瀏覽器內 Wasm 實現的案例之二:同樣抓人眼球的 wasm-flate 的壓縮/解壓縮軟件。
Wasm-flate 是當前瀏覽器中速度最快的壓縮和解壓軟件。這種瀏覽器內的 Wasm 執行使 Web 開發者有機會將強大的新功能無縫集成到其 Web 應用程序中。這樣的 Wasm 開發意味着最終用戶不需要安裝第三方系統級應用,也無需在第三方系統級應用之間切換。
瀏覽器中的像 Wasm-flate 這樣的 Wasm 應用程序能否最終取代傳統的系統級競品應用程序,如WinZip?
Wasm 在區塊鏈中的實現
比特幣和以太坊使用基於堆棧的架構,該架構與 WebAssembly 基於堆棧的架構相似。
當然,每個獨特的基於堆棧的虛擬機都有一些差異。例如,在 Wasm 中找不到類似大家熟知的堆棧項目重複操作的功能,例如比特幣的 OP_DUP 操作碼和以太坊的 DUP1 至 DUP16 操作碼。
以太坊黃皮書中的複製操作。
幸好,Wasm 爲每個 Wasm 函數提供了固定數量的局部變量。這些變量將信息存儲在該特定函數本地的單個索引空間內。更值得關注的是,還有其他方法可以模擬特定堆棧行爲。
另一個重要的差異是每次操作可入棧的項目數量。仔細查看以太坊黃皮書(上圖),能夠注意到兩列標記爲 δ 和 α 的列。
標記爲 δ 的列表示要從堆棧中刪除的項目數。標記爲 α 的下一列代表要放置在堆棧上的其它項目的數量。以太坊虛擬機(EVM)上的每個操作都可以將許多項目入棧。在上面的示例中,DUP16 能夠將17個項目入棧。
但是,在當前版本的 Wasm 中,一條指令只能將一個結果值入棧(webassembly.github.io,2019)。
還有許多像這樣的細微差別。
毫無疑問,構建能將任何高級區塊鏈智能合約源代碼轉換爲可執行的 Wasm 式代碼的編譯器,這樣的工作非常複雜且繁重。
但 Second State 的開發者最近構建了一個名爲 SOLL 的編譯器(點擊此處有視頻demo),這是第一個允許在 Ewasm 測試網上進行以太坊 Solidity 智能合約的編譯、部署、交互的編譯器。
諸如此類的開拓性工作,標誌着去中心化網絡中數字價值和數據的交換,以及設備之間基於規則的交互開始了。將基於瀏覽器的設備編織到已經去中心化的區塊鏈架構中,可以使無需許可、抗審查、沒有邊界、安全並基於Web的交易成爲主流。
在今年的Devcon5(以太坊開發者大會)上,與以太坊的開發者進行交流後,Second State 也正在考慮構建從以太坊的中間語言Yul 到 LLVM 到 Ewasm 的編譯器。
這項新增的工作可能促成用C ++,Rust,Vyper等語言編寫的智能合約,得以部署到以太坊的 Wasm 區塊鏈實現中。
很快,大家會意識到,引入新語言(跨編譯器工具鏈的不同部分)會在多語言協作方面帶來無窮的可能性。
這是 Wasm 潛在的巨大益處。
Wasm——更接近硬件
Wasm不僅僅是Web瀏覽器或區塊鏈虛擬機的字節碼。這個你們知道嗎?
圖片出處:Raimond Spekking / CC BY-SA 4.0(Wikimedia Commons)
Web 跑在不同的瀏覽器、不同類型的設備、機器體系結構和操作系統上。針對Web的代碼必須獨立於硬件和平臺,這樣一來,應用程序在不同類型的硬件上運行,可以執行相同的結果(Rossberg等,2018)。
即使是很小的shell、移動設備、臺式機和 IoT 設備都能承載 Wasm 的運行環境。Wasm 能夠驅動微芯片,乃至整個數據中心。(Webassembly.org,2019)。
Docker 的聯合創始人所羅門·海克斯(Solomon Hykes)今年早些時候表示,“如果在2008年已經有了 WASM + WASI,我們根本不需要創建 Docker。WASM 就是這麼重要。服務器端的 Webassembly 是計算的未來。”
可以預見,Wasm 對於全球軟件開發社區中絕大部分開發者都很有吸引力。Wasm 的設計帶來了令人難以置信的速度、靈活性和可移植性,因此,我們顯然能推斷出 Wasm 將在世界上即將到來的每種計算解決方案(無論是在網頁還是非網頁)中都扮演着重要角色。
技術示例1:用 Rust 編寫 Wasm
讓我們深入研究一些代碼。我們接下來要編寫一個 Rust 程序,對其進行編譯,然後部署在獨立的 Wasm 虛擬機上。
安裝 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
添加組件
rustup component add rls rust-analysis rust-src
創建新的 Rust 項目
cd ~
cargo new --lib add_numbers_via_wavm
cd add_numbers_via_wavm
編輯 Cargo.toml 文件;將 lib 部分添加到文件末尾,如下所示
[lib]
name = "adding_lib"
path = "src/adding.rs"
crate-type =["cdylib"]
關於“ cdylib”的簡要說明
“如果您打算使用 C 語言(或通過 C FFI 的另一種語言)創建要使用的庫,那麼 Rust 無需在最終目標代碼中包含特定於 Rust的內容。對於這樣的庫,您需要在 Cargo.toml 中使用 cdylib crate 類型”(Doc.rust-lang.org,2019)
接下來我們添加必要的 Wasm 軟件和配置
rustup target add wasm32-wasi
rustup override set nightly
現在,創建一個名爲 〜/ .cargo / config 的新文件,並將以下構建文本放入此新創建的配置文件中
[build]
target = "wasm32-wasi"
注:rust-lang的 wasm32-unknown-wasi 最近被重命名爲wasm32-wasi。
Rust源代碼
在 /home/ubuntu/add_numbers_via_wavm/src 目錄中創建一個名爲 adding.rs (adding.rs/) 的文件,並填上下面的 Rust 代碼
#[no_mangle]
pub extern fn main(a: i32, b: i32) {
let z = a + b;
println!("The value of x is: {}", z);
}
編譯源代碼
這將在
/home/ubuntu/add_numbers_via_wavm/target/wasm32-wasi/release
目錄中創建一個adding_lib.wasm文件。
cargo build –release
我們馬上就到執行該 wasm 文件的步驟了,但是,我們必須先安裝 WebAssembly 虛擬機。
創建獨立的 Wasm 虛擬機
通常情況下,我們使用像 wasm-pack 這樣的軟件,可以讓開發者將 Rust 生成的 WebAssembly 與 JavaScript 集成在一起,並且像 wasm-bindgen 這樣的軟件可以促進 wasm 模塊和 JavaScrit 之間的高級交互。
但是我們在這裏要做的是完全不同的。
** 我們沒有使用Javascript,Node.js,也沒在瀏覽器中運行任何程序。我們是在獨立的 WebAssembly虛擬機中執行Rust程序,該虛擬機專門設計用於非 Web 應用程序。**
安裝名爲 WAVM 的虛擬機
sudo apt-get install gcc
sudo apt-get install clang
wget https://github.com/WAVM/WAVM/releases/download/nightly%2F2019-11-04/wavm-0.0.0-prerelease-linux.deb
sudo apt install ./wavm-0.0.0-prerelease-linux.deb
在虛擬機上執行 Wasm
讓我們嘗試使用下面的 WAVM 命令執行我們剛剛編譯的 Rust to Wasm 代碼
wavm run --abi=wasi --function=main ~/add_numbers_via_wavm/target/wasm32-wasi/release/adding_lib.wasm 2 2
The value of x is: 4
正如您在上面看到的,我們可以傳入兩個值(2和2),WebAssembly 虛擬機(WAVM)可以計算總和並通過控制檯將答案返回給我們。 真是激動人心!!!
技術示例2:在x86_64硬件上部署C程序
我們剛剛在獨立虛擬機中執行了 Rust / Wasm。現在,讓我們使用 Lucet 在 x86_64 硬件(macOS Catalina)上部署 C 程序。 Lucet 不是虛擬機,而是 WebAssembly 編譯器和運行時,它支持 Wasm 在服務器端運行。
與 WAVM 一樣,Lucet 還支持 WebAssembly 系統接口(WASI)。WASI 是一種新提議的標準,用於將低級接口安全地公開給文件系統、網絡和其他系統設施。
我們通過從源代碼編譯 Lucet 開始此演示。
然後,我們創建 hello world C 源代碼文件(如下所示)
#include <stdio.h>
int main(int argc, char* argv[])
{
if (argc > 1) {
printf("Hello from Lucet, %s!\n", argv[1]);
} else {
puts("Hello, world!");
}
return 0;
}
……位置改爲安裝 Lucet 的地方……
cd /opt/lucet/bin
……將 C 代碼編譯爲 Wasm……
./wasm32-wasi-clang ~/lucet/tpmccallum/hello.c -o ~/lucet/tpmccallum/hello.wasm
……然後可以看到下面的命令行……
然後,我們將 hello.wasm 文件傳遞到下一個命令,該命令生成 hello.so
lucetc-wasi ~/lucet/tpmccallum/hello.wasm -o ~/lucet/tpmccallum/hello.so
上一條命令的輸出如下所示。
爲了完成此演示,我們最終運行以下命令,該命令以 Mach-O 64位動態鏈接的共享庫 x86_64 文件(在macOS Catalina上)執行該程序。
我們在這裏所做的基本上是在服務器端執行 Wasm。
其它Wasm編譯器和運行時
如前所述,我們有了越來越多的 Wasm 編譯器和運行時。其中包括英特爾的 Wasm Micro 運行時和 Wasm 虛擬機。除了這些項目之外,還有 Wasmer 項目,該項目可以使開發者將一切編譯爲 WebAssembly,然後在任何操作系統上運行它或將其嵌入其他語言中(Wasmer.io,2019)。例如,您先可以使用 Go、Rust、Python、Ruby、PHP、C、C ++ 和 C#編寫代碼。之後將代碼編譯爲 Wasm,然後將該 Wasm 代碼嵌入到上述任何一種語言中。 Wasmer 還致力於創建可在任何平臺上執行的二進制文件。
Wasm 的內部工作機制
讓我們簡要介紹一下 Wasm 的內部工作機制,然後列出有助於您創建一個手寫的 Wasm 應用程序的資源。
數字指令
數字指令按值類型劃分。對於每種類型,可以區分幾個子類別:
- 一元運算: 消耗一個運算數併產生一個相應類型的結果
- 二進制運算: 消耗兩個運算數併產生一個相應類型的結果
- 比較: 消耗各自類型的兩個操作數併產生布爾整數結果
- 測試: 消耗相應類型的一個操作數併產生布爾整數結果
- 轉換: 消耗一種類型的值併產生另一種類型的結果
使用加載和存儲指令訪問內存。所有值以以小端排序讀取和寫入。
變量指令,提供對局部和全局變量的訪問。
控制指令包括if,loop和其他對代碼執行控制有影響的指令。
全球狀態
“存儲”代表可由 WebAssembly 程序操縱的所有全局狀態。
存儲爲抽象機的生命週期分配的每個函數實例、表實例、內存實例和全局實例保留一個單獨的索引位置。通過使用一個地址可以引用/訪問這些單獨的索引位置。
地址是對運行時對象的動態全局唯一引用(webassembly.github.io,2019)。
值類型對 WebAssembly 代碼可用於計算的單個值以及變量接受的值進行分類。 i32 和 i64 類型分別將 32 位和 64 位整數分類。整數不是固有地帶符號或無符號的,它們的解釋由單個操作確定(webassembly.github.io,2019)。 f32 和 f64 類型表示浮點值。
如下所示,每個文件均由一個字節顯式地編碼。
Wasm代碼在線編輯器
現在,您已經基本瞭解 Wasm,是時候編寫和部署手寫 Wasm 代碼了。我們可以使用基於 Web 的在線 WebAssemblyStudio 應用程序來執行此任務。
Wasm有文本文件格式“ .wat”和二進制文件格式“ .wasm”。 WebAssemblyStudio 應用程序(如上圖所示)使我們能夠創建各種源格式的 Wasm 應用程序。包括 Wasm 的上述文本格式 .wat。
這是一個簡單功能,在編輯器內以 Wasm 文本編寫。
那麼這個功能可以做什麼呢?
- 如上所示,第1行定義了模塊。有效的模塊可以少至文本“(模塊)”
- 第2行定義了稱爲“ add”的功能。此函數採用兩個 i32 參數,“ firstValue”和“ secondValue”。它返回一個 i32 變量
- 該代碼的第3行將 firstValue 的值入棧
- 第4行將 secondValue 的值入棧
- 第5行從堆棧中彈出兩個當前項,然後計算這兩個值的總和並將該總和值入棧
- 當函數顯式聲明一個返回值時,在這種情況下(結果爲 i32),始終將堆棧中剩餘的最後一項指定爲函數承諾返回的值。
您可能想知道如何根據堆棧中項目的完美數量來考慮每個單個操作的參數需求以及整個函數的返回承諾。
有了對功能簽名進行分類的“功能類型”,可以提前計算操作和項目之間的關係。更具體地說,函數類型定義每個單獨的操作“出棧”的項目數量以及該操作然後“壓入”堆棧的項目數量。此信息允許執行顯式代碼驗證。
下面是添加操作的說明(彈出兩 個i32 值併入棧一個 i32 值)。
i32.add
------------------------
[pops off] [pushes]
[i32 i32] -> [i32]
手動將 WAT 轉換爲 Wasm
雖然像 WebAssemblyStudio 這樣的在線產品可以爲我們處理所有編譯和轉譯。我們還可以在命令行中執行任務,例如創建 Wasm輸出。
tpmccallum$ ./wat2wasm adding_numbers.wat -o adding_numbers.wasm
上文提及的命令行 C 到 Wasm 的示例,Wasm 二進制格式的輸出對人眼而言是難以辨認的。這些可執行二進制文件並非設計成由操作系統(即 vi 或 emacs)本地查看。 值得慶幸的是,我們可以依靠預先構建的 Wasm 軟件庫來轉換 Wasm 代碼。
Wabt 發音爲“ wabbit”,是非常好用的 Wasm工具庫。 Wabt 可以執行以下任務,包括但不限於將 Wasm文本轉換爲 Wasm 二進制(wat2wasm),將二進制轉換回文本(wasm2wat),計算指令的操作碼使用量(wasm_opcodecnt),將Wasm轉換爲C(wasm2c)等。
以下面的命令爲例。
tpmccallum $ ./wat2wasm adding_numbers.wat -v
有關此基於彙編的代碼的完整輸出,請參見附錄A.1。
結果
回到我們的手寫 Wasm 演示。如果在 WebAssemblyStudio 中單擊“生成並運行”按鈕,我們將看到該函數添加了“ firstValue”和“ secondValue”,並且現在它返回了這些值的總和“ 2”。
結論
Wasm還處在一個很早期的發展階段。儘管如此,許多流行的編程語言的源代碼,例如C,C ++,Rust,Go 和 C#,已經可以編譯爲可用於生產的 Wasm 代碼。
這種前所未有的可移植性,對於促成開發者採用 Wasm,並進行協作,意義重大。
我們知道現在已經有了許多非常令人印象深刻的瀏覽器內 Wasm 應用程序。,越來越多的 Wasm 編譯器和運行時允許Wasm 在 Web 瀏覽器之外,在更接近硬件的地方執行。
Wasm 式的編譯器不可避免地會越來越接近硬件。這使得我們能夠開發出許許多多高效、易於移植和易於訪問且互相獨立的去中心化功能。
Wasm 具有下一代服務導向架構(SOA)的所有功能。它可以針對特定結果,也可以獨立存在,支持抽象化,並且可以輕鬆共享和使用其它底層功能單位。
這是一個振奮人心的合作領域。許多項目的繁重開發工作正在進行,所有這些艱苦的工作,會讓大家看到偉大的成就。
參考文獻
-
Doc.rust-lang.org. (2019). cdylib crates for C interoperability — The Edition Guide. [在線資源] 鏈接: doc.rust-lang.org/edition-gui… [訪問於2019年11月6日].
-
GitHub/appcypher. (2019). Awesome WebAssembly Languages. [在線資源] 鏈接: github.com/appcypher/a… [訪問於2019年10月27日].
-
Reiser, M. and Bläser, L., 2017, October. Accelerate JavaScript applications by cross-compiling to WebAssembly. In Proceedings of the 9th ACM SIGPLAN International Workshop on Virtual Machines and Intermediate Languages (pp. 10–17). ACM.
-
Rossberg, A., Titzer, B., Haas, A., Schuff, D., Gohman, D., Wagner, L., Zakai, A., Bastien, J. and Holman, M. (2018). Bringing the web up to speed with WebAssembly. Communications of the ACM, 61(12), pp.107–115.
-
Wasmer.io. (2019). Wasmer — The Universal WebAssembly Runtime. [在線資源] 鏈接: wasmer.io/ [訪問於2019年11月4日].
-
webassembly.github.io. (2019). WebAssembly Specification. [在線資源] 鏈接: webassembly.github.io/spec/core/ [訪問於2019年10月23日].
-
Webassembly.org. (2019). Non-Web Embeddings — WebAssembly. [在線資源] 鏈接: webassembly.org/docs/non-we… [訪問於2019年10月28日].
附錄
20191128更新
現在,任意指令序列可以使用和生成任意數量的堆棧值。 而不是像以前那樣只推送一個產生的堆棧值。 多值的提議當前的實現,在此已經解釋清楚了,目前處在 Wasm 標準化進程的第三階段。 附錄A.1
tpmccallum$ ./wat2wasm adding_numbers.wat -v
0000000: 0061 736d ; WASM_BINARY_MAGIC
0000004: 0100 0000 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01 ; section code
0000009: 00 ; section size (guess)
000000a: 01 ; num types
; type 0
000000b: 60 ; func
000000c: 03 ; num params
000000d: 7f ; i32
000000e: 7f ; i32
000000f: 7f ; i32
0000010: 01 ; num results
0000011: 7f ; i32
0000009: 08 ; FIXUP section size
; section "Function" (3)
0000012: 03 ; section code
0000013: 00 ; section size (guess)
0000014: 01 ; num functions
0000015: 00 ; function 0 signature index
0000013: 02 ; FIXUP section size
; section "Export" (7)
0000016: 07 ; section code
0000017: 00 ; section size (guess)
0000018: 01 ; num exports
0000019: 03 ; string length
000001a: 6164 64 add ; export name
000001d: 00 ; export kind
000001e: 00 ; export func index
0000017: 07 ; FIXUP section size
; section "Code" (10)
000001f: 0a ; section code
0000020: 00 ; section size (guess)
0000021: 01 ; num functions
; function body 0
0000022: 00 ; func body size (guess)
0000023: 00 ; local decl count
0000024: 20 ; local.get
0000025: 00 ; local index
0000026: 20 ; local.get
0000027: 01 ; local index
0000028: 6a ; i32.add
0000029: 0b ; end
0000022: 07 ; FIXUP func body size
0000020: 09 ; FIXUP section size