【WebAssembly 的未来】成长技能树(下)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hj7jay/article/details/84935804

技能: 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是什么的浏览器。 我们需要确保:在需要支持IE11时,你不必在JavaScript中编写相关模块的第二次完整实现。

对于这方面我们在什么位置?

那么在这些方面我们处于什么状态呢?

JS 和 WebAssembly 之间的快速调用

JS 和 WebAssembly 之间的调用目前在 Firefox 上是快速的,并且其他浏览器也在处理此问题。

简便快速的数据交换

为了实现简便快速的数据交换,目前有一些提案可以帮助解决这个问题。
正如我之前提到的,你必须使用线性内存来处理更复杂的数据类型的一个原因是因为 WebAssembly 仅理解数值。它拥有的唯一类型是整数和浮点数。
在引用类型提案中,这将有所改变。该提案添加了一个新类型,WebAssembly 函数可将其作为参数和返回值。此类型是对 WebAssembly 外部对象的引用 - 例如,JavaScript 对象。
但 WebAssembly 无法直接操作此对象。要实际执行诸如调用方法之类的操作,它仍然需要使用一些 JavaScript 胶水代码。这意味着它可以运行,但它比它被需要的速度慢。
为了加快速度,有一项我们曾称之为主机绑定的提案。它让 wasm 模块声明必须将哪些胶水代码应用于在导入和导出之上,这样胶水代码就不需要用 JS 编写。通过将 JS 中的胶水代码用到 wasm 中,在调用内置 Web API 时可以完全优化此胶水代码。
我们可以更轻松地完成交互的另一部分。这就和跟踪数据在内存中需要保留多长时间有关。如果你在 JS 中需要访问线性内存中一些数据,那么你必须把它保留在那里直到 JS 读取数据为止。但如果你把它永远保留在那里,你会得到所谓的内存泄漏。你怎么知道何时可以删除数据呢?你怎么知道 JS 什么时候完成访问呢?目前,你必须自己管理它们。
 
一旦 JS 处理完这些数据,JS 代码就必须调用类似 free 函数的东西来释放内存。但这很冗余并且极易出错。为了简化此过程,我们将 WeakRef 添加到 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.js、Reason 或 Elm 等语言可编译为 WebAssembly。

技能:GC

出于几个原因,我们需要与浏览器的垃圾回收器进行集成。

首先,让我们看看重写 JS 框架的部分内容。出于几个原因,这可能是好事。例如,在 React 中,你可以做的一件事是在 Rust 中重写 DOM diffing 算法,它具有非常符合人体工程学的多线程支持,并且并行化了此算法。

你也可以通过不同地分配内存来加快其速度。在虚拟 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#,异常处理有时会被广泛使用。

你目前可以填加异常处理逻辑,但这些添加会使代码运行得非常慢。因此,在编译到 WebAssembly 时其默认值是在没有异常处理的情况下编译的。

但是,由于 JavaScript 中存在异常,即使你编译代码时不使用异常,JS 也可能会抛出一个异常。如果 WebAssembly 函数调用了抛出异常的 JS 函数,则 WebAssembly 模块将无法正确处理异常。 因此像 Rust 这样的语言在这种情况下会选择中止运行。我们需要让它做得更好。

技能: 调试

使用JS和编译到JS语言的人们习惯拥有的另一件事是良好的调试支持。所有主流浏览器中的Devtools都可以轻松地逐步调试JS。我们需要同样级别的支持以在浏览器中调试WebAssembly。

技能: 尾调用(Tail calls)

最后,对于许多函数式语言来说,你需要支持称为尾调用的东西。 我不打算详细介绍这个细节,但基本上它可以让你调用一个新函数,而无需在堆栈中添加新的栈帧。因此,对于支持此功能的函数式语言,我们希望WebAssembly也支持它。

对于这些我们处于什么状态?

那么在此我们处于什么状态呢?

垃圾回收

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

JS的Typed Objects提案和WebAssembly的GC提案。Typed Object可以实现对Object固定结构的描述。对此有一篇解释文章,并且该提案将在即将召开的TC39会议上讨论。
WebAssembly GC 提案使得直接访问该结构成为可能。该提案正在积极撰稿中。

