WebAssembly 的 MVP 之后:卡通技能树

人们对 WebAssembly 存在一个误解。他们认为 2017 年在浏览器中上线的 WebAssembly(我们称之为 WebAssembly 的最小可行产品或 MVP)是 WebAssembly 的最终版本。

我能理解这种误解从何而来。WebAssembly 社区小组非常致力于向后兼容。这意味着您今天创建的 WebAssembly 在未来继续在浏览器上运行。

但这并不意味着 WebAssembly 功能已完成。事实上,情况远非如此。许多功能即将添加到 WebAssembly,这将从根本上改变您使用 WebAssembly 的方式。

我认为这些未来的功能就像电子游戏中的人物技能树。我们已经完全填满了前几项技能,但下面还有整个技能树需要填补,才能解锁所有应用。

Skill tree showing WebAssembly skills which will be filled in through the course of the post.

所以让我们看看已经填补的内容,然后我们就可以看到未来会发生什么。

最小可行产品(MVP)

WebAssembly 的故事最初开始于 Emscripten,它使得通过将 C++ 代码转译为 JavaScript 来在 web 上运行 C++ 代码成为可能。这使得将大型的现有的 C++ 代码库(用于游戏和桌面应用程序等)引入 web 成为可能。

尽管如此,它自动生成的 JS 仍然比类似的原生代码慢得多。但是 Mozilla 的工程师在生成的 JavaScript 中发现了一个类型系统,并找到了如何 使此 JavaScript 运行得更快 的方法。这个 JavaScript 子集被命名为 asm.js

当其他浏览器供应商看到 asm.js 的速度有多快后,他们也 开始为他们的引擎添加相同的优化

但这并不是故事的结局。这仅仅是开始。引擎仍然可以做一些事情来使其更快。

但他们不能在 JavaScript 本身中做到这一点。相反,他们需要一种新的语言——一种专门为编译而设计的语言。这就是 WebAssembly。

那么第一个版本的 WebAssembly 需要哪些技能?我们需要做些什么才能获得一个能够在 web 上有效地运行 C 和 C++ 的最小可行产品?

技能:编译目标

从事 WebAssembly 开发的人员知道他们不想仅仅支持 C 和 C++。他们希望许多不同的语言能够编译到 WebAssembly。因此,他们需要一个与语言无关的编译目标。

他们需要一些类似于桌面应用程序编译到的汇编语言的东西——比如 x86。但这种汇编语言不会用于实际的物理机器。它将用于一台概念机器。

技能:快速执行

该编译器目标必须设计成能够非常快地运行。否则,在 web 上运行的 WebAssembly 应用程序将无法满足用户对流畅交互和游戏体验的期望。

技能:紧凑

除了执行时间之外,加载时间也需要很快。用户对加载速度有一定的期望。对于桌面应用程序,这种期望是它们会快速加载,因为该应用程序已经安装在您的计算机上。对于 web 应用程序,人们也期望加载时间很快,因为 web 应用程序通常不需要加载与桌面应用程序一样多的代码。

但是,当您将这两件事结合起来时,就会变得棘手。桌面应用程序通常是相当大的代码库。因此,如果它们在 web 上,用户第一次访问 URL 时,会有很多内容需要下载和编译。

为了满足这些期望,我们需要我们的编译器目标紧凑。这样,它就可以快速通过 web 传输。

技能:线性内存

这些语言还需要能够以不同于 JavaScript 使用内存的方式使用内存。他们需要能够直接管理他们的内存——说出哪些字节属于一起。

这是因为像 C 和 C++ 这样的语言具有称为指针的低级特性。您可以有一个变量,它没有值,而是具有值的内存地址。因此,如果您要支持指针,程序需要能够写入和读取特定地址。

但是您不能让从 web 下载的程序随意访问内存中的字节,使用他们想要的任何地址。因此,为了创建一种安全的方式来访问内存,就像本地程序使用的那样,我们必须创建一些可以访问内存的特定部分,而不是其他任何东西。

为此,WebAssembly 使用线性内存模型。这使用 TypedArrays 实现。它基本上就像一个 JavaScript 数组,只是这个数组只包含内存字节。当您访问其中的数据时,您只需使用数组索引,您可以将这些索引视为内存地址。这意味着您可以假装这个数组是 C++ 内存。

成就解锁

因此,有了所有这些技能,人们就可以在浏览器中运行桌面应用程序和游戏,就好像它们在他们的计算机上本地运行一样。

这几乎是 WebAssembly 在作为 MVP 发布时的技能集。它确实是一个 MVP——一个最小可行产品。

这允许某些类型的应用程序工作,但还有很多其他应用程序需要解锁。

重量级桌面应用程序

要解锁的下一个成就就是重量级桌面应用程序。

你能想象如果像 Photoshop 这样的东西在你的浏览器中运行会怎么样?如果您可以像使用 Gmail 一样在任何设备上立即加载它?

我们已经开始看到类似的事情。例如,Autodesk 的 AutoCAD 团队已将其 CAD 软件在浏览器中提供。Adobe 已使用 WebAssembly 使 Lightroom 可通过浏览器使用。

