WasmGC 提案与托管语言:Java/C#/Kotlin 原生编译到 WASM

系统覆盖 WebAssembly 的 GC 提案(WasmGC):为什么托管语言编译到 WASM 需要 GC、引用类型与结构类型的核心概念(i31ref、struct/array ref、func ref)、GC 指令集与分配语义(struct.new/array.get/ref.cast)、与线性内存的对比(GC 堆 vs 手动管理)、宿主运行时的 GC 集成、各语言的编译路径(JVM/.NET/Kotlin Native/Dart/Swift)、提案状态与支持矩阵(V8/Spidermonkey/Wasmtime/Wasmer)、性能开销与优化方向,以及 GC 与尾调用/栈切换/组件模型的关联,帮助读者理解托管语言如何真正登上 WASM 平台。

引言

WASM 最初为 C/C++/Rust 设计——它们用线性内存自己管理一切,天然适合 WASM 的底层模型。但 Java、C#、Kotlin 这类托管语言依赖垃圾回收(GC)管理对象生命周期,直接编译到 WASM 会撞上「没有 GC 指令」的墙:要么把整个运行时(含 GC)编进去(体积爆炸、性能差),要么用线性内存手动模拟(别扭且易错)。WasmGC 提案从根上解决:让 WASM 原生支持 GC 对象与回收,托管语言可以干净地编译成 WASM。本文把 WasmGC 的提案动机、引用类型、指令集、运行时集成与各语言编译路径一次讲透。

前置:/wasm-introduction-architecture/(内存模型)、/wasm-rust-compilation-guide/(编译工具链)、/wasm-component-model-wit/(类型系统演进)。

目录

1. 为什么托管语言需要 GC 提案

托管语言的对象模型依赖「自动回收无用对象」——这是编译器假设的基础设施。但 WASM 最初的指令集里没有任何「分配对象」「读取字段」「回收」的语义:

WASM 初始模型:
□ 线性内存(memory):可读写字节,但「对象」概念不存在
□ 只能手动管理:malloc/free,或用值栈传简单类型
□ 托管语言直接编译 → 需要自带 GC(体积巨大、难以优化)

WasmGC 提案:
□ 新增引用类型(struct/array/func/extern)
□ 新增 GC 指令(struct.new、array.get、ref.cast…)
□ 分配/回收交给宿主(浏览器/运行时)的 GC

为什么重要:GC 提案让 Java/C#/Kotlin/Swift/Dart 等语言能以「干净」的方式编译到 WASM——对象直接映射为 WASM 引用,GC 由宿主负责,语言运行时大幅缩小。这是 WASM 从「系统语言专属」走向「通用语言目标」的关键一步。

工程定位:GC 提案不是让现有 WASM 变慢,而是让原本「编不进来」的语言变得可行——它扩展的是 WASM 的「语言覆盖」,而非替代线性内存。

2. 引用类型与结构类型

WasmGC 引入的核心类型:引用类型(reference types),与标量(i32/i64/f32/f64)平级:

类型含义典型用途
funcref函数引用闭包、回调、多态(对应 call_indirect)
externref外部宿主对象引用传 JS/宿主对象,不参与 WASM 内存
(ref null $t) / (ref $t)到堆对象的可空/不可空引用GC 对象的指针
(struct $T)结构体(字段集合)对象的实例
(array $T)数组(元素类型固定)数组/集合
;; 声明一个结构类型(类似 C 的 struct / 类的字段布局)
(type $Point (struct (field $x (mut i32)) (field $y (mut i32))))

;; 声明一个数组类型
(type $Ints (array (mut i32)))

关键认知:

  • struct/array 是「类型化的堆对象」:分配后宿主在 GC 堆管理,WASM 只持有引用;
  • 引用与线性内存指针不同:引用不可「算术运算」(不能 +1、不能解引用成整数),由指令专门操作;
  • mut 与不可变:字段可标 (mut i32)(可写)或 i32(不可写,利于优化)。

3. i31ref 与整型装箱

GC 对象里的小整数(如 int、枚举、布尔)如果都装箱成堆对象,内存与分配开销巨大。i31ref(31-bit 整数引用)是经典优化:

i31ref:把 31 位整数直接编码进「引用」本身(tag 指针/立即数)
  → 小整数不用真的分配堆对象
  → 存储与读取零分配、零 GC 压力

