niayyy

WebAssembly 的最小可行产品后的未来: 卡通技能树

原文链接: hacks.mozilla.org

人们对 WebAssembly 有一个误解:他们认为 2017 年在浏览器中的 WebAssembly — 我们称为最低可行产品(MVP, minimum viable product)— 是 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(通过转换成 JavaScript 使 C++ 代码运行在浏览器上),这使得带有大量现有的 C++ 代码库 — 例如:游戏和桌面应用 — 运行在浏览器上成为可能。

但是,自动生成的 JS 代码显著地比原生代码慢。Mozilla 工程师开发了一个属于 JavaScript 子集的语言,指出了让 JavaScript 快速运行的方法,这个 JavaScript 的子集被命名为 asm.js

其他浏览器厂商看到 asm.js 的运行速度,他们也开始向自己的引擎中添加相同的优化。

但是,故事还没有结束。这只是刚刚开始。为了加快运行速度,引擎还可以做更多的事情。

但是不能依靠 JavaScript 语言自身。他们需要一种新的语言 — 专门为编译而设计的。然后 WebAssembly 诞生了。

那么第一版 WebAssembly 需要什么功能?我们需要什么才能使最低可行产品可以真正在 Web 上运行 C 和 C++?

技能:编译目标

https://p4.ssl.qhimg.com/t0170db43138580424e.png 从事于 WebAssembly 工作的人们知道,WebAssembly 不想只支持 C 和 C++,他们想要许多不同的语言都能够编译成 WebAssembbly。因此需要一个与语言无关的编译目标。

他们需要像“汇编语言”一样的东西,例如桌面应用程序被编译成 x86,但是这个“汇编语言”不适用于实际的,物理上的机器,而是对于概念性的机器。

技能:快速执行

https://p1.ssl.qhimg.com/t012b1233df2a35075b.png 为了可以快速的运行,必须设计编译目标。否则,运行在 web 上的 WebAssembly 应用程序不能保证用户对流畅交互和游戏玩法的期待。

技能:压缩

除了执行时间外,加载时间也必须是快的。用户对某些东西的加载有一定的期望。对于桌面应用程序,期望他们将快速的加载,因为他们已经安装在你的电脑上,对 Web 应用程序,期待加载时间也是快的,因为 Web 应用通常不用加载接近桌面应用那么多的代码。

当你组合二者的时候,事情就变得棘手了。桌面应用通常使用更大的代码库。因此如果他们在 Web 上,当第一次通过 URL 访问时,有许多代码需要下载和编译。

为了满足这些期望,我们需要我们的编译目标被压缩。以便它快速在 Web 上下载。

技能:线性内存

这些语言也需要能够用不同于 Javascript 使用内存的方式来使用内存。它们需要能够直接管理它们的内存 — 指出哪些字节在一起。

这是因为像 C 和 C++ 有叫做“指针”的低水平特性。你可以声明一个变量,里面没有值,代替的是这个值的内存地址。因此如果你打算支持指针,这个程序需要能够从特定的地址写和读。

但是你不能让下载的程序随意的获取内存中的字节,使用任何它们想使用的地址。因此为了创建一种安全获取内存的方式(像原生的程序那样),我们必须创造一个可以获取特定的内存部分的东西,而没有其他东西。

为了完成这些,WebAssembly 使用线性内存模型。这是用 TypedArrays 实现的。它基本上像一个 Javascript 数组,除了数组仅仅包含内存字节。当你从里面获取数据时,你只用使用数组索引(你可以把它们当成内存地址)。这意味着你可以假设这个数组为 C++ 内存。

解锁成就

拥有上面所有的技能,人们可以在你的浏览器上运行桌面应用程序和游戏,好像它们在电脑上原生地运行一样

这是 WebAssembly 作为最低可行产品发布必须的技能。它们确实是 MVP — 一个最低可行产品

这允许一些类型的应用程序可以工作,但是还有许多其他应用程序需要去解锁。