但是我们还需要进行一些功能上的改进,才能确保所有这些应用程序(即使是最重量级的应用程序)都能在浏览器中正常运行。

技能:多线程

首先,我们需要支持多线程。现代计算机有多个核心。这些基本上是多个可以同时处理问题的“大脑”。这样可以使事情运行得更快,但要利用这些核心,就需要支持线程。

技能:SIMD

除了线程之外,还有另一种利用现代硬件的技术,它可以让您并行处理事物。

那就是 SIMD:单指令多数据。使用 SIMD,可以将一块内存分成多个执行单元,这些单元有点像核心。然后,您拥有相同的代码——相同的指令——在所有这些执行单元上运行,但它们各自将该指令应用于它们自己的一部分数据。

技能:64 位寻址

WebAssembly 必须充分利用的另一个硬件功能是 64 位寻址。

内存地址只是数字,因此,如果您的内存地址只有 32 位长,您只能拥有那么多内存地址——足以容纳 4 千兆字节的线性内存。

但是使用 64 位寻址,您将拥有 16 泽字节。当然,您的计算机中并没有 16 泽字节的实际内存。因此,最大值取决于系统实际可以提供的内存量。但这将消除 WebAssembly 中对地址空间的人为限制。

技能:流式编译

对于这些应用程序,我们不仅需要它们运行速度快。我们还需要加载时间比现在更快。为了提高加载时间,我们需要一些特定的技能。

一个重要的步骤是进行流式编译——在 WebAssembly 文件仍在下载时对其进行编译。WebAssembly 的设计专门是为了实现简单的流式编译。在 Firefox 中,我们实际上编译它的速度——比它通过网络传入的速度还要快—— 这样,您下载文件时,编译基本完成。其他浏览器也正在添加流式功能。

另一个有帮助的事情是使用分层编译器。

对于 Firefox 中的我们来说,这意味着拥有两个编译器。第一个——基线编译器——在文件开始下载时立即启动。它可以快速编译代码,以便代码快速启动。

它生成的代码速度很快,但并非 100% 的速度。为了获得额外的性能,我们会在后台使用多个线程运行另一个编译器——优化编译器。这个编译器需要更长的时间来编译,但可以生成非常快的代码。完成之后,我们将基线版本替换为完全优化的版本。

这样,我们就可以使用基线编译器获得快速的启动时间,并使用优化编译器获得快速的执行时间。

此外,我们正在开发一个新的优化编译器,称为Cranelift。Cranelift 的设计旨在快速编译代码,在函数级别并行编译代码。同时,它生成的代码比我们当前的优化编译器性能还要好。

Cranelift 现在位于 Firefox 的开发版本中,但默认情况下处于禁用状态。启用它后,我们将更快地获得完全优化的代码,并且该代码将运行得更快。

但我们还有一个更好的技巧,可以让我们在大多数情况下不必编译……

技能:隐式 HTTP 缓存

使用 WebAssembly,如果您在两次页面加载中加载相同的代码,它将编译为相同的机器代码。它不需要根据流经它的数据进行更改,就像 JS JIT 编译器需要的那样。

这意味着我们可以将编译后的代码存储在 HTTP 缓存中。然后,当页面加载并开始获取 .wasm 文件时,它将改为从缓存中提取预编译的机器代码。这样,对于您之前访问过并已缓存的任何页面,都可以完全跳过编译步骤。

技能:其他改进

目前正在围绕其他方法进行很多讨论,这些方法可以改进它,跳过更多工作,敬请关注其他加载时间改进。

我们现在处于什么阶段?

我们现在在支持这些重量级应用程序方面处于什么阶段?

线程
对于线程,我们有一个提案,这个提案已经基本完成,但其中一个关键部分——SharedArrayBuffers—— 必须在今年早些时候在浏览器中关闭
它们将重新打开。关闭它们只是为了减少今年早些时候在 CPU 中发现并公开的 Spectre 安全问题的影響,但进展正在顺利进行,敬请期待。
SIMD
SIMD 目前正在积极开发中。
64 位寻址
对于wasm-64,我们已经对如何添加它有了一个很好的了解,它与 x86 或 ARM 如何获得对 64 位寻址的支持非常相似。
流式编译
我们在 2017 年底添加了流式编译,其他浏览器也正在开发它。
分层编译
我们在 2017 年底也添加了我们的基线编译器,其他浏览器在过去的一年中也一直在添加类似的架构。
隐式 HTTP 缓存
在 Firefox 中,我们即将推出对隐式 HTTP 缓存的支持。
其他改进
目前正在讨论其他改进。

尽管这一切仍在进行中,但您已经看到了这些重量级应用程序今天就出现了,因为 WebAssembly 已经为这些应用程序提供了它们所需的性能。

但是,一旦这些功能全部到位,这将是另一个里程碑,更多这样的重量级应用程序将能够进入浏览器。

与 JavaScript 交互的小模块

但是 WebAssembly 不仅仅用于游戏和重量级应用程序。它也适用于常规的 Web 开发…… 适用于人们习惯的 Web 开发类型:小模块的 Web 开发。

