一个网页标签页,粘进几段 Kotlin 源码,几十毫秒后吐出一个能直接运行的 WASM 模块。中间没有 JVM,没有 LLVM,没有 Binaryen,连 Gradle 都不用装。这就是 minikotlin 展示的东西。
它是一个从零用 C 写的 Kotlin 编译器,自己也被编译成 WASM,在浏览器里把多文件 Kotlin 源码直接编译成 WebAssembly GC 字节码。项目自陈 366 项端到端测试、657 项前端测试,运行时依赖为 0。它不是要取代官方工具链,而是把"一套完整的小型语言工具链能做多小"这件事,重新演示了一遍。
速读:编译链、特性覆盖与零安装边界
编译链只有一条路。.kt 源码进前端(项目内部叫 mkf,做词法、语法、语义分析),转成 HIR(贴近语言本身的类型化树),降到 MIR(操作、局部变量、struct 布局、vtable),最后手写输出 WASM-GC 字节码。中间没有虚拟机接手,也没有外部后端插进来做二次编译。
编译器本身也以 WASM 形式跑在浏览器里,代码在哪运行,编译器就在哪运行,打开网页即可用。示例页面两个 .kt 文件编译出 main.wasm 用了约 41ms——这只是单次演示的读数,页面没有拿它和 JVM 版 Kotlin/Wasm 或 LLVM 工具链做过对照,不能当成通用性能结论。
语言特性覆盖不算浅。类与继承、带默认方法的接口、data class 自动生成 equals/hashCode/copy、enum 与匿名/伴生 object、密封类配合穷尽 when、空安全、泛型(装箱在 Any 上实现)、运算符重载、扩展函数,以及基于 launch/delay/coroutineScope 的非阻塞协程。项目的说法是每一项都配了端到端测试。
硬核之处:四个映射怎么落地 WASM-GC
特性列表长不代表技术含量高。真正见分量的地方,是语言概念怎么变成 WASM-GC 的具体指令——这四处映射决定了这个编译器是不是真的"手写"出来的。
类实例编译成 GC struct,属性是真实字段,分配走 struct.new,不用额外的字节堆去模拟对象。open/override 方法走每个类自己的 vtable,虚调用先取函数引用再 call_ref,是真正的动态派发,不是解释器里查表跳转。
is 检查和 when 里的 is 分支编译成 ref.test,收窄后的值靠 ref.cast 直接复用。智能类型转换在编译期完成,不额外占运行时开销。
协程这块最考验设计。挂起点把函数从悬点切开,后面的执行捕获成一个 continuation,整个过程不靠 Asyncify,不靠 JSPI,也不用线程。但要澄清一点:delay 的恢复靠的是把令牌交给宿主的 setTimeout。"运行时依赖为 0"指的是不依赖额外运行时库,不是说生成的模块能完全脱离浏览器宿主能力独立运行。
谁该关心,接下来看什么
minikotlin 的读者从来不是普通 Kotlin 业务开发者,而是编译器作者、浏览器内开发工具的构建者、盯着 Kotlin/Wasm 方向的工程师。前一类人会去翻它的 struct/vtable 实现,后一类人可能拿它做浏览器内实时编译的原型验证。普通 Kotlin 生产项目该做的是观望,不必考虑迁移或替换现有工具链。
| 维度 | minikotlin | 官方 Kotlin/Wasm 工具链 |
|---|---|---|
| 依赖 | 自陈运行时依赖为 0,不依赖 JVM/LLVM/Binaryen/Gradle | 依赖 JVM 生态与 LLVM/Binaryen 等成熟后端 |
| 运行方式 | 编译器本身以 WASM 跑在浏览器内 | 需要本地安装构建工具链 |
| 验证规模 | 366 项端到端 + 657 项前端测试(项目自陈) | 经过大规模生产项目长期验证 |
| 标准库/调试 | 材料未提供证据,覆盖情况不明 | 标准库、包管理、调试器已成熟 |
表格里能确认的是依赖差异和运行方式,这是 minikotlin 真正拿得出手的部分。标准库覆盖、包管理、调试体验这些工程成熟度指标,材料完全没给证据,不能替项目打包票。
接下来该盯的是两件事:测试数量是不是还停在演示规模,标准库覆盖有没有实质进展。这两项不补上,minikotlin 就只能停在"技术样板"这一格。
麻雀虽小,五脏俱全——但五脏俱全不等于身强体壮。手写 WASM-GC 后端、浏览器内闭环编译,这两件事 minikotlin 做到了。生态兼容和工程成熟度,还没到能拿出来结算的时候。
