区块链、硬件与面向服务的架构,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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。