编码技巧(常见实现):
□ 指针标记:用最低 1 位标记「这是 i31」而非真指针
□ 读写:ref.i31 → 直接取整数;i31.ref → 检查范围后装箱
;; 装箱:把 i32 变成 i31 引用
i32.const 42
ref.i31            ;; 生成一个 i31ref(可能直接是立即数)

;; 拆箱:取回整数
ref.i31
i31.get_s          ;; 得到带符号的 31 位整数

工程意义:编译器把「小整数字段」映射成 i31ref,避免为每个小整数分配对象——GC 语言编译到 WASM 的性能关键就在这类优化。

4. GC 指令集与分配

WasmGC 的指令分几类:

分配:struct.new $T / struct.new_default $T / array.new $T
读取:struct.get $T $f / array.get $T / array.len
写入:struct.set $T $f / array.set $T(需 mut 字段)
判断:ref.test (ref null $T) / ref.cast (ref null $T)
分支:br_on_cast / br_on_non_null / br_on_null
比较:ref.eq(同引用相等)
;; 创建并写入一个 Point 对象
(func $make_point (result (ref $Point))
  i32.const 10
  i32.const 20
  struct.new $Point        ;; 分配 + 初始化两个字段
  ;; 栈顶现在是 (ref $Point)
  return)
;; 读取字段 + 类型收缩
(func $get_x (param $p (ref $Point)) (result i32)
  local.get $p
  struct.get $Point $x     ;; 读取 x 字段
  return)

关键认知:

  • 分配即结构化:struct.new 直接按类型布局分配,无「先 malloc 再手动布置」;
  • 类型收缩 ref.cast:把 (ref null $Base) 安全缩成 (ref $Derived),配合 br_on_cast 做多态分发;
  • 编译器视角:GC 指令让「对象模型」直接落到 WASM,语言运行时不再需要自己造对象堆。

5. 与线性内存的对比

GC 对象在 GC 堆(宿主管理),与线性内存(WASM 手动管理)是两条轨道:

维度线性内存GC 堆(WasmGC)
分配memory.grow + 手动分配器struct.new / array.new
生命周期手动 free / 作用域管理宿主 GC 自动回收
表示整数指针(可算术)引用(不可算术)
遍历任意读写字节按类型读取(struct/array)
适用C/C++/Rust、缓冲区、二进制Java/C#/Kotlin 对象图
典型布局:
[线性内存]──────► 大缓冲区、字节流、FFI 数据
[GC 堆]────────► 语言级对象、集合、闭包(引用相连)

桥梁:externref / 显式拷贝(把 GC 对象拷进线性内存做零拷贝 FFI)

工程要点:两者可共存——同一模块既能用线性内存做高性能字节处理,也能用 GC 堆承载托管语言对象。选择取决于「谁拥有数据、谁管理生命周期」。

6. 宿主运行时的 GC 集成

分配与回收由宿主(浏览器引擎或独立运行时)的 GC 负责:

WASM 模块发出 struct.new
  → 宿主 GC 在托管堆分配
  → 返回引用给 WASM
  → 对象不再被引用时,宿主 GC 回收

浏览器:V8(Chrome/Node)、Spidermonkey(Firefox)、JavaScriptCore
独立:Wasmtime(wasm-gc 实验)、Wasmer、WAMR(轻量)

集成的关键点:

  • 根(roots):WASM 栈/全局里的引用是 GC 根,宿主必须正确标记;
  • 跨边界引用:externref 传出的对象要「钉住」防止被回收;
  • 帧栈:多线程/栈切换时引用根随协程移动(关联提案,见 /wasm-exceptions-stack-switching/)。

工程要点:宿主 GC 的停顿/分配速度决定体验——浏览器引擎为网页优化,独立运行时可配参数;语言运行时不应重复实现 GC,把「谁回收」交给宿主是 WasmGC 的架构原则。

7. 各语言的编译路径

WasmGC 让多种托管语言具备「干净」编译路径:

语言编译路径状态
JavaJVM → WASM(GraalVM/Soy 等实验性后端)实验/进展中
KotlinKotlin/Native → WASM(Kotlin/Wasm 官方支持)官方推进
C# / .NETMono/Runtime → WASM(Blazor 服务器端 + GC 提案)官方路线
DartDart → WASM(Flutter Web 的 wasm 后端)官方支持
SwiftSwiftWasm 探索 GC 路径实验
OCaml / Haskell函数式语言实验后端实验
以 Kotlin/Wasm 为例:
Kotlin 源码 → Kotlin/Native 编译器 → WASM + GC 指令
  → 对象映射为 GC 引用,字符串/集合走 WASM GC 堆
  → 浏览器运行:无需把 JVM 编进去,产物大幅减小