这两个功能完成之后,JS和WebAssembly都将知道对象是什么样子的,并且可以共享该对象并高效地访问存储在其上的数据。 我们的团队实际上已经完成了这个工作的原型。但是,这仍需要一段时间才能通过标准化,所以我们可能会在明年的某个时候审视之。

异常处理

异常处理目前仍处于研究和开发阶段,目前正有人在分析是否可以参考其他提案,例如我之前提到的引用类型的提案。

调试

对于调试,在浏览器devtools中目前有一些支持。例如,你可以在Firefox调试器中单步执行WebAssembly的文本格式。但它仍然不理想。我们希望能够展示你在实际源码中的具体位置,而不是在assembly中。我们需要做的就是弄清楚源映射 - 或一个源映射类型的事物 - 如何在WebAssembly中工作的。因此,WebAssembly CG的一个小组正在从事细化它。

尾调用

尾调用提案也在进行中。

一旦完成了这些功能,我们将解锁JS框架和许多编译到JS的语言。

浏览器之外

现在,当我谈到“浏览器之外”时,你可能会感到困惑。因为难道不是用来查看网页的浏览器吗? 并且这个名字-WebAsmbly难道不是正确的吗。

但真相是你在浏览器中所看到的东西 - HTML,CSS和JavaScript - 只是网络构成的一部分。它们是可见的部分 - 它们是你用来创建用户界面的技术 - 因此它们是最明显的。

但是,网络中另一个非常重要的部分具有不可见的属性。

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

这个链接的创新之处在于我可以链接到你的页面而无需将其放在中央注册表中,并且无需询问你甚至不知道你是谁。我可以仅把那个链接放在那。

这正是链接的简易性,没有任何监管或批准瓶颈,使我们的网络成为可能。这就是我们与不认识的人建立这些全球社区的原驱动力。

但如果我们所拥有的仅是链接,那么我们有两个问题尚待解决。

第一个问题是......你在访问这个网站时,它会为你提供一些代码。它如何知道应该为你提供哪类代码?因为如果你在Mac上运行,那么你需要的机器代码与在Windows上是不同的。这就是为什么你在不同的操作系统上使用不同版本的程序。

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

相反,该网站有单一版本的代码 - 源代码。这是交付给用户的代码。然后它被转译为用户设备上的机器代码。

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

这很棒,你可以从不认识你的,也不知道你正在运行什么样的设备的人那里加载代码。

但这带来了第二个问题。如果你不知道你正在加载的网页来自于何人,你怎么知道他们给你的代码是什么类型的? 它可能是恶意代码。它可能试图接管你的系统。

是否说这个网络愿景——运行来自你所遵循的任何人的代码——意味着你必须盲目地信任网络上的任何人?

这是网络上其他核心概念的用武之地。

它是安全模型。我打算把它称为沙箱。

基本上,浏览器获取页面 - 其他人的代码,并且不是让它在你的系统中随意地运行,而是将其放到沙箱中。它会将一些不危险的操作放入沙箱中,以便代码可以执行某些操作,但它会将危险的东西留在沙箱之外。

所以链接的实用性是基于以下两点的:

  • 可移植性 - 向用户分发代码并使其可运行在任意类型设备上的浏览器的能力。

  • 沙箱 - 一种安全模型,可让你在不会危及机器的完整性的情况下运行代码。

那么为什么这种区别很重要呢? 如果我们将Web视为浏览器使用HTML、CSS和JS向我们展示某些东西,或者如果我们从可移植性和沙箱方面审视Web,那这为什么会有所不同呢?

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

你可以将WebAssembly视为浏览器工具箱中的另一个工具......它本是如此。

它是浏览器工具箱中的另一个工具。但这不仅仅是这些。它还为我们提供了一种方法来获取 Web 另外的两个功能 - 可移植性和安全性模型 - 并将它们用到需要它们的其他用例中。

我们可以将网络扩展到浏览器之外。现在让我们来看看这些网络属性在何处有用。

Node.js

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

Node 为你提供了 Web 上大部分 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 标准库中的功能。它还可能包括 POXSIX 的一部分 —— 便携式操作系统接口 —— 这是一种较早的有助于兼容性的标准。它提供了一个 API,用于跨一组不同的类 unix 操作系统与系统交互。模块肯定需要一些类似 posix 的函数。

