WebAssembly 是一种用于编译到 Web 的新的二进制格式。它目前正在设计和实现中,主要的浏览器供应商正在合作进行开发。进展非常迅速!在这篇文章中,我们将通过深入研究 WebAssembly 的工具链方面来展示我们最近取得的一些进展。
为了使 WebAssembly 可用,我们需要两个主要组件:能够编译代码到 WebAssembly 的工具链,以及能够执行该输出的浏览器。这两个组件都取决于 WebAssembly 规范 完成的进度,但在其他方面基本上是独立的工程工作。这种分离是一件好事,因为它将使编译器能够发出在任何浏览器中运行的 WebAssembly,并使浏览器能够运行 WebAssembly,无论它是由哪个编译器生成的;换句话说,它允许多个工具链和多个浏览器协同工作,从而提高用户选择。这种分离还允许现在并行进行这两个组件的工作。
WebAssembly 工具链方面的一个新项目是Binaryen。Binaryen 是一个用于 WebAssembly 的编译器基础设施库,用 C++ 编写。如果您自己没有开发 WebAssembly 编译器,您可能永远不需要了解它,但如果您使用 WebAssembly 编译器,它可能会在幕后为您使用 Binaryen;稍后我们将看到一些示例。
Binaryen 的核心是一组模块化的类,可以解析和发出 WebAssembly,以及以专为编写灵活的转换过程而设计的 AST 来表示它。在此基础上构建了几个有用的工具
- Binaryen shell,它可以加载 WebAssembly 模块,对其进行转换,在解释器中执行它,打印它等。加载和打印使用 WebAssembly 当前的临时 s 表达式格式,该格式的后缀为 .wast(正在设计 WebAssembly 二进制格式 以及最终的 文本格式,但它们还没有准备好)。
- asm2wasm,它将 asm.js 编译成 WebAssembly。
- wasm2asm,它将 WebAssembly 编译成 asm.js。(这还在开发中。)
- s2wasm,它将 .s 文件(由 LLVM 中正在开发的新 WebAssembly 后端 发出的格式)编译成 WebAssembly。
- wasm.js,Binaryen 本身移植到 JavaScript 的版本。这使我们能够在网页或任何其他 JavaScript 环境中运行所有上述组件。
有关 Binaryen 的一般概述,您可以查看我最近发表的演讲的 幻灯片。不要跳过 第 9 张幻灯片 :)
需要注意的是,WebAssembly 仍处于设计阶段,Binaryen 可以读取和写入的格式(.wast、.s)不是最终的。Binaryen 一直在随着这些变化而不断更新;变化的速度正在降低,但请做好应对中断的准备。
让我们讨论一些 Binaryen 可以提供帮助的具体领域。
使用 Emscripten 编译到 WebAssembly
Emscripten 可以将 C 和 C++ 编译成 asm.js,而 Binaryen 的 asm2wasm 工具可以将 asm.js 编译成 WebAssembly,因此 Emscripten+Binaryen 共同提供了一种完整的将 C 和 C++ 编译成 WebAssembly 的方法。您可以直接在 asm.js 代码上运行 asm2wasm(它可以在命令行上运行),但最简单的方法是让 Emscripten 为您执行此操作,例如使用
emcc file.cpp -o file.js -s ‘BINARYEN=”path-to-binaryen”’
Emscripten 将编译 file.cpp,并发出一个主要的 JavaScript 文件和一个用于 WebAssembly 输出的单独文件,格式为 .wast。在幕后,Emscripten 编译成 asm.js,然后在 asm.js 文件上运行 asm2wasm 以生成 .wast 文件。有关更多详细信息,请参阅 Emscripten 关于 WebAssembly 的维基页面。
但是等等,当浏览器尚不支持时,编译到 WebAssembly 有什么用呢?好问题 :) 是的,我们不想发布这段代码,因为浏览器无法运行它。但它对于测试目的仍然非常有用:我们希望知道 Emscripten 能够尽快正确地编译到 WebAssembly,因为我们不想等待浏览器支持。
但是,如果我们无法运行它,我们如何检查 Emscripten 是否确实正确地编译到 WebAssembly 呢?为此,我们可以使用 wasm.js,Emscripten 在我们之前运行该 emcc 命令时将其集成到我们的输出 .js 文件中。wasm.js 包含编译到 JavaScript 的 Binaryen 的部分,包括 Binaryen 解释器。如果您运行 file.js(在 node.js 中或在网页上),那么解释器将执行该 WebAssembly。这使我们能够实际验证编译的 WebAssembly 代码是否执行了正确操作。您可以在 此处 查看此类已编译程序的示例,并且在 构建套件存储库 中有一些用于测试目的的其他构建。
当然,鉴于这种奇怪的测试环境,我们还没有达到我们希望的那么稳固的地步:一个编译到 WebAssembly 的 C++ 程序,在一个本身从 C++ 编译到 JavaScript 的 WebAssembly 解释器中运行,并且还没有其他方法可以运行该程序。但我们有一些理由相信结果
- 此输出通过了 Emscripten 测试套件。其中包括许多实际的代码库(Python、zlib、SQLite 等)以及许多针对 C 和 C++ 中极端情况的单元测试。经验表明,当该测试套件通过时,其他代码也很可能能够工作。
- Binaryen 解释器通过了 WebAssembly 规范测试套件,表明它正在正确运行 WebAssembly。换句话说,当浏览器获得原生支持时,它们应该以相同的方式运行它(但速度要快得多!此代码在简单的解释器中运行以进行测试,因此速度非常慢;但请注意,正在研究 快速 方法 来 polyfill)。
- 此输出是使用 Emscripten 生成的,Emscripten 是一个在生产环境中使用的稳定编译器,并且在 Binaryen 之上只有一小部分代码(只有几千行)。新代码越少,错误的风险就越小。
总的来说,这表明我们目前处于良好的状态,并且可以使用 Emscripten + Binaryen 将 C 和 C++ 编译到 WebAssembly,即使浏览器尚不支持它。
请注意,除了发出 WebAssembly 之外,我们在此模式下发出的构建通常还会使用 Emscripten 工具链中的所有其他内容:Emscripten 移植的 musl libc 和 syscalls 以访问它、OpenGL/WebGL 代码、浏览器集成代码、node.js 集成代码等等。因此,这支持 Emscripten 已有的所有功能,并且使用 Emscripten 的现有项目只需切换一个开关即可切换到发出 WebAssembly。这是让现有的编译到 Web 的 C++ 项目在 WebAssembly 发布时获益的关键部分,而无需他们付出任何额外的努力。
使用新的实验性 LLVM WebAssembly 后端与 Emscripten
我们刚刚看到了 Emscripten 的一个重要里程碑,即它可以编译到 WebAssembly 甚至测试我们是否获得了有效的输出。但事情并没有到此结束:那是使用 Emscripten 当前的 asm.js 编译器后端,以及 asm2wasm。有一个新的 LLVM 后端正在直接在上游 LLVM 存储库中开发用于 WebAssembly,虽然它还没有准备好供普遍使用,但从长远来看,它将非常重要。Binaryen 也支持这一点。
LLVM 后端,与大多数 LLVM 后端一样,会发出汇编代码,在本例中,使用特定的 .s 格式。该输出接近于 WebAssembly,但并不完全相同——它看起来更像是 C 编译器的输出(指令的线性列表,每行一条指令等),而不是 WebAssembly 更结构化的 AST。但是,.s 文件可以以相当直接的方式转换为 WebAssembly,并且 Binaryen 包含 s2wasm,这是一个将 .s 转换为 WebAssembly 的工具。它可以在命令行上独立运行,但也具有 Emscripten 集成支持:Emscripten 现在有一个 WASM_BACKEND 选项,您可以像这样使用它
emcc file.cpp -o file.js -s ‘BINARYEN=”path-to-binaryen”’ -s WASM_BACKEND=1
(请注意,您还需要 BINARYEN 选项,因为 s2wasm 是 Binaryen 的一部分。)提供该选项后,Emscripten 将使用新的 WebAssembly 后端而不是现有的 asm.js 后端。在调用后端并从其接收 .s 后,Emscripten 调用 s2wasm 将其转换为 WebAssembly。一些您可以使用新后端构建的程序示例在 Emscripten 维基 上。
因此,有两种方法可以使用 Binaryen 编译到 WebAssembly:Emscripten + asm.js 后端 + asm2wasm,它现在可以工作并且应该相当健壮和可靠,以及Emscripten + 新的 WebAssembly 后端 + s2wasm,它还没有完全投入使用,但随着 WebAssembly 后端的成熟,它应该成为一个强大的选项,并有望在未来取代 asm.js 后端。目标是使这种转换无缝进行:在两种 WebAssembly 模式之间切换只需设置一个选项,就像我们看到的那样。
Emscripten 中 asm.js 和 WebAssembly 支持之间也是如此,这只是一个可以设置的选项,并且那里的转换也应该是无缝的。换句话说,将有一条简单明了的路径从
- 使用 Emscripten 发出 asm.js 到
- 使用它通过 asm2wasm 发出 WebAssembly(今天可以实现,但浏览器还无法运行它)到
- 使用它通过新的 LLVM 后端发出 WebAssembly(后端准备就绪后)。
每个步骤都应该带来巨大的好处,而无需开发人员付出任何额外努力。
最后,请注意,虽然这篇文章重点介绍了将 Binaryen 与 Emscripten 结合使用,但其核心设计是成为一个通用的 C++ WebAssembly 库:如果您想使用 WebAssembly 编写一些与工具链相关的内容,您可能需要代码来读取 WebAssembly,打印它,操作 AST 等,Binaryen 提供了这些功能。它在编写 asm2wasm、s2wasm 等方面非常有用,希望其他项目也能发现它的用处。
关于 Alon Zakai
Alon 是 Mozilla 研究团队的成员,他主要从事 Emscripten 的工作,Emscripten 是一个将 C 和 C++ 编译成 JavaScript 的编译器。Alon 于 2010 年创立了 Emscripten 项目。
21 条评论