JS WebAssembly (Wasm) `Reference Types` (GC 集成提案) 的影响

大家好,我是你们今天的Wasm段子手兼技术向导。今天咱们来聊聊WebAssembly里一个挺有意思的新玩意儿:Reference Types,也叫GC集成提案。这玩意儿听起来有点高深,但实际上,它就像给Wasm加了个“任意门”,让它可以更方便地和JavaScript世界,乃至其他支持垃圾回收的语言“勾搭”起来。

咱们先来热热身,想象一下,以前的Wasm就像一个住在隔离舱里的人,虽然能干活,但和外界交流只能通过一些固定的“窗口”(比如数字)。现在,Reference Types就像给隔离舱开了一扇门,让他可以自由出入,甚至能把外面的东西直接拿进来。

一、为啥需要Reference Types?

在没有Reference Types之前,Wasm和JavaScript之间的交互是比较麻烦的。基本上,只能通过传递数字(整数、浮点数)来实现。如果要传递更复杂的数据结构,比如对象、数组,那就得费一番周折:

  1. 线性内存大法: 把数据序列化到Wasm的线性内存里,然后把内存地址和长度传给JavaScript。JavaScript拿到地址和长度后,再从线性内存里读取数据,反序列化成JavaScript对象。

    • 优点: 简单粗暴,啥都能传。
    • 缺点: 性能损耗大,序列化/反序列化是耗时操作;内存管理麻烦,容易出bug(比如内存泄漏)。
  2. import/export 函数大法: 通过Wasm的import/export机制,定义一些函数,专门用来传递数据。

    • 优点: 比线性内存大法稍微优雅一点。
    • 缺点: 仍然需要自己管理内存,类型安全得不到保证,容易出错。

这两种方法就像古代的信使,传递信息效率低,还容易丢件。Reference Types的目标就是解决这些问题,让Wasm和JavaScript之间的交流更直接、更高效、更安全。

二、Reference Types是啥?

简单来说,Reference Types就是Wasm现在可以支持“引用”这种类型了。以前Wasm只能操作数字,现在它可以操作JavaScript对象、DOM节点、甚至是其他Wasm模块的实例的引用。

这就像Wasm现在有了“指针”,可以直接指向JavaScript世界的各种东西。但是,和C/C++的指针不同,Wasm的引用是类型安全的,而且由垃圾回收器自动管理,不用担心内存泄漏。

具体的,Reference Types引入了以下几种新的类型:

类型 描述 对应JavaScript类型
externref 外部引用。可以指向任何JavaScript对象,包括null。 任何JavaScript值 (包括 null, undefined, 数字, 字符串, 对象, 数组, 函数等)
funcref 函数引用。可以指向Wasm函数或者JavaScript函数。 JavaScript函数 或者 Wasm函数
anyref 可以指向任何引用类型的值,包括externreffuncref以及未来可能出现的其他引用类型。 任何引用类型的值
eqref 只能指向Wasm的结构体(struct),这个类型是GC提案的一部分,和Reference Types一起使用,用来支持Wasm的垃圾回收。这个咱们后面会细说。 Wasm结构体实例
i31ref 只能指向31位有符号整数。这是一种优化手段,可以避免将小整数装箱成对象。 31位有符号整数 (作为引用)
(还有一些其他类型,比如datarefstringref,但目前还没有完全标准化,咱们先不细说)

三、Reference Types怎么用?

咱们来举几个例子,看看Reference Types是怎么让Wasm和JavaScript“眉来眼去”的。

例子1:Wasm调用JavaScript函数

以前,Wasm调用JavaScript函数,只能传递数字。现在,它可以直接传递JavaScript对象了。

// JavaScript
function greet(name) {
  console.log("Hello, " + name.name + "!");
}

// Wasm (WAT format)
(module
  (import "js" "greet" (func $greet (param externref)))
  (func (export "callGreet") (param externref)
    local.get 0
    call $greet
  )
)
// JavaScript 启动Wasm
const wasmInstance = await WebAssembly.instantiateStreaming(fetch('module.wasm'), {
  js: {
    greet: greet
  }
});

const { callGreet } = wasmInstance.instance.exports;
const person = { name: "Wasm" };
callGreet(person); // 输出 "Hello, Wasm!"

在这个例子中,Wasm模块导入了一个JavaScript函数greet,这个函数接受一个externref类型的参数,也就是一个JavaScript对象的引用。Wasm模块通过callGreet函数,把一个JavaScript对象person传递给greet函数。

例子2:JavaScript调用Wasm函数,并接收JavaScript对象

// JavaScript
// Nothing special here

// Wasm (WAT format)
(module
  (func (export "createObject") (result externref)
    (global.get $js_object_constructor)
    call_ref (type $make_js_object)
  )
  (type $make_js_object (func (result externref)))
  (import "js" "Object_constructor" (global $js_object_constructor (mut externref)))
)
// JavaScript 启动Wasm
const wasmInstance = await WebAssembly.instantiateStreaming(fetch('module.wasm'), {
  js: {
    Object_constructor: Object
  }
});

const { createObject } = wasmInstance.instance.exports;
const obj = createObject();
console.log(obj); // 输出一个 JavaScript 对象