有时,您的应用程序中有一些小角落会进行大量繁重的处理,在某些情况下,使用 WebAssembly 可以更快地进行这些处理。我们希望简化将这些部分移植到 WebAssembly 的过程。

再说一次,这是一种已经发生的事情。开发人员已经将 WebAssembly 模块集成到有许多小型模块进行大量繁重工作的地方。

一个例子是 Firefox 的 DevTools 和 webpack 中使用的源映射库中的解析器。它使用 Rust 重写,编译为 WebAssembly,这使得它的速度提高了 11 倍。WordPress 的 Gutenberg 解析器在进行相同类型的重写后,平均速度提高了 86 倍

但是,要让这种使用真正得到广泛应用——让人们真正乐于使用它——我们需要一些额外的功能。

技能:JS 和 WebAssembly 之间的快速调用

首先,我们需要在 JS 和 WebAssembly 之间进行快速调用,因为如果你将一个小模块集成到现有的 JS 系统中,你很可能会需要在两者之间进行很多调用。所以你需要这些调用很快。

但是当 WebAssembly 最初发布时,这些调用并不快。这就是我们回到 MVP 的原因——引擎对两者之间的调用只有最基本的支持。它们只是让调用工作,并没有让它们变快。所以引擎需要优化这些。

我们最近在 Firefox 上完成了这方面的工作。现在一些调用实际上比非内联 JavaScript 到 JavaScript 调用要快。其他引擎也在努力改进这方面。

技能:快速、轻松的数据交换

这让我们想到了另一个问题。当你在 JavaScript 和 WebAssembly 之间进行调用时,你通常需要在它们之间传递数据。

你需要将值传递给 WebAssembly 函数,或者从它那里返回一个值。这也会很慢,而且也很困难。

有几个原因让它变得困难。一个原因是,目前 WebAssembly 只能理解数字。这意味着你不能将更复杂的值(如对象)作为参数传递进去。你需要将该对象转换为数字并将其放入线性内存中。然后你将线性内存中的位置传递给 WebAssembly。

这有点复杂。将数据转换为线性内存需要一些时间。所以我们需要让这个过程更容易、更快。

技能:ES 模块集成

我们还需要做的另一件事是与浏览器的内置 ES 模块支持进行集成。现在,你使用一个命令式 API 实例化一个 WebAssembly 模块。你调用一个函数,它会返回一个模块。

但这意味着 WebAssembly 模块并不是 JS 模块图的一部分。为了像使用 JS 模块那样使用导入和导出,你需要进行 ES 模块集成。

技能:工具链集成

仅仅能够导入和导出并不足以让我们实现这一切。我们需要一个地方来分发这些模块,并从那里下载它们,以及用于打包它们的工具。

WebAssembly 的 npm 是什么?那么 npm 呢?

WebAssembly 的 webpack 或 Parcel 是什么?那么 webpack 和 Parcel 呢?

这些模块对使用它们的人来说应该没有任何区别,所以没有必要创建单独的生态系统。我们只需要与它们集成的工具。

技能:向后兼容性

还有一件事我们必须在现有的 JS 应用程序中做得很好——支持旧版本的浏览器,即使是那些不知道 WebAssembly 是什么的浏览器。我们需要确保你不必用 JavaScript 编写一个完整的第二个模块实现,仅仅是为了支持 IE11。

我们在这方面进展如何?

那么我们在这方面进展如何?

 

JS 和 WebAssembly 之间的快速调用
JS 和 WebAssembly 之间的调用在 Firefox 中现在很快,其他浏览器也在努力改进这方面。
轻松、快速的数据交换
为了进行轻松、快速的数据交换,有一些提案可以帮助实现这一点。

正如我之前提到的,你必须使用线性内存来处理更复杂类型数据的其中一个原因是,WebAssembly 只能理解数字。它拥有的唯一类型是整数和浮点数。

使用引用类型提案,这种情况将会改变。该提案添加了一种新的类型,WebAssembly 函数可以将其作为参数进行接收,并返回。而这种类型是对 WebAssembly 外部对象的引用,例如 JavaScript 对象。

但是 WebAssembly 无法直接对该对象进行操作。要真正执行诸如调用其方法之类的操作,它仍然需要使用一些 JavaScript 胶水代码。这意味着它可以工作,但速度比所需的速度慢。

为了加快速度,我们提出了一项名为Web IDL 绑定提案。它允许 wasm 模块声明哪些胶水代码需要应用到它的导入和导出,以便不需要用 JS 编写胶水代码。通过将胶水代码从 JS 中拉入 wasm,当调用内置的 Web API 时,可以完全优化掉胶水代码。

我们可以让交互的另一个部分变得更容易。这与跟踪数据需要在内存中保留多长时间有关。如果你在线性内存中有一些 JS 需要访问的数据,那么你必须将其保留在那里,直到 JS 读取完这些数据。但是,如果你永远保留它,就会出现所谓的内存泄漏。你怎么知道什么时候可以删除数据?你怎么知道什么时候 JS 完成了对数据的处理?目前,你需要自己管理这些。