技能:便携式接口

Node 核心人员需要做的是找出要公开的函数集和要使用的 API。

但如果这是标准的,不是很好吗?不是特定于 Node 的东西,但是也可以跨其他运行时和用例使用?

如果你愿意,可以使用 POSIX for WebAssembly。PWSIX?是的,一个可移植的 WebAssembly 系统接口。

如果用正确的方法,你甚至可以为 Web 实现相同的 API。这些标准 API 可以被填充到现有的 Web API 中。

这些函数不会是 WebAssembly 规范的一部分,而且会有 WebAssembly 的主机无法使用它们。但是对于那些可以使用这些函数的平台,不管代码运行在哪个平台上,都将有一个统一的 API 来调用这些函数。这将使通用模块 —— 同时在 Web 和 Node 上运行的模块变得更加容易。

拥有这些之后我们目前处于什么状态?

那么,这是否真的会发生呢?

有一些事情正在朝着此想法努力。有一个名为包名映射的提案,将提供一种机制,用于将模块名称映射到加载对应模块所在的路径上。这可能会同时得到浏览器和 Node 的支持,因此可以使用它在相同 API 中提供不同的路径以加载完全不同的模块。这样,.wasm 模块本身可以指定一个(模块名,函数名)导入对,它可以在不同的环境甚至 Web 上运行。

有了这种机制之后,剩下要做的就是弄清楚哪些函数是意义的以及它们的接口应该是什么。

目前对此还没有相关研究。但目前已有很多朝着此方向发展的探讨。它看起来很可能以某种形式正在发生中。

还有一点好处是,因为解锁这个功能让我们处在解锁浏览器之外的其他一些用例的半道中。并且有了这个之后,我们就可以加快步伐。

CDNs, Serverless, 以及边缘计算

其中一个例子是诸如 CDN、Serverless 和边缘计算等事物。在这些情况下,你将代码放到他人的服务器上,并由其确保该服务器的维护并保证代码放到所有用户临近的地方。

技能: 运行时

他们需要创建自己的运行时。这意味着使用 WebAssembly 编译器 - 可以将 WebAssembly 编译为机器码 - 并将其与用在与我之前提到的系统交互的函数组合。

对于 WebAssembly 编译器,Fastly 使用 Cranelift ,我们在构建 Firefox 时也使用此编译器。它被设计为非常快速,并且不会占用太多内存。

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

因此,目前是可以创建自己的运行时的,但需要花费一些投入。它是必须在不同公司之间重复的投入。

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

如此,其他公司可以使用这个运行时 - 就像他们目前使用 Node 一样 - 而不需要从头开始创建自己的运行时。

便携式 CLI 工具

WebAssembly 也可以在更传统的操作系统中使用。现在需要说明的是,我不是在内核中讨论(尽管勇敢的人也在尝试),而是在 Ring3 用户模式下运行的 WebAssembly。

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

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

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

这些设备通常是资源受限的 —— 它们没有太多的计算能力,也没有太多的内存。这种情况下,像 Cranelift 这样的编译器和 wasmtime 这样的运行时会很出色,因为它们效率高,内存低。在资源极度受限的情况下,WebAssembly 可以在将应用程序加载到设备之前完全编译成机器码。

还有一个事实是,目前有很多不同的设备,它们都略有不同。WebAssembly 的可移植性确实有助于这一点。

这就是 WebAssembly 值得期望的地方。

结论

现在让我们缩小下并查看下这个技能树。

我在这篇文章的开头说过,人们对WebAssembly有一种误解 - 在MVP中的WebAssembly是WebAssembly的最终版本。

我想你现在可以看出为什么这是一种误解了。

是的,MVP开辟了很多机会。它使得将大量桌面应用程序带入Web成为可能。但我们仍然有很多用例待解锁,从重量级桌面应用程序到小型模块,JS框架,浏览器以外的所有东西...... Node.js、serverless、区块链和可移植CLI工具,以及物联网。

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

猜你喜欢

转载自blog.csdn.net/hj7jay/article/details/84935804