在这个例子中,Wasm模块导出了一个createObject函数,这个函数返回一个externref类型的返回值,也就是一个JavaScript对象的引用。JavaScript代码调用createObject函数,接收返回的JavaScript对象。

例子3:传递Wasm函数引用

// JavaScript
function executeWasmFunction(func) {
  console.log("Executing Wasm function...");
  func(); // 调用Wasm函数
}

// Wasm (WAT format)
(module
  (func $wasmFunction (export "wasmFunction")
    (i32.const 42)
    (call $log)
  )
  (import "js" "executeWasmFunction" (func $executeWasmFunction (param funcref)))
  (import "js" "log" (func $log (param i32)))
  (func (export "registerWasmFunction")
    (global.get $executeWasmFunction)
    (global.get $wasmFunction_ref)
    call_ref (type $callback_type)
  )
  (global $wasmFunction_ref (mut funcref) (ref.func $wasmFunction))
  (global $executeWasmFunction (mut funcref) (ref.null funcref))
  (type $callback_type (func ()))
)
// JavaScript 启动Wasm
const wasmInstance = await WebAssembly.instantiateStreaming(fetch('module.wasm'), {
  js: {
    executeWasmFunction: executeWasmFunction,
    log: console.log
  }
});

const { registerWasmFunction, wasmFunction } = wasmInstance.instance.exports;
registerWasmFunction(); // JavaScript会调用 Wasm的wasmFunction

在这个例子中,JavaScript定义了一个executeWasmFunction函数,它接收一个funcref类型的参数,也就是一个函数的引用。Wasm模块导出了一个wasmFunction函数,并通过registerWasmFunction函数,将wasmFunction的引用传递给executeWasmFunction函数。

四、GC 集成提案:Wasm的“完全体”

Reference Types只是GC集成提案的一部分。GC集成提案的目标是让Wasm能够直接操作垃圾回收堆,从而支持更多高级语言(比如Java、C#、Python、甚至JavaScript本身)编译到Wasm。

GC集成提案的核心思想:

  1. Wasm拥有自己的垃圾回收堆。 这个堆和JavaScript的堆是分开的,但是可以通过Reference Types互相访问。

  2. Wasm可以定义自己的数据结构(struct)。 这些数据结构可以包含引用类型的成员,比如externreffuncrefeqref

  3. Wasm可以创建和管理这些数据结构的实例。 这些实例会被垃圾回收器自动管理,不用担心内存泄漏。

GC集成提案引入了一些新的指令和类型:

  • struct 定义数据结构。
  • field 定义数据结构的成员。
  • new 创建数据结构的实例。
  • get 获取数据结构成员的值。
  • set 设置数据结构成员的值。
  • eqref 指向Wasm结构体实例的引用。

例子:用Wasm定义一个简单的链表

(module
  (type $list_node_type (struct (field (mut externref)) (field (mut externref))))

  (func $create_list_node (param $value externref) (result externref)
    (struct.new $list_node_type
      (local.get $value)
      (ref.null externref)
    )
  )

  (func $get_list_node_value (param $node externref) (result externref)
    (struct.get $list_node_type 0 (local.get $node))
  )

  (func $get_list_node_next (param $node externref) (result externref)
    (struct.get $list_node_type 1 (local.get $node))
  )

  (func $set_list_node_next (param $node externref) (param $next externref)
    (struct.set $list_node_type 1 (local.get $node) (local.get $next))
  )

  (export "createListNode" (func $create_list_node))
  (export "getListNodeValue" (func $get_list_node_value))
  (export "getListNodeNext" (func $get_list_node_next))
  (export "setListNodeNext" (func $set_list_node_next))
)

这个例子定义了一个简单的链表节点list_node_type,它包含两个成员:valuenext,都是externref类型。Wasm模块还定义了一些函数,用来创建、获取和设置链表节点的属性。

五、Reference Types和GC集成提案的影响

Reference Types和GC集成提案对Wasm生态系统会产生深远的影响:

  1. 更高效的互操作性: Wasm和JavaScript之间的交互会更加高效,可以避免大量的序列化/反序列化操作。

  2. 更多语言可以编译到Wasm: GC集成提案使得更多支持垃圾回收的语言(比如Java、C#、Python)可以更容易地编译到Wasm,从而在Web上运行。

  3. 更强大的Web应用: Wasm可以更好地利用Web平台的各种API,从而构建更强大的Web应用。

  4. 新的编程模型: Reference Types和GC集成提案为Web开发带来了新的编程模型,可以更灵活地组合Wasm和JavaScript代码。

六、总结

Reference Types和GC集成提案是WebAssembly发展的重要一步。它们让Wasm可以更好地和JavaScript世界融合,从而发挥更大的作用。虽然目前这些提案还在不断完善中,但它们的前景非常广阔。

我们可以预见,未来会有越来越多的Web应用会采用Wasm技术,利用Reference Types和GC集成提案,构建更高效、更强大的Web应用。

好了,今天的讲座就到这里。希望大家有所收获,也欢迎大家多多关注WebAssembly的发展,一起见证Web技术的未来!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注