在上一篇文章中咱们探讨了WASM在服务端的巨大潜力。这篇文章将从技术角度出发,以将 Rust 程序、C 程序编译成 WASM 的实例来深刻解读 WebAssembly(Wasm),并探讨了 WASM 在区块链、硬件以及面向服务的架构(SOA)的实现。html
本文做者: Second State 的研究员、开源核心开发 Tim McCallum。linux
如下为正文:git
本文不只仅是对 Wasm 的技术探讨,还在更广义的范围内讨论 了Wasm 将来的潜力。github
技术示例1:把一个简单的 Rust 程序编译成Wasm,并部署到一个独立的 Wasm 虚拟机(称为WAVM)上。web
技术示例2:编写一个 C 程序,而后将其编译为 Wasm 并部署在 x86_64 硬件(macOS Catalina)上。在这个示例中,咱们将使用 Fastly 的本地 WebAssembly 编译器和称为 Lucet 的运行时来执行。shell
本文还将讨论:编程
WASM 是一种接近机器的、独立于平台的、低级的、相似于汇编的语言(Reiser and Bläser,2017)。 Wasm 让 Web 有了安全、快速、可移植的低级代码(Rossberg等,2018)。ubuntu
Wasm 计算模型基于堆栈机器(译者注:一种计算模型),指令经过隐式的操做数栈控制值,使用(出栈)参数值并产生或返回(入栈)结果值(webassembly.github.io,2019)。
下图是过去几年“ WebAssembly”学术论文的数量。
能够看出, 与“ WebAssembly” 相关的学术论文急剧增长,同时包含关键词“ WebAssembly”和“ Blockchain”两个词的论文数量也呈上升趋势。浏览器
本文将分别讨论浏览器内 Wasm 的实现和区块链中的 Wasm 实现。安全
WASM 的设计实现了渐进式 Web 开发(Webassembly.org,2019)。 Wasm 在浏览器中有许多让人眼前一亮的实现。
案例之一:在线 Wasm 迷宫游戏。
在编译后,这个网页版游戏的大小不超过2048字节!
浏览器内 Wasm 实现的案例之二:一样抓人眼球的 wasm-flate 的压缩/解压缩软件。
Wasm-flate 是当前浏览器中速度最快的压缩和解压软件。这种浏览器内的 Wasm 执行使 Web 开发者有机会将强大的新功能无缝集成到其 Web 应用程序中。这样的 Wasm 开发意味着最终用户不须要安装第三方系统级应用,也无需在第三方系统级应用之间切换。
浏览器中的像 Wasm-flate 这样的 Wasm 应用程序可否最终取代传统的系统级竞品应用程序,如WinZip?
比特币和以太坊使用基于堆栈的架构,该架构与 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不只仅是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 将在世界上即将到来的每种计算解决方案(不管是在网页仍是非网页)中都扮演着重要角色。
让咱们深刻研究一些代码。咱们接下来要编写一个 Rust 程序,对其进行编译,而后部署在独立的 Wasm 虚拟机上。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env
rustup component add rls rust-analysis rust-src
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。
在 /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-pack 这样的软件,可让开发者将 Rust 生成的 WebAssembly 与 JavaScript 集成在一块儿,而且像 wasm-bindgen 这样的软件能够促进 wasm 模块和 JavaScrit 之间的高级交互。
可是咱们在这里要作的是彻底不一样的。
咱们没有使用Javascript,Node.js,也没在浏览器中运行任何程序。咱们是在独立的 WebAssembly虚拟机中执行Rust程序,该虚拟机专门设计用于非 Web 应用程序。
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
让咱们尝试使用下面的 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)能够计算总和并经过控制台将答案返回给咱们。 真是激动人心!!!
咱们刚刚在独立虚拟机中执行了 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 Micro 运行时和 Wasm 虚拟机。除了这些项目以外,还有 Wasmer 项目,该项目可使开发者将一切编译为 WebAssembly,而后在任何操做系统上运行它或将其嵌入其余语言中(Wasmer.io,2019)。例如,您先可使用 Go、Rust、Python、Ruby、PHP、C、C ++ 和 C#编写代码。以后将代码编译为 Wasm,而后将该 Wasm 代码嵌入到上述任何一种语言中。 Wasmer 还致力于建立可在任何平台上执行的二进制文件。
让咱们简要介绍一下 Wasm 的内部工做机制,而后列出有助于您建立一个手写的 Wasm 应用程序的资源。
数字指令按值类型划分。对于每种类型,能够区分几个子类别:
使用加载和存储指令访问内存。全部值以以小端排序读取和写入。
变量指令,提供对局部和全局变量的访问。
控制指令包括if,loop和其余对代码执行控制有影响的指令。
“存储”表明可由 WebAssembly 程序操纵的全部全局状态。
存储为抽象机的生命周期分配的每一个函数实例、表实例、内存实例和全局实例保留一个单独的索引位置。经过使用一个地址能够引用/访问这些单独的索引位置。
地址是对运行时对象的动态全局惟一引用(webassembly.github.io,2019)。
值类型对 WebAssembly 代码可用于计算的单个值以及变量接受的值进行分类。 i32 和 i64 类型分别将 32 位和 64 位整数分类。整数不是固有地带符号或无符号的,它们的解释由单个操做肯定(webassembly.github.io,2019)。 f32 和 f64 类型表示浮点值。
以下所示,每一个文件均由一个字节显式地编码。
如今,您已经基本了解 Wasm,是时候编写和部署手写 Wasm 代码了。咱们可使用基于 Web 的在线 WebAssemblyStudio 应用程序来执行此任务。
Wasm有文本文件格式“ .wat”和二进制文件格式“ .wasm”。 WebAssemblyStudio 应用程序(如上图所示)使咱们可以建立各类源格式的 Wasm 应用程序。包括 Wasm 的上述文本格式 .wat。
这是一个简单功能,在编辑器内以 Wasm 文本编写。
那么这个功能能够作什么呢?
您可能想知道如何根据堆栈中项目的完美数量来考虑每一个单个操做的参数需求以及整个函数的返回承诺。
有了对功能签名进行分类的“功能类型”,能够提早计算操做和项目之间的关系。更具体地说,函数类型定义每一个单独的操做“出栈”的项目数量以及该操做而后“压入”堆栈的项目数量。此信息容许执行显式代码验证。
下面是添加操做的说明(弹出两 个i32 值并入栈一个 i32 值)。
i32.add ------------------------ [pops off] [pushes] [i32 i32] -> [i32]
虽然像 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)的全部功能。它能够针对特定结果,也能够独立存在,支持抽象化,而且能够轻松共享和使用其它底层功能单位。
这是一个振奋人心的合做领域。许多项目的繁重开发工做正在进行,全部这些艰苦的工做,会让你们看到伟大的成就。
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