0%

逃离STL方言,又入WIT迷宫:C++与WASM互操作的另一面

逃离STL方言,又入WIT迷宫:C++与WASM互操作的另一面

本以为逃开 C++ 跨编译器的“方言”问题,投奔 WASM 的标准化怀抱就能一身轻松。结果,一脚踩进了 WIT(WebAssembly Interface Types) 的坑里,发现这里的水也挺深。

事情得从 WASI(WebAssembly System Interface) 说起。它的初心很简单:把系统调用封装成 wasm 能用的函数。早期版本(WASI Preview1)直接用 i32/i64 这类底层类型当指针或句柄,配上 C ABI,虽然粗糙但直接。问题出现在大家想用它正经传递点高级数据——比如在不同语言间传个字符串或数组。这时候,光靠“指针+长度”的裸奔模式就顶不住了,内存布局、编码、生命周期管理……每个环节都可能对不上。

于是 Preview2 登场,带来了 WITComponent Model,想用一套中立的接口描述语言来解决类型互操作。理想很宏大:写一份 .wit 文件,工具链自动生成各语言的打包/解包代码,实现跨语言无缝调用。可惜,目前它还处在“测试阶段”,生态和工具链都在追赶中,而且一个明显的现实是:这套模型目前在 Rust 生态外能用得非常有限

更让我觉得有趣(也有点困惑)的是,Component Model 在全局初始化、导入导出这些设计上,竟和我自己设计的 ISDK 语言思路高度相似。这种共鸣感一闪而过,随即被一个根本性质疑取代:你作为基于字节码的底层虚拟机,难道不应该优先确定最稳定、最基础的 ABI 吗? 现在这种先定义上层组件模型、再让底层去适应的方式,感觉顺序有点倒置了。

目前 WIT 的痛点很具体:

  1. 内存复制开销:任何高级类型跨越组件边界都要完整拷贝一遍,对性能敏感或资源受限的场景很不友好。
  2. 布局“一刀切”:比如字符串只有 (ptr, len) 一种标准布局,短字符串无法内联,也无法适配特定平台的高效布局。
  3. 复杂性转移:规范把性能优化和内存管理的负担转嫁给了运行时和工具链,导致像 C++ 等语言的绑定支持进展缓慢,生态碎片化。

这些设计选择,使得当前规范在追求通用性的同时,也背上了不小的性能包袱实现成本


当然,发现问题不是为了吐槽,而是为了寻找改进的可能。我认为,WIT 和 Canonical ABI 可以朝着 增强类型系统灵活性 的方向演进:

  1. 标准化多种数据布局变体
    除了默认的 canonical-string,可以增加 inline-byte-string(短字符串内联)、null-terminated(C 风格空终止)等选项,让开发者根据场景选择。
  2. 提供官方跨语言 Pack/Unpack SDK
    避免各语言实现不一致导致 ABI 对齐问题,由规范组或核心社区维护统一的绑定生成工具,支持上述布局选项。
  3. 回归“组合低级原语以构建高级功能”的设计哲学
    先明确并稳定小而精的低级类型原语及其 ABI,再让上层在此基础上灵活组合,兼顾互操作性和性能优化。

这些想法并非空谈。实际上,我已经将这些建议整理成具体的提案,提交给了 WebAssembly 组件模型规范组。你可以在 GitHub Issue #581 看到详细内容。

所以,WASM 目前想作为“万能跨平台调用层”确实还不完全成熟,但它的演进路径是开放的。或许,除了等待和适配,我们也可以成为推动改变的一份子。

或许再等等?或者……我们自己动手?👀

(下一篇本来想写“直接调用 WASM 函数”,看来得先改成“如何在现行 WIT 约束下勉强求生”了……)