解读 WebAssembly 的 2020:Web 以外的进展与计划
来源 | bytecodealliance.org
整理 | 于航
策划 | 蔡芳芳
本文是 InfoQ“解读 2020”年终技术盘点系列文章之一。
大约一年前,“字节码联盟”(BA,Bytecode Alliance)宣布成立,而自那时起已经过去了将近一年的时间。
尽管 2020 年这个特殊的年份,让 WebAssembly(后面简称 Wasm)社区在某些阵线上的发展放慢了脚步,但相对的,在其他方面却也取得了很多的进展。
注:文中提及的“社区”,是指包括:字节码联盟、WWG、WCG 以及所有 Wasm 工具、基础设施相关开发者共同组成的这样一个 Wasm 社区,这里不单独进行区分。
如今,社区已经适应了这种新的常态,并且正在整装待发地准备在各个阵线上来个“加速超车”。但在此之前,让我们先来看一看,迄今为止社区所取得的成就。
社区的一个重要目标是希望能够将 WebAssembly 从一种应用程序的“编译目标”,发展成为一种可以用来组织应用程序的“模块化生态系统”。
这样,便有机会解决当前软件开发方式中许多长期存在的问题。例如,通过这种方式可以使应用程序更加安全地使用那些不是由作者本人编写的第三方依赖库中的代码。
而这也就是为什么“纳米进程模型”如此重要的一个原因。为了使开发人员能够轻松避免这些问题的,这是一种必要的开发范式上转变。
纳米进程模型的三个核心组成部分仍在发展中:
WASI(WebAssembly System Interface,WebAssembly 操作系统接口);
模块链接(Module Linking);
接口类型(Interface Types);
对于 WASI,你可以将其简单理解为运行时宿主(host)和 Wasm 模块互相通信的一种方式。
而 “模块链接” 可以被视为是两个 Wasm 模块直接相互通信的一种方式。
左侧:一个 Wasm 模块,它与宿主之间使用 WASI 进行“交流”
右侧:两个 Wasm 模块借助“模块链接”相互通信
在这两种情况下,通信的双方通常都是由不同的源语言编写的。这意味着它们可能会使用不同的方式来表示值和资源的处理过程。基本上,他们都使用非本地编程语言。
对于“接口类型”,可以将其比作一个“外语词典”。它可以帮助引擎实现上述“交流”过程。
那么上述这些提案,它们在今天的发展情况如何呢?
注记:字节码联盟不托管规范。BA(Bytecode Alliance)成员会驱动下面所提到这些规范的发展,这些成员正在与 W3C WebAssembly CG 中的其他成员一起协作。字节码联盟旗下的项目包含了对这些规范特性的具体实现。
WASI,WebAssembly 操作系统接口
社区在最初引入 WASI 这个概念时,通常会将其与 POSIX 和其他类型的操作系统接口进行比较。然而,这个比较过程实际上有点过分简化了。
左侧为文件目录结构,中间包含操作系统内核的保护性屏障,右侧是访问文件资源的应用程序
尽管 WASI 确实旨在通过设计一组标准化模块,来提供一些低层次的,类似于“操作系统调用”的操作,但社区同时也打算为专有的高层次宿主 API 提供相应的标准化模块。
如今,这两个方面都取得了相应的进展。
对于低层次的操作系统接口层,其工作主要集中在具体的实现上。
在规范方面,已经不断确认并解决了一些规范上的跨平台可实现性问题。其中的一个例子便是 wasi-socket API(最近 在 Wasmtime 中实现了相应的原型)。在这个例子中,社区的讨论重点是如何基于“Capability-based Security(基于功能的安全)” 来合理地处理套接字资源。
跨三个不同操作系统使用的 .wasm 文件
在实现方面,社区已经做了很多工作来提高实现的安全性和可靠性。而这些工作也“催生”出了一系列十分成熟、健壮的模糊测试方法(将在下文中介绍)。
另一件社区已经完成的事情,是将一系列可能影响安全性的操作拆分到了一个专用的、名为 cap-std 的库中。这是一个跨平台的库,以“面向功能”的方式提供了 Rust 标准库的大部分功能。这样,社区便可以更加专注于,在各类平台上以最正确的方式去构建那些可能影响安全性的基础设施。下一步,社区将会在 WASI 的相关实现中使用 cap-std。
对于新增加的、专有的高层次宿主 API 模块,也有一些令人兴奋的进展。
一个很好的例子是 wasi-nn,它是一个 神经网络的标准化接口。通常,经过训练的机器学习模型都会被部署在具有不同体系结构和操作系统类型的各类不同设备上。而借助于 wasi-nn,.wasm 文件便能够以一种可移植的方式去执行诸如“描述张量”及“执行推理请求”等操作,而无视底层具体的指令集架构(ISA)及操作系统(OS)差异。
伴随着这些 API 模块的发展,社区也将会在 Wasmtime 中实现这些功能。
这样,人们便可以在一些真实用例中尝试,并同时给予社区最真实的反馈。
如果想要拥有一个关于“可重用代码”的生态系统,就需要设计一种合理的方式,以便将所有的可重用模块都联系在一起。
现阶段,我们可以通过宿主 API 来将这些模块联系在一起。例如,在 Web 上,你可以将一系列的 WebAssembly.instantiate 调用串联,以便将一组模块链接在一起。而在 Wasmtime 中,你可以通过 Linker API 来完成这个操作。
但是,这种命令式的编码实现风格有一些缺点,比如它的使用方式相当笨拙,效率低下,并且要求宿主环境包含某种类型的垃圾回收器或循环引用收集器。
而借助于“模块链接”提案,模块之间的链接过程将会变成声明式的,并且更容易使用。同时这也意味着即使是在编译期,引擎也能够拥有关于模块如何相互链接的所有信息,而这将会带来许多潜在的优化空间和额外的功能特性,同时也消除了产生模块循环引用的可能。
目前,社区仍主要专注于“加载时链接”,这将使模块能够与常见的库代码分离。而对于这一点,该提案几乎是完备的。从长远来看,我们也能够添加对“运行时动态链接”的支持。
下一步,社区将首先尝试完成一个原型实现,该原型预计会在未来的几个月内完成。
同样的,“接口类型”提案也在快速发展中。
目前的“接口类型”提案可以使 Wasm 与一系列丰富的值类型“沟通”。而现在,该提案的设计能让沟通的过程变得更加高效。其中一个优化是,几乎所有情况下引擎都不再需要在沟通的过程中间产生值副本。
那么在短期内,还有哪些需要做的事情呢?
尽管借助于“接口类型”提案,Wasm 可以与不同的值类型沟通,但它们还无法与和特定资源相关的“句柄”以及“缓冲区”这两种类型沟通。而这两者对于支持 WASI 和其他 API 都十分重要,因为诸如“文件”之类的资源会使用到句柄,并且同时也应该能够读取文件并将读到的内容写入到一个缓冲区中。
左侧是 WASI 现在支持的类型,包括:字符串、数字、引用、布尔值、枚举、对象和联合
右侧是接下来将要支持的类型:句柄和缓冲区
一旦这些特性确认之后,“接口类型”提案将具备用于支持 WASI 和“模块链接”提案所需的一切基础,从而使得 Wasm 可以通过一种“源语言无关”的方式来使用相关的值和资源。为此,社区将继续完善 W3C 中的相关规范。
同样,在实现方面,社区也为后续的快速推进做好了准备。如今,一些“接口类型”提案所依赖的,在 Wasm 核心规范中定义的新特性也已经被实现,例如 “Reference Types”,“Multi-Value” 以及 “Multi-memory Support”。
社区同时还在开发一些可以帮助开发者方便快速地应用“接口类型”特性的工具。目前,大家可以使用 wasm-bindgen 为 JavaScript 代码创建类型绑定。在接下来的一年中,社区将会从 Rust 语言开始,在语言工具链中逐渐增加对“接口类型”提案的支持。
上述提到的大部分工作预计都会在接下来的六个月内完成。
同时,对于那些希望能够以“向前兼容”的方式,来在当下体验这些新特性的开发者,也可以使用 WITX 来定义自己的类型接口。更多的信息可以从 Pat Hickey 的 演示文稿 或 Radu Matei 的 博客文章 中进一步了解。
为了能够让“纳米进程”模型服务更多人群,社区需要集成尽可能多的编程语言。
通常来说,如果想要生成使用纳米进程模型的代码,则需要一个满足以下要求的编译器:
支持将 Wasm 作为编译目标;
支持最新的 Wasm 标准提案。
为了帮助编程语言社区更快地采用 Wasm,社区已经开始构建一个名为 wasm-tools 的工具集。这是用来帮助其他编译器“接入” Wasm 的一组标准工具。
这些都是如今 Wasmtime 中正在使用的工具,因此,随着新的 Wasm 特性被不断实现,它们也同样会被这些工具所支持。例如,社区已经开始在这些工具中构建对“模块链接”提案的支持。
目前,这些工具包括:
wasmparser,这是一个 Wasm 文件解析器。它非常节省内存资源,因为不会进行任何额外的资源分配,并且可以通过“流加载”的方式进行解析;
wasmprinter,这个工具可以将一个以 .wasm 结尾的 Wasm 二进制格式文件转换为对应的 .wat 文本格式,这对于调试和测试将会很有帮助;
wat 和 wast,这两个工具可以将以 .wat 和 .wast 结尾的 Wasm 文本格式代码转换为对应的二进制格式字节码,这对于运行测试用例非常有用(因为在文本格式下,维护测试用例将会更加方便);
wasm-smith,这是一个 测试用例生成器。它可以生成“伪随机”的 Wasm 模块,并且保证这些 Wasm 模块是合法有效的,可以将其用于模糊测试。
同样的,明年社区也将逐渐添加更多的工具。例如,社区将在 wasm-tools 中托管 Rust Interface Types 工具集 中与具体语言无关的那部分,而这将使得那些能够将 Wasm 作为编译目标的语言更容易地支持“接口类型”提案。除此之外,Wasm 社区也计划将与编程语言社区展开合作,以便在这些工具可用时及时对相关语言进行集成。
假设有一个完整的、使用 Wasm 构建的应用程序,那么我们便可以在类似 Wasmtime 这样的运行时中直接运行它。但是有时候,人们可能只想在项目中运行一部分 Wasm 代码。
例如,人们可能想要编写一些 Python 代码,来进行一些密集计算。但是 Python 可能太慢了,而本地扩展却又无法轻易地做到可移植,在这种情况下,我们便可以尝试使用 Wasm 模块来解决这个问题。
通过将 Wasmtime 嵌入到特定语言的运行时中,便可以为该语言提供对 Wasm 代码的解析和执行支持。
Wasmtime 位于中心,箭头指向下面列出的这些语言的 LOGO
以下这些语言现在已经支持借助 Wasmtime 来运行 Wasm 代码:
Python;
Go;
.NET;
Java (两种方式:kawamuray/wasmtime-java,或者 bluejekyll/wasmtime-java);
Perl;
Rust;
Zig;
Ruby。
随着新的纳米进程特性逐渐完善,它们已经被实现在 Wasmtime 中。每一个新特性的开发生命周期都会以“能够在语言运行时嵌入使用”作为终点。这意味着对于每一个 Wasm 新特性,这些语言运行时都能够以最快的速度支持。
这也同样意味着,编程语言社区不需要自己来设计和实现相应的 Wasm 模块链接及绑定方式。它们可以仅依赖于 Wasm 标准,而这将会使 Wasm 生态各类实现之间的“通用可交互性”成为可能。
去年,社区已经将维护的旧运行时嵌入方式转换为使用新的标准 Wasm C API,并且也正在确保 Rust 的嵌入 API 能够与 C API 保持同步更新。
众所周知,构建所有的这些 Wasm 基础设施是一项艰巨的任务,单靠 Wasm 核心社区有时会显得力不从心。因此,与多方利益相关者进行有效的协作便显得格外重要。
以下是 Wasm 社区在过去一年中所做的一些改变,而这些改变将使协作的过程变得更加完善。
Cranelift 是在许多运行时(包括 Wasmtime、Lucet 和 SpiderMonkey)以及一些其他项目(例如 Rust 编译器的备用可选后端)中使用的代码生成器。它将 Wasm 字节码转换为机器码。借助新的后端,人们可以更容易地为 Cranelift 添加对新 ISA 的支持,或者与他人协作以改进现有 ISA 的实现。
Cranelift 的旧后端对所有的内容都使用了同一个中间表示形式(IR)。除此之外,它还包含有一些难以理解的、晦涩的抽象层。
而新系统将整个链路分为两个阶段,并同时添加了机器相关的第二个 IR。这样,每个 IR 都可以专注于完成其自身的任务。新系统还移除了那些晦涩的抽象(可以在名为 “Kill Recipes With Fire” 的 GitHub Issue 帖子中了解更多的信息)。
不仅如此,社区还新构建了一个名为 Peepmatic 的工具。这个工具可以在生成第二个 IR 之前对原始代码应用“窥孔优化”,此时的代码仍具有可移植性。而且,社区也正在使 Peepmatic 变得更加灵活,以便在第二阶段也可以应用窥孔优化,这个阶段中的 IR 是特定于机器的。
这样做的目的是为了使任何“非控制流转换”都可以通过 Peepmatic 来进行。这样一来,在 Cranelift 中就可以减少很多一次性代码,从而提高其可维护性。
它同样有助于保持正确性:Peepmatic 所使用的 DSL 可以使一些“正确性问题”变得不再容易出现,而且相比原有实现更容易进行推理。社区还计划增加对窥孔优化的验证过程。这样,当一个优化并不适用于所有输入时,便可以及时将其检测出来。
为了充分发挥 Peepmatic 和 Wasm 沙箱的全部潜力,社区正在积极地与学术研究人员合作。例如,社区正在与 John Regehr 和他的学生 Jubi Teneja 合作,通过与 Souper 的集成来添加“超优化”策略。而在 UCSD 和 Helmholz 信息安全中心研究人员的帮助下,现在也已经提出了一些潜在的方法,可用于增强 Cranelift 抵御“旁路攻击”的能力。
如果有来自许多不同组织的很多人都在同时向一个应用添加新功能,则需要设置适当的防护措施,以确保他们在此过程中不会破坏对方添加的功能。
而一个非常好的用以捕捉“边缘性错误”的方法便是“模糊测试”(Fuzzing)。社区已经投入了大量精力来构建顶级的模糊测试基础设施。甚至这也使得其成为了 第一个 被 GoogleOSS-Fuzz 连续模糊测试服务接受的,主要使用 Rust 编写的项目。从那时起,OSS-Fuzz 已经发现了 80-90 个错误。而如果没有模糊测试的帮助,社区将不可能找到这些错误。
那么社区进行了怎样的模糊测试呢?
首先是对 Wasm 的执行进行模糊测试。如上所述,wasm-smith 测试用例生成器,它十分擅长创建有趣的测试用例以作为模糊测试的不同输入。除此之外,社区还进行了差分模糊测试,比较通过优化获得的结果和未经优化获得的结果,以确保两者能够获得相同的结果。另外,还有 Peepmatic 专用的模糊测试,以及 其他的各种配置。
为了确保对库的调用过程能够正常工作,社区还对 API 进行了模糊测试。并且也 对 wasm-tools 进行了模糊测试,以确保它们可以正常工作。如果模糊测试器返回的字节可以被 wasmparser 成功解析为 Wasm 字节码,就可以使用 wasmprinter 打印出来,这能确保它们可以成功地被打印为相应的文本格式,然后同时也会确保文本解析器可以正确解析这些文本。
在 Cranelift 中,模糊测试不仅在入口 IR 层面上进行。除此之外,在整个编译管道末端附近设置的第二个入口点处也会进行模糊测试。这使得测试可以更容易地覆盖到寄存器分配器算法的所有边缘情况。
为了确保能够继续拥有最佳的模糊测试效果,社区制定了一条新的规则:任何还没有为其添加模糊测试的功能,都无法被认定为已经完成。
大家都知道一个事实,如果你不了解某件事情的具体工作原理,那么就很难进行协作。为此,社区也改进了 文档 和一些示例。
增加了一个用来演示创建 Wasm 模块,并在 Wasmtime 中运行的 教学示例;
增加了一些 Wasmtime 高级用法的 示例;
增加了用于多种不同语言的 嵌入文档;
来年社区将会为此付出更多的努力。
自从社区宣布 Bytecode Alliance 的成立以来,便一直计划着合并 Lucet 和 Wasmtime。而随着 Wasmtime 团队加入 Fasty,这个计划的执行也将变得更加容易!
Lucet 和 Wastime 在快乐的“合并日”旗帜下击掌,而工程师们则围着它们聚会
这对字节码联盟旗下的项目意味着什么呢?
Mozilla 将会继续有一个专门的团队致力于 Firefox 中与 Wasm 相关的工作,专注于 Web 开发者的需求。在此过程中,他们将继续研究 Cranelift 代码生成器,该代码生成器已在 Firefox、Lucet 和 Wasmtime 等许多项目中使用。
Fastly 将会为 Mozilla 孵化的“浏览器外”项目(包括 Wasmtime 和 WASI)提供赞助,社区也将进一步扩大该工作的范畴。
这是对字节码联盟的“合作性、多方利益相关者”工作模式的一个证明。人们具体在哪里工作并不重要,而重要的是社区内的伙伴仍在共同努力。
这个过程看起来很棒。但是,如果社区无法将我们上述提到的这些东西交付到用户手中,一切将变得毫无意义。
以下是一些正处在“交付阶段”的项目。
Firefox 在 x86/x64 上对 Wasm 的支持始终是一流的。但是,由于架构方面的限制,其在 Arm 架构上的性能却有点跟不上。Cranelift 是专门为 Wasm 后端设计的,与 x86/x64 一样,其体系结构也同样可以很好地适用于 Arm 和其他平台。
这一点现在已经初见成效。如今在默认情况下,Arm64 平台上的 Firefox Nightly 将会默认使用 Cranelift 来处理 Wasm 相关内容,并且 Firefox 也正努力在 x86/x64 上使用它。
这是字节码联盟帮助加速整个生态系统的另一种方式。通过让联盟的 Wasm 专家与 Arm 和 Intel 的 CPU 体系结构专家合作,社区已经能够达成更好的设计,从而帮助我们更快地前进并获得更好的结果。
Fastly 的目标一直是吸引开发人员将数据和应用移动到 Edge Cloud 上。最近,他们使用 Wasm 朝着这个目标取得了巨大的进步。
他们是 最早看到 Wasm 在服务端潜力的公司 之一。从那以后,他们将其发展成了一个名为 “Compute@Edge” 的 Serverless 计算环境,其启动时间比其他替代产品快 100 倍。
Shopify 为全球超过 100 万个不同的商家提供服务,而这些客户都有着不同的需求。
为了使代码库和用户体验保持简单,Shopify 有一个产品原则:花费最多的时间去构建大多数商家所需要的东西。如果某些商家需要的功能超出该核心范围,Shopify 则会提供 App Store 上的第三方应用程序,来解决这些“长尾”需求。
这些应用中的许多都构建在其自己的基础设施上,在“限时特价”期间,应用有时会因负载激增而无法正常运作。为了帮助应用开发人员构建更稳定的应用程序,Shopify 希望允许应用程序代码在 Shopify 内部直接运行。
但是,如何才能以快速、安全的方式运行这些第三方代码?答案是使用 Wasm。
借助在 Lucet 之上构建的新平台,Shopify 能够进行顺利度过限时促销。在此期间,12 万个 Wasm 模块,在 60 秒的限时促销窗口时间内执行,同时每个模块的运行时开销都保持在 10 毫秒以下。
虽然 Wasmtime 是一个很棒的通用 Wasm 运行时,但有时你需要让它适合你自己的一些特殊情况。许多开发人员正在其上构建更复杂的“专用运行时”。
这些运行时将帮助开发人员更快地将产品投放到用户手中。
Enarx 是一个受信执行环境。它可以让你的数据完全保密,即使是运行在硬件上的数据也不例外;
Krustlet 是一个可以支持在 Kubernetes 上原生运行 Wasm 工作负载的工具;
WaSCC 是一个实现了“演员模型(Actor Model)”的框架,可用于连接到云原生服务,例如消息代理和数据库。
接下来,联盟和社区将重点关注什么?
其中一个重点是规范化治理模型,以便联盟可以招募新成员。为了能够将那些潜在的、在 Wasm 生态系统中从事关键工作的人们“招募”到核心团队,联盟需要为“如何进行选举?”以及“如何进行重要技术决策?”之类的过程定义标准化流程。而且需要将所有这些内容都纳入章程中,以便合作伙伴们知道他们需要签署的内容。
今年的疫情危机扰乱了这项工作,但在适应新常态之后,这项工作已经取得了良好进展,目前正在努力进行收尾工作。在这些工作就位之后,联盟将能够接受新成员并致力于扩大组织规模。
正如之前所提到的,“模块链接”提案和“接口类型”提案的进展都十分顺利。社区将专注于完成这些提案的 MVP 版本,并在运行时和工具链中完成相应的功能实现。预计 2021 年上半年将完成大部分工作。
一旦“接口类型”提案的 MVP 版本到位,Wasm 社区便可以开始与编程语言社区展开合作,将它们的语言类型映射为“接口类型”。这样一来就能够在由不同语言编写的模块之间实现最大的互操作性,并在支持 WASI 的 Wasm 宿主中轻松运行这些模块。
毋庸置疑,2020 年是特殊的一年,回顾过去的 10 个月,Wasm 社区也发生了很多“不平凡”的事情。其中有首次 Wasm Summit 峰会的成功召开,也有 Mozilla 裁员对 Wasm 发展的“不利”影响。但正如 Lin Clark 在文中说的那句话一样,重要的并不是我们在哪里,而是无论我们在哪里却都在为同一个目标而努力协作。Wasm 的发展从未止步,并且也在不断影响着从 Web 到 out-of-web 等各个领域的发展。
希望明年的 Wasm 社区,也能有你的存在。❤️
-
点个在看少个 bug 👇