工程定位:WasmGC 的目标不是「让每种语言无脑编译」,而是让语言运行时可以很薄——对象的分配回收交给宿主,语言只需保留自己的类型系统与语义。这条路走通,WASM 才能成为「通用编译目标」。

8. 提案状态与支持矩阵

WasmGC 已进入稳定化,主流引擎逐步支持:

提案状态(截至 2026):
□ Phase 4(标准化):引用类型、GC 指令核心
□ 浏览器:V8 / Spidermonkey 已默认支持或 flag 开启
□ 独立运行时:Wasmtime 实验支持,Wasmer 部分支持
□ 工具链:wasm-tools / cargo-component / Kotlin 工具链跟进
# 在 V8(Node)里启用 wasm-gc(示例,版本相关)
node --experimental-wasm-gc app.mjs

工程要点:

  • 检查运行时支持:部署前在目标浏览器/运行时确认 GC 指令可用(特性检测);
  • 回退方案:不支持 GC 的环境退回「自带运行时」版本(体积大但可用);
  • 关注工具链:wasm-tools 的 validate/dump 对 GC 类型的支持是最新验证手段。

9. 性能开销与优化方向

GC 语言编到 WASM 的性能「天花板」取决于几个因素:

主要开销:
□ 分配频率:每次 struct.new 走宿主 GC(比手动快但非零)
□ 装箱:小整数用 i31ref 避免堆分配(编译器优化关键)
□ 多态:ref.cast / br_on_cast 的分发成本
□ GC 停顿:宿主 GC 全局停顿(大堆时明显)

优化方向:
□ 编译期消除装箱(i31ref / 标量替换)
□ 减少不必要的分配(escape analysis)
□ 与线性内存结合:热路径缓冲区走线性内存
□ 选择 GC 友好的运行时参数(增量/分代 GC)
经验基线:GC 语言 WASM 产物 ≈ 原生 JIT 的 60~90% 性能
(视语言、工作量与宿主 GC 而定),但远优于「自带 GC 运行时」的方案。

工程要点:优化重点不是「让 WASM 更快」,而是让语言运行时薄——编译器把对象模型正确落到 GC 指令,宿主 GC 做剩下的。工具链(编译优化 + GC 指令生成质量)是当前最大提升空间。

10. 速查表与一句话记忆

问题一句话答案
为什么需要 WasmGC托管语言(Java/C#/Kotlin)依赖 GC,WASM 原生不支持
新增什么类型引用类型:struct/array/funcref/externref/i31ref
怎么分配对象struct.new / array.new,宿主 GC 管理
与线性内存区别GC 堆不可算术、自动回收;线性内存手动管理
谁负责回收宿主(浏览器/运行时)的 GC
各语言怎么用Kotlin/Wasm 官方、.NET/Dart 官方推进
性能怎么看依赖装箱消除与宿主 GC,远优于自带运行时

一句话记忆:WasmGC = 让 WASM 原生支持「引用 + 分配 + 回收」——托管语言(Java/C#/Kotlin)不再自带运行时,对象走 GC 堆(struct/array/i31ref),回收交给宿主——WASM 从「系统语言专属」走向「通用语言目标」。

延伸阅读

  • /wasm-introduction-architecture/ — WASM 内存模型与模块结构
  • /wasm-rust-compilation-guide/ — Rust 编译到 WASM(无需 GC 的路径)
  • /wasm-component-model-wit/ — 类型系统与组件互操作
  • /wasm-exceptions-stack-switching/ — 异常与栈切换提案(引用根随协程移动)
  • /wasm-multithreading-sharedarraybuffer/ — 多线程下的对象与共享内存
  • Rust 专题 — 系统语言编译路径对比
  • Java 专题 — JVM 与 GraalVM 的 WASM 后端

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「wasm」更多文章

  1. WASM 构建与打包工具链:wasm-bindgen、wasm-pack 与前端集成
  2. WASM 媒体处理:音视频编解码、转码与滤镜
  3. WASM 密码学:WebCrypto、WASM 密码库与安全计算