将C++中的anyref支持添加到WebAssembly编译器

2020-05-19 22:54:46

当前MVP形式的WebAssembly已在所有主流浏览器上发布,并且已经能够实现令人惊叹的功能。这并不意味着它的发展已经结束:相反,在不同的发展阶段,有许多后MVP功能提案。

一个这样的建议是关于将所谓的anyref/external ref类型(和相关指令)添加到规范中。但是,什么是anyref?为什么需要将其添加到WebAssembly?

完整的建议在本文中进行了详细说明,但基本思想是能够表示来自WebAssembly的不透明的主机引用(Web浏览器中的JavaScript对象)。即使使用此建议,也只能通过函数参数和返回值传递这些引用,并将其存储和加载到表中。未来的提案将扩展可能的功能,并最终在WebAssembly中实现全面的GC支持。

当前的anyref规范非常有限,特别是它们不允许将这些引用直接存储在线性内存中。它们需要存储在索引处的表中,然后索引本身可以存储在线性内存中。要做到这一点,需要对表进行一些运行时记账,以“分配”和“解除分配”槽并给出索引。

在C/C++和Rust等语言中直接集成anyref支持(即直接将DOM对象表示为anyref)并非易事,当前的主流观点似乎是在自动生成的粘合代码中暗地里使用anyref,同时保持实际的用户编写的代码只处理索引(请参阅此处查看讨论这一问题的有趣线程)。

Cheerp编译器在这里处于一个有趣而独特的位置:从一开始,它就支持对象内存模型,在该模型中,除了能够将C++对象存储为JavaScript垃圾收集对象之外,还能够将它们存储在连续的线性内存中(这是WebAssembly的内存模型)。

这允许Cheerp将C++代码编译成常规JavaScript,并轻松地与JavaScript库(包括本机浏览器API)进行互操作。还可以混合和匹配这两种表示,并为任何声明的结构/类选择其中一种。

有一些限制:虽然编译到WebAssembly和JavaScript的代码可以使用线性内存对象(用cheerp::WASM属性声明),但是编译到WebAssembly的代码不能访问垃圾收集的JavaScript对象(用cheerp::Genericjs属性声明)。这是由我们的编译器前端强制执行的。anyref可以让我们放松一些当前的限制,因为我们现在可以在WebAssembly代码中传递对JavaScript对象的引用!

作为anyref支持的实验原型,我们决定只允许在特殊客户端命名空间中定义的类在WebAssembly函数中使用:这些类在C++端没有布局,只有方法。编译以set_和get_开头的特殊方法以访问对象的相应成员字段。此命名空间中声明的类可以对来自Cheerp外部的JavaScript的现有对象进行建模,如原生Web API或第三方JavaScript库。

由于这些对象只能由指针处理(实际类型实际上是不透明的,因为只有声明是可见的),因此更容易对它们进行推理,并且它们可以完美地映射到anyref的当前功能。

我们可以使用anyref启用的最基本功能是允许将客户端命名空间中的类型指针作为参数传递给WebAssembly函数(也可以返回):

这可能很方便,但只传递指针而不能调用方法并不太有用。目前不支持对anyref值调用方法,但我们可以生成将anyref作为第一个参数并转发方法调用的JavaScript包装器:

这样,我们还可以允许从WebAssembly函数调用客户端对象上的方法。但是,如果一开始就分配对象呢?我们还可以生成包装器JavaScript函数,该函数将返回新创建的对象:

现在,我们几乎可以直接从WebAssembly对客户端命名空间中的对象执行任何操作!

目前,我们支持对全局客户端对象的有限访问:我们只允许对象类型(而不是指针)的外部声明,并且我们通过生成将所需的全局对象作为anyref返回的包装函数来实现对它们的访问。这是只读访问,但足以使控制台、文档等工作:

仍然存在的主要限制是我们无法在任何地方存储anyref值(至少在纯WebAssembly代码中,我们总是可以求助于使用chep::Genericjs函数),因此我们只能在函数调用期间处理它们。

这很烦人,但是随着将来扩展对全局变量的支持(使用真正的WebAssembly全局变量而不是包装器函数),并公开对WebAssembly表的访问,我们计划改善这种情况。

为了展示在当前状态下可以做什么,我使用JavaScript 3D库Three.js编写了这个(非常不完整的)类似乒乓球的游戏,该库仅使用编译到WebAssembly的函数实现(当然不包括自动包装器):

(重要提示:您需要一个支持anyref的浏览器才能尝试游戏。(对于Chrome,使用--js-flags=";--experimental-wasm-anyref";运行)。

对于Cheerp来说,anyref特性看起来非常有前途,我们计划最终将它的使用扩展到所有的Genericjs类型,以实现编译成JavaScript和WebAssembly的代码以及外部JavaScript库之间真正无缝的互操作性。

附注:该功能的官方名称最近似乎从anyref更改为extref(请参阅此提交)。