重量级的桌面应用

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

你可以想象一些应用像 Photoshop 运行在你的浏览器吗?是否可以像使用 Gmail 那样加载到任何设备?

我们已经开始看到这样的事情。例如:Autodesk 的 AutoCAD 团队已经使他们的 CAD 软件在浏览器上可用。Adobe 已经使用 WebAssembly 使 LIghtroom 可在浏览器上运行。

但是,我们需要一些其他功能去确保所有的这些应用程序 — 即使是最重量级的 — 可以很好的运行在浏览器上。

技能:多线程

首先,我们需要支持多线程。现代计算机有多个内核。它们基本上有多个大脑,可以同时工作处理问题。这样使得才处理速度更快,但是为了充分利用这些内核,你需要支持多线程。

技能:SIMD

除了线程外,还有另一种利用现代硬件的技术,使你能够并行的处理事务。

即 SIMD:单指令多数据(Single Instruction Multiple Data)。借助 SIMD,可以占用大块的内存,并分为不同的执行单元,这些不同执行单元像是内核。然后你让相同的代码位 — 相同的指令 — 运行在所有的这些执行单元,但是他们每个都将该指令应用于他们自己的数据位。

技能:64 位寻址

WebAssembly 需要充分应用的另一个硬件能力是 64 位寻址。

内存地址仅仅是数字。因此如果你的内存地址仅仅 32 位长,你则只能有那么多的内存地址 — 足以容纳 4GB 的线性内存。

但是使用 64 位寻址,你有 16EB 字节,当然,你的计算机的实际的内存达不到 16 EB。因此最大数量取决于系统实际提供给你多少内存。但这使 WebAssembly 不受人为的限制。

技能:流式编译

对于这些应用程序,我们不只需要他们快速运行。我们需要加载时间比以前更快。我们需要一些特别的技巧去缩短加载时间。

最好的是去做流式编译 — 在 WebAssembly 下载的同时进行编译。WebAssembly 专门设计能够轻松的流式编译。在 Firefox,我们实际上编译他的速度如此的快 — 比通过网络传输的速度更快 — 在下载文件的同时几乎很好的完成了编译。其他浏览器也正在添加流式编译。

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

在 Firefox 中,这意味着有两个编译器。第一个 — 流式编译器 — 在文件开始下载时立即启动。它快速编译代码,为了可以快速启动。

它产生代码速度很快,但是不到 100%。为了获得额外的性能,我们运行另一个编译器 — 优化编译器 — 运行在后台的一些线程中。这一个编译花费的时间更长,但是产生的代码运行更快。一旦完成,我们替换基准版本为完全优化的版本。

因此,我们我们通过流式编译器快速启动,通过优化编译器快速执行。

另外,我们正在开发一个新的优化编译器叫 Cranelift。Cranelift 通过功能级别的并行来快速编译代码。同时,它生成的代码比当前的优化编译器具有更好的性能。

Cranelift 现在可用于 Firefox 的开发版本,但是默认状态下不可用。一旦启用它,我们将更快的得到完全优化的代码,并且代码将运行的更快。

但是还有一个更好的技巧我们可以去做,这样我们不必大部分时间都在进行编译了......

技能:隐式 HTTP 缓存

使用 WebAssembly,如果你在两个页面加载相同的代码,它将编译相同的机器码,它不需要根据流动的代码进行更改,像 JS JIT 编译器做的那样。

这意味着我们可以存储编译过的代码在 HTTP 缓存中。然后当页面正在加载并获取 .wasm 文件时,它将从缓存中拉取预编译的机器码。对于你以及访问过的页面,这将完全跳过编译的步骤。

技能:其他改进

当前,为了跳过更多的工作,许多讨论是在围绕着其他方法进行改进。因此,请关注其他加载时间的改进。

Where are we with this?

我们现在在那些方面支持这些重量级的应用程序?

线程

