前序
在本文中,我们将讨论驱动 WebAssembly 程序运行的核心组件——引擎。首先,我们先了解下通用引擎的工作模式:
引擎可以将程序文件加载到内存中,并解析符号,将其中的符号转化成具体的、可访问的内存地址。加载完成后,引擎可以解释执行目标程序,翻译每一条指令(引擎可以解释执行,也可以进一步编译为物理机器的可执行指令后执行。)在执行时,需要调度器控制是单线程还是多级流水线执行程序,最后引擎可以利用内存或其他硬件存储数据,完成程序的执行,也需要提供对外访问系统资源的接口。
当然如果需要定制化能力,就需要流水线支持了。接下来我们就看下,目前主流的WebAssembly引擎的能力。
WebAssembly 引擎
我们将着重介绍几个活跃在 WebAssembly 社区的引擎。这些项目都有着比较广泛的使用群体,在不同的场景下为用户创造了价值。
wasmtime
Wasmtime是由 bytecodealliance 开源的WebAssembly和WASI的小型高效运行时引擎。它在Web外部运行WebAssembly代码,既可以用作命令行类程序,也可以用作更大应用程序中嵌入的库。
运行时
wasm-runtime维护 wasm 模块在运行时的各类数据结构,为程序运行提供必要的支持。wasm-runtime组件负责维护多个重要的实体:Store、InstanceHandle和VMContext
Store:对应 WebAssembly 中的 "store” 概念,可以被看作是一组相关的 WebAssembly 对象。包括实例、全局变量、内存、表等。
Store 在某种程度上是用于几乎所有 wasm 操作的 "上下文"。是隔离的单位,不能同时从多个线程使用,不同 store 中的数据结构不允许互操作,类似于 V8 中的 Isolate.
InstanceHandle:作为 wasm 模块实例的底层表示,但也用于宿主定义的对象;在内存中的布局先有一些 Rust 拥有的值,捕获了内存/表/等的动态状态
归属于某个 Store,同时也包含一个 VMContext 用于维护一个 wasm 模块实例所有的数据结构.
VMContext:一个原始指针,在 InstanceHandle 的分配中传递,在 JIT 代码中使用。每个 InstanceHandle 都有一个与之对应的 VMContext。VMContext 的布局由模块动态确定,JIT 代码针对这一结构进行专门化。核心作用:
-
存储 WebAssembly 实例状态,如全局值、指向表的指针、指向内存的指针和指向其他 JIT 函数的指针。
-
分离 wasm 导入和本地状态。导入的值存储指向实际值的指针,本地状态定义了内联状态。
-
保持一个指向栈限制的指针,在此点 JIT 代码将触发栈溢出。
wasm-cache:可选依赖项。顾名思义主要用于管理文件的缓存,但默认只会在 wasmtime 提供的 CLI 中启用;
wasm-debug:实现从 WASM-DWARF 到原生 DWARF 的映射。考虑到 wasmtime 将所有 wasm 函数都编译为原生函数,要实现源码调试,两种调试信息之间的转换非常关键;
wasm-profiling:实现对生成的 JIT 代码进行分析,以帮助开发者掌握 wasm 程序运行状况。
现状
作为 Bytecode Alliance 推出的重点项目之一,wasmtime 对于 WebAssembly 相关标准支持的完整度非常高。它支持了标准的 WASI,实现了标准的 wasm c-api,并紧密跟踪 WebAssembly 核心特性。wasmtime 不仅实现了 Fixed-Width SIMD、Reference Types、Bulk Memory operations 等成熟提案,还支持 Tail-Call、Threads 等还处于标准实现阶段的提案。
虽然 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%的执行性能提升。
wasmer
Wasmer 是支持 WASI 和 Emscripten 的 WebAssembly 运行时,提供基于 WebAssembly 的超轻量级容器,其可以在任何地方运行:从桌面端到云端、以及 IoT 设备,并且能嵌入在任何编程语言中。
通过设计,WebAssembly模块运行所在的环境与基础主机系统的本机功能完全隔离(或沙盒化)。这意味着默认情况下,Wasm模块被设计为仅执行纯计算。因此,通常无法从WASM访问“ OS”级资源,例如文件描述符,网络套接字,系统时钟和随机数。但是,在许多情况下,Wasm模块需要执行的工作不仅仅是执行纯计算。它们必须与本机“ OS”功能交互。
Wasmer旨在提供三个关键功能:
-
多语言支持,程序能够以任何编程语言运行
-
可移植的二进制文件能够在Wasmer支持的任何“OS(例如Linux,macOS,Windows和FreeBSD)上运行且无需修改。
-
充当Wasm模块通过诸如WASI和Emscripten之类的ABI与本机OS功能交互的安全桥。
特性
快速又安全:Wasmer在完全沙盒化的环境中以“接近本机”的速度运行 WebAssembly。
可插拔:Wasmer 可以根据你的需求支持不同的编译框架 (LLVM,Cranelift等).
通用:你可以在任何平台(macOS, Linux and Windows) 和芯片组运行 Wasmer.
标准化:运行时通过了官方WebAssembly测试集、支持WASI 和 Emscripten.
现状
Wasmer可以说是在WASI生态中响应速度仅次于Mozilla的组织,他们号称打造了一款可以让代码“一次构建,处处运行”(Build Once, Run Anywhere.)的运行时环境,该环境可以运行ECMAScripten标准与WASI标准的wasm栈机码,并且方便为wasm代码分发,该组织开发了类似于nodejs生态中npm的包管理工具wapm,这样用户就可以很轻松地发布自己的程序,以及利用他人的程序了–这促进了WASM生态的发展,同时作为生态底层的领导者,Wasmer也将拥有更多发言权。
官方表示,不只在浏览器中,WebAssembly(Wasm)会是未来软件执行和容器化的关键组件。透过Wasm进行软件容器化,可以让二进制文件在不需要修改的前提下,在Linux、macOS、Windows以及网页浏览器等环境执行,并保护主机不受恶意程序码、错误和臭虫影响。Wasmer容器的特点之一,便是在Docker容器因过于笨重无法运作之处,执行容器化工作负载。
wasm3
wasm3 号称是最快速的一款基于解释器执行的轻量级 WebAssembly 引擎,使用 C 语言编写开发,拥有比较完善的 wasm 运行时系统,主要贡献者包括乌克兰开发者 Volodymyr Shymanskyy 等人。
wasm3 最大特点就是依靠纯解释器执行所有的 WebAssembly 指令,没有引入 JIT/AOT 编译。根据 Benchmark 数据显示,wasm3在很长一段时间霸榜运行速度最快的解释型引擎。
特性
wasm3运行速度快的原因来源于其架构的关键设计:指令线索化(threaded code)和 寄存器指令转译。指令线索化与普通的 switch-case 模式不同,线索化的解释器并不存在一个外层的控制结构。相反地,线索化指令总是会在自身解释程序的最后位置进行对下一条指令的调用,从而“自驱动”地执行所有指令。也正是由于指令解释函数总是以对下一条指令的调用结束,编译器可以对大部分的指令操作进行尾调用(tail-call)优化,以减少函数调用栈帧的压入和弹出操作。
寄存器指令转译:WebAssembly 基于栈式机器的设计导致其速度会比基于寄存器的指令运行速度会更慢,因此 wasm3 也将 wasm 指令序列转译成了更直接和高效的寄存器指令序列(简称 M3 指令)。这样Wasm3经过精心优化,其运行速度接近本地代码,甚至在某些情况下超越。
现状
由于是纯解释执行,无引入JIT等库能力,它的轻量特点——移动端二进制产物体积不到 70 KB,作为移动端引擎,wasm3 可谓表现优异。 wasm3 也可以在 iOS 等设备上运行。而这一点是其它纯编译型引擎无法做到的。
wasm3 通过支持标准的 WebAssembly System Interface(WASI)提供获得系统能力的通道,因此能够以独立(Standalone)的方式运行 wasm 函数,即使它依赖于如printf之类的接口。
目前,wasm3 已经被 wasmcloud、Siemens Opensource 等众多项目引入,作为 WebAssembly 的运行时。相信随着愈加广泛的应用,wasm3 也会逐步完善,为接入它的项目带来更多的价值。
WasmEdge
WasmEdge 是轻量级、安全、高性能、可扩展、兼容OCI的软件容器与运行环境。目前是 CNCF 沙箱项目。WasmEdge 被应用在 SaaS、云原生,service mesh、边缘计算、边缘云、微服务、流数据处理、LLM 推理等领域。
WasmEdge 是一种编译型的 wasm 引擎,可以按照 JIT/AOT 两种模式对 wasm 指令进行编译并最终执行。WasmEdge 使用 LLVM 作为编译器后端,利用了 LLVM 出色的优化编译能力。
除了卓越的性能表现之外,WasmEdge 的一个最为显著的特点就是社区提供丰富的扩展能力。比如,在云原生的使用场景中,很多开发者使用 JavaScript 语言开发应用。为此WasmEdge 以 wasm 模块形式(名为 wasmedge_quickjs.wasm)提供了 QuickJS 引擎能力, 以便在 wasm 环境中执行 JavaScript 代码。在此基础上,WasmEdge 社区提供了一揽子基于 JavaScript 的能力扩展:
-
Node.js Compatibility
-
TensorFlow
-
React SSR
-
Networking APPs
正是因为 WasmEdge 的优势,2022 年 11 月,Docker 正式宣布集成 WasmEdge 以提供运行 WebAssembly 应用的能力。自此,WebAssembly 和 WasmEdge 都将在 Docker 容器化的开发中扮演更加重要的角色。
wasm-micro-runtime
wasm-micro-runtime 也简称为 WAMR,也是 Bytecode Alliance 的开源 WebAssembly 引擎项目,适用于嵌入式平台、各类 IoT 设备等场景。它的特点是WAMR 的二进制产物很轻量,纯 AOT 配置的产物体积只有约 50KB,非常适合资源受限的宿主。
作为一款成熟的、可用于生产环境的 WebAssembly 引擎,WAMR 对社区标准支持的完整度也非常高:除了 MVP 中的特性全部支持之外,对 Post-MVP 中的 Fixed-Width SIMD、引用类型、共享线性内存、线程等提案的实现也都已正式上线。不过,对于 JS-API 中的一些接口,如 Memory.grow、Table.grow 等接口还未支持。
总结
本文我们重点讨论了当前社区常见的WebAssembly引擎,分析其特性、性能及现状,当然除了本文所涉及的引擎,还有众多优秀的引擎比如fizzy、老牌js引擎V8等就不再详细介绍了。从社区生态发展来看,WebAssembly的势头还是高歌猛进的,各社区及企业联盟也都在大力投入资源,也希望在大家共同努力下WebAssembly的标准提案能更快落地。