Safari 18.2 今天把 WebAssembly 尾调用优化正式落地了——递归不再爆栈,函数式语言终于在浏览器里跑开了
写递归函数的时候,开发者最怕的不是写错,而是栈溢出。
函数末尾 return 一个函数调用——递归也好、互相调用也好——在传统 Wasm 里,编译器会给你新建一个栈帧,每次递归都多压一层。深度 10000 的链表求和,栈上压 10000 层,直接崩。
这就是 Wasm Tail Calls 要解决的事。
尾调用优化:递归变循环
当一个函数的最后一步是调用另一个函数时,尾调用优化(TCO)会丢弃当前栈帧,直接跳到目标函数——不是”调用”,是”跳转”。
没有 TCO:每次递归压一层栈,O(n) 栈空间;有 TCO:编译器把递归变成跳转,O(1) 栈空间。
;; 有 TCO:末尾直接跳转,不压栈
(func $sum-tco (param $list i32) (param $acc i32) (result i32)
loop (param $list i32) (param $acc i32) (result i32)
local.get $list
i32.eqz
if (result i32)
local.get $acc
else
local.get $list
i32.load
local.get $acc
i32.add
local.get $list
i32.const 4
i32.add
return_call $sum-tco ;; 尾调用:丢弃当前帧,直接跳转
end
end
)
谁最受益
Wasm Tail Calls 主要受益者是编译到 Wasm 的函数式语言:
- Haskell(GHC Wasm 后端):纯函数式语言没有 loop 语法,全靠递归+TCO 才能跑
- Scheme / Racket:规范要求 TCO,Wasm Tail Calls 让它在浏览器里合规运行
- Erlang/Elixir:BEAM actor 模型靠大量轻量进程,尾调用是关键
- Scala(Dotty):支持尾调用注解
对主流前端场景的影响是间接的——如果你用 TypeScript 或 Rust 写 Wasm 模块,通常不需要关心 TCO;但如果你用的框架、语言或工具链内部依赖函数式范式,现在它们的 Wasm 编译结果会更小、更安全。
三引擎支持现状
| 浏览器 | Wasm Tail Calls |
|---|---|
| Chrome | 119+(2023-11) |
| Firefox | 120+(2024-01) |
| Safari | 18.2+(2026-10) |
Safari 18.2 后,三大浏览器全部支持 Wasm Tail Calls,配合 WasmGC(Safari 18.2 同批支持),浏览器对函数式语言编译到 Wasm 的支持正式闭合。
下一步
- 查工具链:如果你用 GHC(WebAssembly target)、rustc(Wasm backend)、Scala.js,确认它们已支持
return_call目标 - 看性能:内存受限场景(嵌入式 Wasm)下,TCO 可以显著降低栈内存占用
- 搭配 WasmGC:同批 Safari 18.2 支持的两个特性,组合使用:函数式语言 → Wasm + GC + Tail Calls → 浏览器原生运行
评论区
0 条评论
登录后可评论。