对于线程,我们已经完成一个提案,但是其中一个关键的部分 — 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 第一次加载时,这些调用并不快。在这里我们回到整个最低可行产品的问题上来 — 这个引擎对于二者的调用支持很少。他们只能进行调用,不能保证快速的。因此,引擎需要优化这些。

最近,我们在 Firefox 上完成了此工作。现在,其中一些调用实际上比 non-inlined JavaScript 对 JavaScript 的调用更快,其他的引擎也正在这样做

技能:简单快速的数据交换

但是,这让我们想到另一件事情。当我们在 JavaScript 和 WebAssembly 之间调用时,通常需要在它们之间传递数据。

你需要把值传进 WebAssemby 函数中或者从中返回一个值。这可能很慢,也可能很困难。

有几个困难的原因。一是因为,WebAssembly 目前仅能理解数字。这意味着你不能传更复杂的值,像对象,作为参数。你需要去把对象转成数字,并放入线性内存。然后你传递给 WebAssembly 数字在线性内存中的位置。

这有点复杂。把数据转换成数字需要一些时间。因此我们需要它更容易,更快。

技能:ES 模块集成

我们需要做的另一件事情是整合浏览器的内置 ES 模块支持。现在你使用命令式 API 实例化一个 WebAssembly 模块。你调用一个函数,它为你提供一个模块。

但是这意味着 WebAssembly 模块实际上并不是 JS 模块图的一部分。为了像使用 JS 模块一样使用 importexport ,你需要去进行 ES 模块集成。

技能:工具链集成

但是,仅仅能够 importexport 不能让我们一帆风顺。我们需要一个地方去分发这些模块,并从中下载它们以及捆绑在一起的工具

用于 WebAssembly 的 npm 呢?那么,那 npm 呢?

用于 WebAssembly 的 webpack 和 Parcel 呢?那么,Webpack 和 Parcel 呢?

这些模块与使用他们的人看起来没有什么不同,因此,没有理由去创造一个独立的生态。我们仅仅需要与它们集成的工具。

技能:向后兼容

在现有的 JS 应用程序中,还有一件事情我们需要去做好 — 支持更老版本的浏览器,尽管那些浏览器不知道 WebAssembly 是什么。我们需要确保你不必为了支持 IE11,而对你在 JavaScript 中的模块整个进行重写。

Where are we on this?

那么,我们现在到哪了?

JS 和 WebAssembly 之间快速的调用

在现在的 firefox 中, JS 和 WebAssembly 之间可以快速调用,其他浏览器正在为此工作。

简单快速的数据交换

为了简单快速的数据交换,有一些提案将解决这个问题。

正如我之前提到的,对于更复杂类型的数据必须使用线性内存的一个原因是:因为 WebAssembly 只能理解数字。它仅有 ints 和 floats 类型。

随着引用类型提案的提出,这将改变。这个提案添加了一些 WebAssembly 函数可以用来作为参数和返回的新类型。这种类型是对 WebAssembly 外部对象的引用 — 例如,一个 JavaScript 对象。

但是 WebAssembly 不能直接操作这个对象。实际上要做一些事情,像用它调用一个方法。他仍将需要用一些 JavaScript “胶水”。这意味着他可以工作,但是比需要的速度要慢。

为了加快速度,还有一个叫做主动绑定的提案。我们在 wasm 模块中声明我们在导入和导出时用到的“胶水”,这样“胶水”就不必用 JS 编写。通过把 JS 转成 wasm,当你调用内置 Web APIs 时完全优化“胶水”。

我们可以简化交互的另一部分。这和追踪数据需要在内存中保存多长时间有关。如果 JS 需要获取一些线性存储器中的数据,则必须保留这些数据,直到 JS 读取完为止。但是如果你永远保留,将会发生内存泄漏。如何知道何时可以删除数据?如何知道 JS 使用完成?当前,你必须自己进行管理。