一旦 JS 完成了对数据的处理,JS 代码就必须调用类似于释放函数的东西来释放内存。但这很繁琐而且容易出错。为了让这个过程更容易,我们正在为 JavaScript 添加弱引用。有了它,你就可以观察 JS 端的对象。然后,当该对象被垃圾回收时,你可以在 WebAssembly 端进行清理。

所以这些提案都还在进行中。与此同时,Rust 生态系统创建了工具,可以为你自动完成所有这些工作,并为正在进行中的提案提供垫片。

有一个工具值得特别一提,因为其他语言也可以使用它。它被称为wasm-bindgen。当它看到你的 Rust 代码应该执行诸如接收或返回某些类型的 JS 值或 DOM 对象之类的操作时,它会自动为你创建执行此操作的 JavaScript 胶水代码,这样你就不必再费心考虑了。并且由于它使用的是一种与语言无关的方式编写,因此其他语言的工具链可以采用它。

ES 模块集成
对于 ES 模块集成,该提案已经走得相当远了。我们正在与浏览器供应商合作,开始实施它。
工具链支持
对于工具链支持,Rust 生态系统中有一些工具,例如wasm-pack,它可以自动运行打包代码以供 npm 使用所需的一切。打包器也在积极努力地支持它。
向后兼容性
最后,对于向后兼容性,有 wasm2js 工具。它接受一个 wasm 文件并输出等效的 JS。该 JS 不会很快,但至少这意味着它将在不支持 WebAssembly 的旧版浏览器中工作。

所以我们正在接近实现这一目标。一旦我们实现了它,我们就可以打开实现另外两个目标的道路。

JS 框架和编译为 JS 的语言

一个是使用 WebAssembly 重写 JavaScript 框架等的大部分内容。

另一个是让静态类型编译为 js 的语言可以编译为 WebAssembly——例如,让像Scala.jsReasonElm 这样的语言编译为 WebAssembly。

对于这两种用例,WebAssembly 需要支持高级语言特性。

技能:GC

我们之所以需要与浏览器的垃圾回收器集成,原因有以下几个。

首先,让我们看看重写 JS 框架的部分内容。这样做可能有很多好处。例如,在 React 中,您可以用 Rust 重写 DOM 差异算法,Rust 具有非常符合人体工程学的多线程支持,从而实现该算法的并行化。

您还可以通过不同的方式分配内存来加快速度。在虚拟 DOM 中,您可以使用特殊的内存分配方案,而不是创建一堆需要进行垃圾回收的对象。例如,您可以使用 bump 分配方案,它具有极低的分配成本和一次性释放功能。这可能会帮助您加快速度并减少内存使用量。

但您仍然需要从该代码中与 JS 对象(例如组件)进行交互。您不能只是不断地将所有内容复制进出线性内存,因为那样做很困难且效率低下。

因此,您需要能够与浏览器的 GC 集成,以便您可以使用由 JavaScript VM 管理的组件。一些这些 JS 对象需要指向线性内存中的数据,有时线性内存中的数据也需要指向 JS 对象。

如果最终产生了循环,则可能对垃圾回收器造成麻烦。这意味着垃圾回收器将无法判断对象是否仍在使用,因此它们永远不会被回收。WebAssembly 需要与 GC 集成,以确保这些跨语言数据依赖关系能够正常工作。

这也有助于将 JS 编译为静态类型语言,例如 Scala.js、Reason、Kotlin 或 Elm。这些语言在编译为 JS 时会使用 JavaScript 的垃圾回收器。由于 WebAssembly 可以使用相同的 GC(内置于引擎中的 GC),因此这些语言可以编译为 WebAssembly,并使用相同的垃圾回收器。它们不需要更改 GC 对它们的工作方式。

技能:异常处理

我们还需要更好的异常处理支持。

一些语言,例如 Rust,没有异常。但在其他语言中,例如 C++、JS 或 C#,异常处理有时被广泛使用。

您目前可以使用 polyfill 来实现异常处理,但 polyfill 会使代码运行速度非常慢。因此,当前编译为 WebAssembly 的默认做法是,在没有异常处理的情况下进行编译。

但是,由于 JavaScript 具有异常,即使您将代码编译为不使用异常,JS 也可能将一个异常抛入其中。如果您的 WebAssembly 函数调用了一个抛出异常的 JS 函数,那么 WebAssembly 模块将无法正确处理异常。因此,像 Rust 这样的语言在这种情况下会选择中止。我们需要让这种机制更好地工作。

技能:调试

使用 JS 和编译为 JS 语言的人们习惯于拥有良好的调试支持。所有主要浏览器的开发工具都让您能够轻松地逐步执行 JS。我们需要对在浏览器中调试 WebAssembly 提供相同级别的支持。

技能:尾调用

最后,对于许多函数式语言,您需要支持一项名为 尾调用 的功能。我不会过多地介绍这方面的细节,但基本上,它可以让您调用新函数,而无需在堆栈中添加新的堆栈帧。因此,对于支持此功能的函数式语言,我们希望 WebAssembly 也支持它。

我们在这方面进展如何?

那么我们在这方面进展如何?

