Turbo Haskell
摘要
Edward Kmett 的 Turbo Haskell(THC)通过 Truffle/GraalVM 编译 GHC Core,支持全部 prim-ops、基于 Native Image 的 JIT 与 AOT 编译、面向 Python/Ruby/R/JavaScript 的多语言 FFI、SIMD、基于 Project Loom 的绿色线程,甚至还能编译 GHC 本身。
<p><a href="https://lobste.rs/s/j7kltq/turbo_haskell">评论</a></p>
查看缓存全文
缓存时间: 2026/09/30 14:13
# Turbo Haskell 来源:https://comonad.com/reader/2026/turbo-haskell/
恰好一周前(纯属玩笑),我在拜访 Bartosz Milewski(https://bartoszmilewski.com/)休假期间,开始编写 THC——我的"Turbo Haskell 编译器"。自那以来,它已经有了一点点增长。THC 现在实现了 GHC 9.14.1 的每一个 prim-op,并提供了一个针对 GHC Core 的 JIT,可以在 JVM 上运行 Haskell。它采用了我几年前在 Cadenza(https://github.com/ekmett/cadenza)中为运行带类型的函数式语言所开发的方法(演讲(https://www.youtube.com/watch?v=gbmURWs_SaU)),基于 Truffle(https://www.graalvm.org/latest/graalvm-as-a-platform/language-implementation-framework/)和 GraalVM(https://www.graalvm.org/)构建。GHC 仍然负责解析、类型检查、脱糖和 Core 优化。THC 从那里接手,通过其自己的 Truffle/GraalVM 运行时编译并执行这些 Core。Template Haskell 和 Linear Haskell 等高级语言特性得到了完全支持。虽然它可以作为 GHC 级别 Haskell 的 JIT 使用,但它也支持借助 Native Image 进行提前编译(AOT),从而能够生成可执行文件。THC 能够以 JIT 或 AOT 方式编译一系列 Haskell 程序,包括 `pandoc`、`happy`、`alex`,而且今天起甚至包括 GHC 本身!THC 使用 Cabal 解析包,并完全支持包含多个库的包,包括 Backpack。
## 借用其他语言的库
THC 提供了多语言 FFI,支持 Python(https://github.com/oracle/graalpython)、Ruby(https://github.com/truffleruby/truffleruby)、R(https://github.com/oracle/fastr)和 JavaScript(https://github.com/oracle/graaljs),让 Haskell 可以"打劫"其他语言的库,并将其输出直接引入 JIT 编译的 Haskell 程序中。在 FFI 上,`Data.Text` 与 Truffle 字符串之间的转换对于其他多语言环境中 UTF-8 编码的字符串是零拷贝的。我们的想法是:如果你需要一个数据框、想运行一个 LLM,或者想做一个 D3.js 可视化,你只需通过 foreign import 把 `Text` 传出去即可。基于准引用(quasi-quotation)的 `inline-` 风格绑定实现起来也应该相当容易。你的 Haskell 库中的 C/C++ 代码则通过 FFI 在原生模式 Sulong(https://www.graalvm.org/latest/reference-manual/llvm/,即 JVM 上的 LLVM)中运行。托管模式 Sulong 也提供,其中 LLVM 在 JVM 内部被解释执行,指针由 JVM 管理并进行垃圾回收,但普通的 `foreign import` 路径并不使用它。
## 求值与并发
在内部,THC 为 Truffle 求值支持两种不同的后端:基于字节码的 JIT 目标和传统的基于 AST 的 JIT 目标。两者都可以以单线程或多线程方式运行,后者会附加额外的锁。THC 还支持 GHC 字节码本身,因此它可以运行 GHCi 生成的 BCO 代码。THC 完全支持 `throwTo`、会留下可恢复代码的异步异常,以及屏蔽(masking)。THC 同时支持"常规"Java 线程和 Project Loom(https://openjdk.org/projects/loom/),并基于 Loom 提供轻量级的 GHC 风格绿色线程,配合 HEC 风格的运行时执行器,使得廉价的 `MVar` 等机制成为可能。
## SIMD
THC 使用现有的 GHC prim-op 支持 SIMD,功能相对有限,但它可以更进一步,允许在运行时选择 SIMD "物种"宽度,并利用该信息通过孵化中的 Vector API(https://openjdk.org/jeps/508)(`jdk.incubator.vector`)对循环进行 JIT 编译。这让 JIT 开始显出其价值!事实上,没有任何东西阻止运行时在运行时提供完全多态于 `RuntimeRep` 的代码——唯一的原因是我们还没有利用这种自由度的 Core!
## 尾调用
热尾调用会变成循环。当执行不得不退回到普通调用时,THC 会周期性地展开累积的栈帧。之前在 JVM 上运行函数式代码的尝试,例如 Eta 编程语言(https://eta-lang.org/),以及我与 Runar Bjarnason 一起为 Scalaz 单子设计的 trampoline 机制,都使用了蹦床(trampoline)方案。而 THC 则使用一种代码变换技巧,让热尾调用保持在带有侧出口(side exit)的紧密基本块式循环内。在追踪模式(tracing mode)下的递归过程中,THC 会填充一个 64 位 Bloom 过滤器(https://en.wikipedia.org/wiki/Bloom_filter)来检测可能的递归尾调用。当它发现一个可能的命中时,会抛出一个慢路径异常,将延续(continuation)与调用位置连接起来,然后由自定义的 Truffle 节点让 Graal 将当前跨越函数体的尾调用循环变换为一个紧密的循环。误判只意味着额外的慢路径开销;它们不会改变程序的结果。当后续代码路径出现分叉时,THC 会像追踪 JIT 一样尝试增长额外的侧循环,直到触及 JVM 对函数体大小的限制。到那时,它最终会以一种"泄露"栈帧的方式溢出一个尾调用。这种泄露是暂时的。我们可以使用一种与 CHICKEN Scheme 的垃圾回收策略(https://www.more-magic.net/posts/internals-gc.html)有些类似的方法来压缩累积的栈帧,复用我们为支持异步异常下可恢复代码所需的那个机制。结果是,热循环可以运行得*非常*热。特别是对 `Data.Map` 进行基准测试时,我发现它大约只需要 66 次回退的蹦床调用,而快速路径调用有数百万次。
## 性能
运行时还可以使用压缩普通对象指针(compressed oops)(https://docs.oracle.com/en/java/javase/25/vm/java-hotspot-virtual-machine-performance-enhancements.html)。这些将堆引用表示为 32 位偏移量,而非完整的 64 位指针,从而减少引用占用的内存,让更多的数据能留在缓存中。在 JVM 通常的 8 字节对象对齐下,这种模式会将堆限制在大约 32 GB。性能是最初几天开发的关键考量。对于 `Data.Map` 之类的测试,预热后我能让运行速度落在大约快 3 倍到慢 3 倍的范围内,多数情况下徘徊在比 GHC 慢 10–20% 左右。话虽如此,最近几天我们为了扩大覆盖面而全力冲刺,并没有进行基准测试,期间某些简单基准测试的性能退化了大约 10 倍。开发工作仍在持续推进,以保持对这些性能问题的控制。我们还没有测试非尾调用路径上的栈增长相对于 GHC 的栈使用是否保持有界。确保这个上界是未来可能需要的工作。
## 开发
代码位于 github.com/ekmett/thc(https://github.com/ekmett/thc),文档(https://ekmett.github.io/thc/)涵盖了如何构建、运行和使用 THC。开发工作在 `irc.libera.chat` 的 ##thc 频道(https://web.libera.chat/##thc)中进行。欢迎加入我们。
——Edward Kmett
在 Reddit 上讨论(https://www.reddit.com/r/haskell/comments/1wu24oe/turbo_haskell/)。
相似文章
H2JVM - 用于编写JVM字节码的Haskell库
H2JVM 是一个Haskell库,允许开发者直接在Haskell中编写JVM字节码,支持底层JVM操作。
底层Haskell:在Haskell/GHC中模拟内联汇编的邪道方法
本文探讨了在Haskell/GHC中模拟内联汇编的技术,用于调用晦涩的CPU指令以及从外部函数高效返回多个值,并使用了扩宽乘法和无进位乘法等示例。
优化 GHC 构建速度
本文概述了一种系统的方法来改进 Haskell 构建时间,通过基准测试、识别瓶颈(如慢模块)、分析原因(如过度的类型级编程),并评估优化中的权衡。
Hobbes – 一种语言与嵌入式JIT编译器
Hobbes是摩根士丹利开发的一种开源语言、嵌入式JIT编译器和运行时,用于在C++应用程序中进行高效的动态表达式求值、数据存储和分析。
Show HN: Tiny – 一种带有内联Go原生函数的动态解释型语言
Tiny是一种用Go编写的新的动态解释型编程语言,具有字节码虚拟机、JIT编译和内联Go原生函数,以实现高性能。