一旦 JS 使用完数据,JS 代码必须调用像 free 函数之类的东西来释放内存。但这是乏味且易出错的。为了使过程更容易,我们把 weekRefs 添加到了 JavaScript。这样,就可以在 JS 端观察对象。然后当这个对象被垃圾回收时,你可以在 WebAssembly 端进行清理。

因此这些提案都在进行中。同时,Rust 生态系统已经创造了工具可以自动化的完成,polyfill 这个进行中的提案。

值得一提的是,因为其他语言也在使用。被叫做 wasm-bindgen。当看到你的 Rust 代码需要接收或者返回默写类型的 JS 值或者 DOM 对象时,它将自动创造一个 JavaScript “胶水”代码,因此你无需考虑它。因为它是语言无关的,所以其它语言的工具链也可以采用它。

ES 模块集成

对于 ES 模块集成,这个提案还很遥远。我们正在和浏览器厂商合作去实现它。

工具链支持

对于工具链支持,在 Rust 生态中有一些像wasm-pask之类的工具,能够自动运行将代码打包成 npm 所需要的一切。打包工具也正在积极地提供支持。

向后兼容

最后,为了向后兼容,有 wasm2js 工具。它把一个 wasm 文件转化成等效的 JS 文件。JS 运行的不是很快,但是至少意味着它将在不理解 WebAssembly 的旧版浏览器上可以运行。

因此我们将解锁这个惩戒。一旦我们解锁它,我们将打开另外两条路径。

JS 框架和编译成 JS 语言

一条是通过 WebAssembly 重写像 JavaScript 框架中的大部分内容。

另一条路是使可编译成 js 的静态类型语言可以编译成 WebAssembly,例如: Scala.jsReason 或者 Elm 编译成 WebAssembly。

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

技能:垃圾回收器

由于以下原因我们需要与浏览器的垃圾回收器集成。

首先,让我们看一下 JS 框架的重写部分。这可能有几方面的好处。例如,在 React 中,可以做的一件事就是用 Rust 重写 DOM diffing 算法,将具有多线程的支持,并且可以优化该算法。

你也可以通过分配不同的内存来加快速度。在虚拟 DOM 中,可以使用特殊的内存分配方案去代替创造一堆进行垃圾回收的对象。例如,你可以使用凹凸内存分配方案,该方案具有非常简单的分配和一次性分配。这可能有助于加快处理速度并减少内存使用。

但是你也需要与代码中的 JS 对象 — 像组件 — 进行交互。你不能只把所有内容复制进出线性内存,因为这将是困难且效率低下的。

因此你需要能够与浏览器的垃圾回收进行集成,这便于通过 JavaScript 虚拟机进行管理。有些 JS 对象需要指向线性存储器中的数据,有时线性存储器中的数据需要指向 JS 对象。

如果这最终导致循环,则可能对垃圾收集器造成麻烦。这意味着垃圾收集器将无法判断对象是否已被使用,因此将永远无法对其进行回收。WebAssembly需要与垃圾回收器进行集成,以确保这些跨语言数据依赖能够正常工作。

这也将有助于可编译为 JS 的静态类型语言,例如 Scala.js,Reason,Kotlin 或者 Elm。这些语言在编译为JS时会使用JavaScript的垃圾回收器。因为 WebAssembly可以使用相同的垃圾回收器(即内置在引擎中的垃圾回收器),所以这些语言将能够编译为 WebAssembly 并使用相同的垃圾收集器。他们不需要更改垃圾回收器。

技能:异常处理

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

某些语言,像 Rust,不用处理异常。但是在其他语言中,例如 C ++,JS 或 C#,有时会广泛使用异常处理。

您当前可以 polyfill 异常处理,但是 polyfill 使代码运行得非常慢。因此,当前编译为 WebAssembly 时的默认设置是不进行异常处理。

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

技能:调试

使用 JS 和可编译为 JS 语言的人们习惯的另一件事是良好的调试支持。所有主流浏览器中的 Devtools 均可轻松调试 JS。我们需要在浏览器中对调试 WebAssembly 有相同级别的支持。