垃圾回收
对于垃圾回收,目前正在进行两个提案

JS 的 Typed Objects 提案 和 WebAssembly 的 GC 提案。Typed Objects 将使您能够描述对象的固定结构。为此,有一个解释器,并且该提案将在即将举行的 TC39 会议上进行讨论。

WebAssembly GC 提案将使您能够直接访问该结构。该提案正在积极开发中。

有了这两项功能,JS 和 WebAssembly 都知道对象的结构,并且可以共享该对象并有效地访问存储在其中的数据。我们的团队实际上已经有了该功能的原型。但是,它们还需要一段时间才能完成标准化,因此我们预计可能要到明年才能实现。

异常处理
异常处理 仍处于研究和开发阶段,目前正在研究它是否可以利用前面提到的引用类型提案等其他提案。
调试
对于调试,浏览器开发工具目前提供了一些支持。例如,您可以在 Firefox 调试器中逐步执行 WebAssembly 的文本格式。但这还不理想。我们希望能够在您的实际源代码中显示您的位置,而不是在程序集代码中。为此,我们需要做的是弄清楚源映射(或类似源映射的功能)如何在 WebAssembly 中工作。因此,WebAssembly CG 的一个子组 正在努力制定规范。
尾调用
尾调用提案 也正在进行中。

一旦所有这些都到位,我们将解锁 JS 框架和许多编译为 JS 的语言。

所以,所有这些都是我们可以在浏览器内部实现的目标。但浏览器外部呢?

浏览器外部

现在,当我谈到“浏览器外部”时,您可能会感到困惑。因为浏览器不是您用来浏览网页的吗?而且这在名称中不是吗——WebAssembly。

但事实是,您在浏览器中看到的东西——HTML、CSS 和 JavaScript——只是构成网页的一部分。它们是可见的部分——它们是您用来创建用户界面的东西——所以它们是最显而易见的。

但网页的另一个非常重要的部分却具有不那么明显的属性。

那就是链接。它是一种非常特殊的链接。

这种链接的创新之处在于,我可以链接到您的页面,而无需将其放在中央注册表中,也无需询问您或甚至知道您是谁。我可以直接添加该链接。

正是这种轻松的链接方式,无需任何监督或审批瓶颈,才使得我们的网络得以发展。正是它使我们能够与不认识的人组成这些全球社区。

但如果我们只有链接,那么这里有两个问题我们还没有解决。

第一个问题是……您访问该站点,它会向您提供一些代码。它如何知道应该向您提供哪种代码?因为如果您在 Mac 上运行,那么您需要的机器代码与您在 Windows 上运行的机器代码不同。这就是为什么您在不同的操作系统上拥有不同版本的程序的原因。

那么,一个网站是否应该为每台可能的设备提供一个不同版本的代码?不。

相反,该站点有一个版本的代码——源代码。这就是提供给用户的代码。然后,它在用户的设备上被转换为机器代码。

这个概念的名称是可移植性。

所以,这很棒,您可以从不认识您也不认识您运行的是哪种设备的人那里加载代码。

但这也带来了第二个问题。如果您不认识那些提供您加载网页的人,那么您如何知道他们提供的代码是什么样的?它可能是恶意代码。它可能试图接管您的系统。

这种网页的愿景——运行来自您点击链接的任何人的代码——难道不意味着您必须盲目信任任何在网上的人吗?

这就是网页中另一个关键概念的用武之地。

那就是安全模型。我称之为沙箱。

基本上,浏览器会获取网页(即其他人的代码),并且不会让它在你的系统中任意运行,而是将其放入沙盒中。它会将一些不危险的玩具放到沙盒中,这样代码就可以执行一些操作,但会将危险的操作留置在沙盒之外。

因此,链接的效用基于以下两点:

  • 可移植性——将代码交付给用户并在任何可以运行浏览器的设备上运行的能力。
  • 以及沙盒——允许你运行代码而不会危及机器完整性的安全模型。

那么,这种区别为什么重要?如果我们将网页视为浏览器使用 HTML、CSS 和 JS 向我们展示的内容,还是将其视为可移植性和沙盒,这有什么区别呢?

因为它改变了你对 WebAssembly 的看法。

你可以将 WebAssembly 视为浏览器工具箱中的另一个工具… 它确实是。

它是浏览器工具箱中的另一个工具。但不仅仅如此。它还为我们提供了一种方法,可以将网页的这两种其他功能——可移植性和安全模型——应用于也需要它们的用例。

我们可以将网页扩展到浏览器的边界之外。现在让我们看看网页的这些属性在哪里会很有用。

Node.js

WebAssembly 如何帮助 Node?它可以为 Node 带来完全的可移植性。

Node 为你提供了网页上 JavaScript 的大部分可移植性。但有很多情况是 Node 的 JS 模块不够用——你需要提高性能或重用不是用 JS 编写的现有代码。

在这些情况下,你需要 Node 的原生模块。这些模块是用 C 等语言编写的,需要针对用户正在运行的特定类型的机器进行编译。

原生模块是在用户安装时进行编译的,或者预编译成适合各种不同系统的二进制文件。这两种方法中,一种对用户来说很麻烦,另一种对包维护者来说很麻烦。

