Apple 将 TrueType Hinting 解释器迁移至 Swift

Apple 已使用内存安全的 Swift 重写了其平台的 TrueType hinting 解释器,并将在 2025 年秋季版本中取代原有的 C 实现。此次迁移的驱动力在于需要保护一个关键的攻击面——因为字体解析器需要处理来自互联网的不可信数据——同时提高执行速度。

性能提升与内存安全

基于 Swift 的解释器平均比其取代的 C 实现快 13%。通过利用 Swift 现代的类型系统和优化器,Apple 在不牺牲可读性或安全性的情况下实现了这一性能提升。新的实现中仅在语言互操作边界处包含少量经过验证的 unsafe 语句,但核心逻辑是完全内存安全的。

确保像素级精确度

对于此次迁移,正确性被定义为与 C 实现的输出在二进制和像素上完全一致。为了保证这一点,Apple 采用了两种严谨的测试策略:

  • 单元测试: 为 C 和 Swift 实现提供了 99.7% 的代码覆盖率。
  • 模糊测试与语料库分析: 一个模糊测试器将 1000 万个 PDF 文件的语料库缩减到了 4200 个。这导致在 25,572 种字体中渲染了 2700 万个字形,每个字形都在四种不同的变换下与参考 C 解释器进行了对比。

Apple 报告称,为该项目编写的测试代码量几乎是解释器本身代码量的四倍。

Swift 中的技术优化

为了达到或超过 C 的性能,Apple 专注于四个主要的优化类别,以消除运行时开销和内存分配。

消除引用计数与排他性开销

Swift 的自动引用计数 (ARC) 可能会引入开销,特别是在存在别名时。Apple 通过以下方式缓解了这一问题:

  • 在整个架构中采用 ~Copyable 值类型,以消除对引用计数的需要。
  • 使用 Span(在 Swift 6.2 中引入)来高效地操作这些不可拷贝类型的序列。

优化跨语言数据桥接

解释器的初始版本会将数据从 C 结构体拷贝到 Swift 中并返回,这占用了 20% 的运行时时间。Apple 将其替换为投影类型 (projection types)。这些类型提供了对底层 C 结构体的安全、带边界检查的访问,而无需拷贝或转换数据,在保持原始 C 布局的缓存友好性的同时,提供了符合 Swift 习惯的可读性。

减少堆分配

为了避免与 mapfilter 等函数相关的短寿命内存分配成本,团队使用了 .lazy 序列或带有 continue 的标准循环。

对于栈操作,Apple 实现了延续传递 (continuation-passing) 方法。调用者传递一个在栈元素被移除之前操作 Span 的代码块,而不是分配一个新数组来返回弹出元素,从而在结构上消除了堆分配和元素拷贝。

最小化动态分派

为了防止协议和泛型带来的方法调用间接性,团队避免了过度泛化抽象,并鼓励内联。这使得 Swift 优化器能够对泛型上下文进行特化并提升边界检查。

社区洞察与背景

公告发布后,技术讨论突出了此次转换的成功与挑战:

  • 工具链稳定性: 一位开发者指出,在尝试使用文中描述的生命周期特性时遇到了编译器崩溃,这表明这些高级特性在 Apple 内部环境之外的通用使用中可能仍不稳定。
  • 更广泛的 OS 策略: 评论指出,这是 Apple 将各种 OS 层级迁移至内存安全语言的更大努力的一部分,正如最近的 "State of Platform" 主题演讲中所提到的。
  • AI 集成: Apple 明确提到,他们将迁移过程中的经验总结成了 LLM 编码助手的指令,随后用于加速后续项目中 C/C++ 到 Swift 的转换。

Apple 已在 GitHub 上以 MIT 许可证发布了 Swift TrueType hinting 解释器源代码,作为参考实现。

Sources