技能:尾调用

最后,对于许多函数式语言,你需要支持称为尾调用的功能。我不会对此进行详细介绍,但是基本上,它可以让您调用新函数而无需在堆栈中添加新的堆栈帧。因此,对于支持此功能的语言,我们希望WebAssembly也支持它。

Where are we on this?

那么我们在哪里呢?

垃圾回收

对于垃圾回收,目前有两个提案:

JS 的“类型对象”提案和 WebAssembly 的垃圾回收提案。类型对象将用于描述对象的固定结构。对此有一个解释器,这个提案将在即将举行的 TC39 会议上进行讨论。

WebAssembly 垃圾回收提案将使直接访问该结构成为可能。该提案正在积极制定中。

有了这两个选项,JS 和 WebAssembly 都知道对象的结构,并可以共享该对象并有效地访问存储在里面的数据。我们的团队实际上已经有了这项工作的原型。但是,这些仍需要一些时间才能通过标准化,因此我们可能会在明年的某个时候进行研究。

异常处理

异常处理仍处于研发阶段,现在正在研究它是否可以利用其他提案,例如我之前提到的引用类型建议。

调试

对于调试,当前浏览器的 devtools 提供了一些支持。例如,您可以在 Firefox 调试器中逐步浏览 WebAssembly 的文本格式,但这仍然不理想。我们希望能显示在实际源代码中的位置,而不是程序集中的位置。为此,我们需要做的事情就是弄清楚WebAssembly 的源映射(或源映射类型的事物)如何工作。因此,有一个WebAssembly CG 的子小组致力于这一点。

尾调用

尾调用提案也正在进行中。

一旦所有这些都准备就绪,我们将解锁 JS 框架和许多可编译为 JS 的语言。

因此,这些都是我们可以在浏览器中解锁的成就。但是在浏览器之外呢?

Outside the Browser

现在,当我谈论“浏览器外部”时,你可能是困惑的。因为浏览器不是你用来浏览网页的吗?WebAssembly 的名称并不准确。

但事实是,您在浏览器中看到的内容(HTML,CSS 和 JavaScript)只是构成网页的一部分。它们是可见的部分 — 它们是你用来创建用户界面的部分 — 因此它们是最易观察到的。

但是网页中还有一个非常重要的部分,它的属性不是那么明显的。

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

该链接的创新之处在于,我可以链接到您的页面,而不必将其放在中央注册表中,也不必询问您甚至不知道你是谁。我只用把链接放在那。

正是这种简单的链接,没有任何监督或批准的瓶颈,使我们的网站成为可能。这就是使我们能够与不认识的人组成这些全球社区的原因。

但是,如果我们都是链接,那么这里有两个我们尚未解决的问题。

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

那么网站是否应该为每种可能的设备使用不同版本的代码?不。

相反,该站点具有一个版本的代码 — 源代码。这就是交付给用户的东西。然后在用户设备上倍转换成机器码。

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

因此,这很好,您可以从不认识你并且不知道你运行在哪种设备的人那里加载代码。

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

网络的愿景(运行你所链接的任何人的代码)是否意味着你必须盲目地信任网络上的任何人?

这是网络上另一个关键概念。

那就是安全模型。我将其称为沙盒。

基本上,浏览器获取页面(该页面是他人的代码),并将放在沙盒中,而不是让它在您的系统中随意运行。它将一些不危险的玩具放入该沙盒中,以便代码可以执行某些操作,但将危险的东西留在了沙盒之外。

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

  • 可移植性 — 能够将代码交付给用户并使其运行在可以运行浏览器的任何类型的设备上。

  • 沙盒 — 可让您运行代码而不会危及计算机完整性的一种安全模型。

那么为什么这种区别很重要?如果我们将网页视为浏览器使用 HTML,CSS 和 JS 向我们展示的内容,或者如果我们就可移植性和沙盒而言,我们为什么认为网络会有所不同?

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