现在,如果这些原生模块是用 WebAssembly 编写的,那么它们就不需要专门针对目标架构进行编译。相反,它们会像 Node 中的 JavaScript 那样运行。但它们会以接近原生的性能运行。

因此,我们获得了 Node 中运行代码的完全可移植性。你可以将完全相同的 Node 应用程序运行在所有不同类型的设备上,而无需进行任何编译。

但是,WebAssembly 无法直接访问系统的资源。Node 中的原生模块没有被沙盒化——它们可以完全访问浏览器将沙盒之外的所有危险玩具。在 Node 中,JS 模块也可以访问这些危险的玩具,因为 Node 使它们可用。例如,Node 提供了用于从系统中读取和写入文件的方法。

对于 Node 的用例来说,模块具有这种访问危险系统 API 的权限是有意义的。因此,如果 WebAssembly 模块默认情况下没有这种访问权限(就像 Node 的当前模块那样),我们如何才能让 WebAssembly 模块获得所需的访问权限?我们需要传入函数,以便 WebAssembly 模块可以与操作系统交互,就像 Node 处理 JS 一样。

对于 Node 来说,这可能包括像 C 标准库中包含的大量功能。它也可能包括 POSIX 的一部分——可移植操作系统接口——这是一个较旧的标准,有助于兼容性。它提供了一个用于跨各种不同类 Unix 操作系统与系统交互的 API。模块肯定需要大量类似 POSIX 的函数。

技能:可移植接口

Node 核心团队需要做的是确定要公开的一组函数以及要使用的 API。

但如果这实际上是一个标准化内容,岂不是很好吗?不是特定于 Node 的东西,而是可以跨其他运行时和用例使用的东西?

如果你是 WebAssembly 的 POSIX。一个 PWSIX?一个可移植的 WebAssembly 系统接口。

如果以正确的方式完成,你甚至可以将相同的 API 移植到网页上。这些标准 API 可以填充到现有的 Web API 上。

这些函数不会是 WebAssembly 规范的一部分。并且会有一些 WebAssembly 主机没有提供这些函数。但是,对于能够使用这些函数的平台,将有一个统一的 API 用于调用这些函数,无论代码在哪个平台上运行。这将使通用模块(跨网页和 Node 运行的模块)变得更加容易。

我们现在处于什么阶段?

那么,这真的有可能发生吗?

有几件事有利于这个想法。有一个名为 package name maps 的提议,它将提供一种机制,用于将模块名称映射到从其加载模块的路径。并且浏览器和 Node 都可能支持它,可以使用它来提供不同的路径,从而加载完全不同的模块,但具有相同的 API。这样,.wasm 模块本身可以指定一个在不同环境中都能正常运行的单个(模块名称,函数名称)导入对,即使是网页也是如此。

有了这种机制,剩下的就是实际确定哪些函数有意义以及它们的接口应该是什么。

目前还没有关于此的积极工作。但现在很多讨论都朝这个方向发展。并且看起来很可能发生,以某种形式或另一种形式。

这是件好事,因为解锁这一点让我们离解锁浏览器之外的其他用例又近了一步。有了它,我们可以加快步伐。

那么,这些其他用例的例子是什么呢?

CDN、无服务器和边缘计算

一个例子是 CDN、无服务器和边缘计算之类的东西。在这些情况下,你将代码放在其他人的服务器上,他们会确保服务器得到维护,并且代码离所有用户都很近。

为什么你想要在这些情况下使用 WebAssembly?最近有一个关于这方面的精彩演讲。

Fastly 是一家提供 CDN 和边缘计算的公司。他们的 CTO Tyler McMullen 这样解释道(我在这里改述了一下)

如果你观察一个进程的工作方式,该进程中的代码没有边界。函数可以访问进程中它们想要的任何内存,并且可以调用它们想要的任何其他函数。

当你在同一个进程中运行许多不同人的服务时,这是一个问题。沙盒可以解决这个问题。但你又会遇到一个规模问题。

例如,如果你使用 JavaScript VM(比如 Firefox 的 SpiderMonkey 或 Chrome 的 V8),你就会得到一个沙盒,并且可以将数百个实例放入一个进程中。但对于 Fastly 正在处理的请求数量来说,你需要的不仅仅是每个进程数百个——你需要数万个。

Tyler 在他的演讲中更详细地解释了这一切,所以你应该去看一看。但关键是,WebAssembly 为 Fastly 提供了这种用例所需的安全性、速度和可扩展性。

那么,他们需要做什么才能让它工作呢?

技能:运行时

他们需要创建自己的运行时。这意味着获取一个 WebAssembly 编译器(可以将 WebAssembly 编译成机器码的东西),并将它与我之前提到的用于与系统交互的函数组合起来。

对于 WebAssembly 编译器,Fastly 使用了 Cranelift,这也是我们正在构建到 Firefox 中的编译器。它被设计得非常快,而且内存使用量很低。

现在,对于与系统其他部分交互的函数,他们不得不创建自己的,因为我们还没有这样的可移植接口。