您可以将 WebAssembly 视为浏览器工具箱中的另一种工具......

它是浏览器工具箱中的另一个工具。不仅如此。它还为我们提供了一种方式去调用 Web 的其他两项功能(可移植性和安全性模型)并将其用于其他也需要它们的用例中。

我们可以将 Web 扩展到浏览器之外。现在,让我们看看 Web 的这些属性在哪里有用。

Node.js

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

Node 可为您提供大部分 JavaScript 在 Web 上的可移植性。但是在很多情况下,Node 的 JS 模块还不够用 — 您需要提高性能或重用未用 JS 编写的现有的代码。

在这些情况下,您需要 Node 的原生模块。这些模块使用 C 之类的语言编写,并且需要针对用户所运行的不同种类的计算机进行编译。

原生模块可以在用户安装时编译,也可以预编译成二进制文件以用于各种不同系统。这些方法是用户的痛苦之一,另一方面也是包的维护者的痛苦。

现在,如果这些原生模块改用 WebAssembly 编写,则不需要为目标体系结构专门编译它们。相反,它们就像在 Node 中运行的 JavaScript 一样。但是他们会以接近原生的性能做到这一点。

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

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

对于Node的用例,使模块具有对危险系统 APIs 的这种访问权在一定程度上是有意义的。因此,如果 WebAssembly 模块默认不具有这种访问权限(如Node的当前模块一样),我们如何为 WebAssembly 模块提供所需的权限?我们需要传入函数,以便 WebAssembly 模块可以与操作系统一起使用,就像 Node 用 JS 做的那样。

对于 Node,这可能包括 C 标准库之类的许多功能。它还可能包括 POSIX (便携式操作系统接口)中的内容,它有助于兼容较早标准。它提供了一个API,可以在一系列不同的类 Unix 操作系统中与系统进行交互。模块肯定需要一堆类似 POSIX 的功能。

技能:便携式接口

Node 核心人员需要做的是弄清楚要公开的功能集和要使用的 API。

但是,如果这实际上是某种标准,那不是很好吗?不是只针对 Node 的东西,还可以在其他运行时和用例中使用吗?

如果愿意,可以使用 POSIX for WebAssembly。一个 PWSIX?一个便携式WebAssembly 系统接口。

如果以正确的方式完成操作,您甚至可以为 Web 实现相同的 API。这些标准 API 可以 polyfill 到现有的 Web APIs 上。

这些功能将不属于 WebAssembly 规范。而且所有 WebAssembly 应用将使他们不可用。但是对于那些可以使用它们的平台,无论代码在哪个平台上运行,都将有一个统一的 API 来调用这些功能。这将使通用模块(可同时在 Web 和 Node 上运行)变得如此简单。

Where are we with this?

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

有几件事正在进行,通过这个想法的支持。有一个名为包名称映射的提案,它将提供一种将模块名称映射到加载模块的路径的机制。可能被浏览器和 Node 支持,它们可以使用它来提供不同的路径,从而使用相同的 API 加载完全不同的模块。这样,.wasm 模块本身可以指定一个可以在不同环境(甚至是 Web)上运行的单个(模块名称,函数名称)导入对象。

有了这种机制,实际上要做的就是弄清楚哪些功能有意义,以及它们的接口应该是什么。

目前尚无任何积极的工作。但是,现在有很多讨论都朝着这个方向发展。它看起来很可能以一种或另一种形式发生。

这样是好的,因为解锁这个可以使我们在浏览器之外解锁其他一些用例。有了这个,我们可以加快步伐。

因此,其他用例的示例是什么?

CDNs,无服务器云函数和边缘计算

一个例子就是CDNs,无服务器云函数和边缘计算。在这些情况下,你要将代码放在其他人的服务器上,并确保服务器得到维护,并且代码对于所有用户容易获取。

在这些情况下,为什么你想要使用 WebAssembly?最近在一次会议上有一个精彩的演讲,对此做了确切的解释。