因此,今天可以创建您自己的运行时,但这需要一些努力。而且这种努力将在不同的公司之间重复。

如果我们不仅拥有可移植接口,而且还拥有一个可以在所有这些公司和其他用例中使用的通用运行时呢?这肯定能加快开发速度。

那么其他公司就可以像今天使用 Node 一样使用这个运行时,而不是从头开始创建自己的运行时。

我们在这方面进展如何?

那么,这方面的现状如何呢?

尽管目前还没有标准的运行时,但现在有几个运行时项目正在进行中。其中包括基于 LLVM 构建的 WAVM 和 wasmjit。

此外,我们正在计划一个基于 Cranelift 构建的运行时,叫做 wasmtime。

一旦我们拥有一个通用的运行时,这将加快许多不同用例的开发速度。例如……

可移植的 CLI 工具


WebAssembly 也可以用于更传统的操作系统。现在需要明确的是,我并不是指内核(尽管 勇敢的灵魂也在尝试这样做),而是指在 Ring 3 中运行的 WebAssembly - 在用户模式下。

然后,您可以做一些事情,比如拥有可以在所有不同类型操作系统上使用的可移植 CLI 工具。

这非常接近另一个用例……

物联网


物联网包括可穿戴设备和智能家居设备。

这些设备通常资源受限 - 它们的计算能力和内存都不大。这正是 Cranelift 这样的编译器和 wasmtime 这样的运行时能够发挥作用的地方,因为它们效率高、内存使用量低。在极度资源受限的情况下,WebAssembly 使得在设备上加载应用程序之前完全编译成机器代码成为可能。

还有这样一个事实,即有如此多的不同设备,而且它们都略有不同。WebAssembly 的可移植性将对此有所帮助。

所以这是 WebAssembly 未来发展方向的另一个地方。

结论

现在让我们后退一步,看看这棵技能树。

Skill tree showing WebAssembly skills which will be filled in through the course of the post.我在本文开头说过,人们对 WebAssembly 存在误解 - 认为出现在 MVP 中的 WebAssembly 是 WebAssembly 的最终版本。

我认为你现在应该明白为什么这是一种误解。

是的,MVP 开启了许多机会。它使将许多桌面应用程序带到网络成为可能。但是,我们还有许多用例需要解锁,从重量级桌面应用程序到小型模块,再到 JS 框架,以及浏览器之外的所有东西……Node.js、无服务器、区块链、可移植的 CLI 工具以及物联网。

所以我们今天拥有的 WebAssembly 并不是故事的结尾 - 这仅仅是开始。

关于 Lin Clark

Lin 在 Mozilla 的高级开发部门工作,专注于 Rust 和 WebAssembly。

更多 Lin Clark 的文章……

关于 Till Schneidereit

Till 在 Mozilla Research 的开发人员技术部门工作,并在 TC39 和 WebAssembly CG 中进行 JavaScript 和 WebAssembly 的标准化工作。

更多 Till Schneidereit 的文章……

关于 Luke Wagner

Luke Wagner 是 Mozilla 的软件工程师,负责 Firefox 中的 JavaScript 和 WebAssembly 开发。

更多 Luke Wagner 的文章……


29 条评论

  1. Ralf

    感谢这篇文章,像往常一样,特别感谢您对卡通的出色制作。:)

    到 WAVM 的链接似乎不对,它指向一个广播电台。(呵呵,说到可以链接到任何东西……;)

    2018 年 10 月 22 日 下午 10:10

    1. Lin Clark

      感谢您的提醒!它提醒了我们的编辑,他已修复了它 :)

      2018 年 10 月 23 日 上午 06:07

  2. DG

    这是我最近读过最好的技术写作示例之一。不仅仅因为它是对 WebAssembly 未来发展方向的令人兴奋的预览(事实确实如此),也不仅仅因为它很好地平衡了技术细节和高级概述(这也是真的)。

    我最喜欢这篇文章的地方是它的结构。它是技术写作的杰作。它将一个非常大的问题分解成易于理解的片段,以合理的顺序介绍它们,所有这些都构建成一个宏伟的结论,这个结论在事后看来是显而易见的,但在开始时是难以理解的。干得好!

    2018 年 10 月 22 日 下午 11:05

    1. WSRast

      我完全同意您的观点。这篇文章今天在我的收件箱中真是一个宝藏。我一直想更多地了解 WebAssembly,以及它的路线图,特别是关于前端 web 开发。这篇文章完全满足了我的需求!

      2018 年 10 月 25 日 上午 07:46

  3. Gaurav

    很棒的文章!

    如果在 Android FF 浏览器中使用 cranelift(以及相关的轻量级运行时)(当然只在浏览器沙箱中运行),它会提高整体性能(加载时间和执行时间都提高)吗?

    2018 年 10 月 22 日 下午 11:23

  4. Billy L

    EOS 区块链是基于 wasm 构建的。
    https://github.com/EOSIO/eos

    值得一提

    2018 年 10 月 22 日 下午 12:53

    1. David

      同意。非常高兴知道,谢谢。

      2018 年 10 月 24 日 上午 09:32

  5. DD

    哇,太棒的解释了!

    我不是程序员,所以我不太可能直接使用它或用它制作工具……但这令人兴奋的是,它可能会对 web 技术的许多领域产生影响!

    我认为,大量人(在一般的 web 技术开发社区中)开始研究和讨论 WebAssembly 的时间可能很快就会到来!

    祝所有参与者一切顺利!

    2018 年 10 月 22 日 下午 13:18

    1. DD

      我只想补充一点

      如果有人发现这篇文章的某个部分令人困惑,就像我有时会感到困惑一样,我发现文章中的其他链接非常有用;它们是添加了很多上下文的好文章。所以,如果这里有什么东西你不太理解,并且附近有一个链接,它可能是一个非常有帮助的链接!

      我正在学习很多关于 WebAssembly 的诞生过程、emscripten 和 asm.js 的知识……

      谢谢,各位!

      2018 年 11 月 8 日 上午 06:53

  6. Per

    感谢您写了这篇很棒的文章!

    2018 年 10 月 22 日 下午 14:39

  7. ZhendongWang

    你好。这是一篇关于 web assembly 的好文章。
    那么我可以把它翻译成中文并在中国技术 BBS 上分享吗?

    2018 年 10 月 22 日 下午 21:50

    1. Lin Clark

      是的,我很乐意看到一篇中文翻译。请确保在翻译中添加一个指向原文的链接。并且,在您发布翻译后,请在这里的评论中添加一个指向它的链接,以便其他人可以找到它 :)

      2018 年 10 月 23 日 上午 06:28

  8. Constantin

    这样说的话,听起来 WebAssembly 就像新的 Java。:)

    2018 年 10 月 23 日 上午 01:45

  9. Henrik

    对 web 应用程序的未来感到非常兴奋!在不久的将来,我们甚至可能会将浏览器视为操作系统。:)

    2018 年 10 月 23 日 上午 03:36

  10. Sebastian

    这是我读过的最简洁、最简单、信息量最大的技术文章。谢谢!

    2018 年 10 月 23 日 上午 07:17

  11. Daniel Hoffmann Bernardes

    很棒的文章,即使对于那些不是编译器专家的人来说(这在 web 开发中并不常见),它也能很好地解释一切。

    我对 risc-v 和 webassembly 感到好奇,我在一些 github 问题中看到过关于保持 webassembly 与 risc-v 兼容的内容。您认为我们会看到一个没有进行编译,而只是直接在硬件中运行 webassembly 的世界吗?就像旧的 lisp 机器或协处理器(比如 gpu)那样,它们更有效地运行 webassembly 吗?

    2018 年 10 月 23 日 上午 08:00

  12. Tyler prete

    谢谢,这真是太棒了。我对这项技术感到超级兴奋,并看到了它巨大的潜力!

    2018 年 10 月 23 日 下午 22:50

  13. Carl

    请原谅我的无知,但在这个技能树中,WebAssembly 直接与 DOM 交互的能力在哪里?我想我同时也在问,在树中哪个地方可以完全绕过或最大限度地减少 JavaScript 的使用?

    2018 年 10 月 24 日 下午 1:02

    1. Lin Clark

      这由“简单快速的 데이터 교환”涵盖。那里列出的两个主要提案将有助于解决这个问题,它们是引用类型提案和宿主绑定提案。

      2018 年 10 月 29 日 上午 10:29

  14. Aral Roca

    非常棒的文章。解释得很好。

    2018 年 10 月 25 日 上午 5:40

  15. Evan Summers

    很棒的文章,内容丰富且引人入胜 - 谢谢。

    2018 年 10 月 28 日 上午 1:05

  16. Miguel Useche

    很棒的文章!我喜欢你解释了关于 WASM 的所有我们需要了解的内容,以及它的未来发展方向。

    2018 年 10 月 28 日 下午 7:33

  17. Miguel Useche

    我喜欢这篇文章,它解释了关于 WASM 和其未来发展方向所需的一切。

    2018 年 10 月 28 日 下午 7:38

  18. David

    有点偏题了,但有人知道这个技能树是用什么软件制作的吗?设计很不错。

    2018 年 10 月 29 日 上午 5:50

    1. Lin Clark

      它是手绘的。我用 Photoshop 和 Wacom Cintiq 平板电脑。

      2018 年 10 月 29 日 上午 10:37

      1. David

        我害怕这样。很酷。

        2018 年 10 月 29 日 下午 12:08

  19. lain-dono

    > POSIX for WebAssembly

    看看这个:https://github.com/NuxiNL/cloudabi

    为什么?基于能力的安全,只有 49 个系统调用,为 Linux 和 FreeBSD 提供工作实现。

    2018 年 11 月 1 日 上午 6:25

  20. Mobeen Sarwar

    感谢你分享这篇有趣的文章!| 一如既往的优秀。它解释了关于 WASM 的所有内容。

    2018 年 11 月 8 日 上午 2:51

  21. Mobeen Sarwar

    感谢你分享这篇有趣的文章。

    2018 年 11 月 8 日 上午 2:55

本文的评论已关闭。