Fastly 是一家提供 CDNs 和边缘计算的公司。他们的 CTO 泰勒·麦克穆伦(Tyler McMullen)以这种方式解释了这一点(我在这里释义):

如果您查看工作流程,那么该流程中的代码没有边界。函数可以访问该过程中所需的任何内存,并且可以调用所需的其他任何函数。

当您在同一过程中运行许多不同的人的服务时,则有一个问题。沙盒可能是解决此问题的一种方法。但是随后您遇到了规模问题。

例如,如果使用JavaScript虚拟机(例如 Firefox 的 SpiderMonkey 或 Chrome 的 V8),则会得到一个沙箱,并且可以将数百个实例放入一个进程中。但是,由于Fastly 正在处理的请求数量众多,因此每个进程不只需要数百个,而是需要成千上万个。

Tyler 在演讲中对所有内容的解释都做得更好,因此您应该去观看它。但关键是WebAssembly 提供给 Fastly 此用例所需的安全性,速度和规模。

那么,他们需要做些什么呢?

技能:运行时

他们需要创建自己的运行时。这意味着要使用 WebAssembly 编译器(可以将WebAssembly 编译为机器代码的东西),并将其与我之前提到的与系统进行交互的功能结合起来。

对于 WebAssembly 编译器,Fastly 使用 Cranelift,这也是我们在Firefox中构建的编译器。它的速度非常快,并且不占用太多内存。

现在,对于与系统其余部分交互的功能,他们必须创建自己的功能,因为我们还没有该可移植接口。

因此,今天可以创建自己的运行时,但这需要一些努力。这项工作必须在不同公司之间重复进行。

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

然后,其他公司可以像当前使用 Node 一样来使用该运行时,而不是从头开始创建自己的运行时。

Where are we on this?

那么,这是什么状态?

即使还没有标准的运行时,现在也有一些运行时项目在进行中。包括 WAVM,这是建立在 LLVM 和 wasmjit 的顶部。

另外,我们正在计划在 Cranelift 之上构建的运行时,称为 wasmtime。

一旦有了通用的运行时,就可以加速开发各种不同用例的过程。例如…...

可移植 CLI 工具

WebAssembly 也可以在更传统的操作系统中使用。现在要弄清楚,我不是在谈论关于内核(尽管勇敢的人也在尝试),而是在用户模式下以 Ring 3 运行WebAssembly。

然后,您可以做一些事情,例如拥有可在所有不同种类的操作系统中使用的便携式 CLI 工具。

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

物联网

物联网包括可穿戴技术和智能家电等设备。

这些设备通常受资源限制 — 它们没有太多的计算能力,也没有太多的内存。而这恰恰是一种情况,例如 Cranelift 之类的编译器和 wasmtime 之类的运行时喜欢的,因为它们是高效且低内存的。而且,在资源极度受限的情况下,WebAssembly 可以在将应用程序加载到设备上之前完全编译成机器代码。

还有一个事实是,有许多不同的设备,它们都略有不同。WebAssembly 的可移植性对此非常有帮助。

因此,这是 WebAssembly 拥有未来的另一个地方。

Conclusion

现在,让我们缩小并查看此技能树。

Skill tree showing WebAssembly skills which will be filled in through the course of the post.我在这篇文章的开头说过,人们对 WebAssembly 有一个误解 — 这种想法是,WebAssembly 的最低可行产品是 WebAssembly 的最终版本。

我想您现在可以明白为什么这是一个误解。

是的,最低可行产品开辟了很多机会。它使得将许多桌面应用程序带到 Web 上成为可能。但是我们仍然有许多用例可以解锁,从重量级的桌面应用程序到小型模块,JS 框架,再到浏览器之外的所有东西……Node.js,无服务器云函数,区块链,可移植的 CLI 工具,以及物联网。

因此,我们今天拥有的 WebAssembly 并非故事的结局 — 而仅仅是开始。