Apple 使用 Swift:迁移 TrueType Hinting 解释器

Hacker News Top 新闻

摘要

Apple 将 TrueType Hinting 解释器从 C 语言迁移到 Swift,实现了内存安全并提升了 13% 的性能。源代码已开源。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/12 20:56

# Swift 在苹果的应用:迁移 TrueType 提示解释器 来源:https://www.swift.org/blog/migrating-truetype-hinting-to-swift/ TrueType 是一种广泛使用的矢量字体标准,用于在网页、PDF、操作系统和应用程序中渲染文本。像 Helvetica、Garamond 和 Monaco 这些常见字体,都是基于 TrueType 轮廓构建的。该格式规定了一个**提示解释器**(https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#environment),旨在帮助轮廓在低分辨率显示器上忠实地栅格化。现代高分辨率显示器仅凭轮廓就能实现精美的排版,但那些需要提示才能清晰渲染的 TrueType 字体仍在被使用,我们也会继续支持它们。 字体解析器处理来自不可信来源的数据,这使得 TrueType 提示解释器成为一个安全关键的攻击面。为了让该格式在苹果平台上更具弹性,我们将其提示解释器从 C 语言重写为**内存安全**(https://docs.swift.org/compiler/documentation/diagnostics/strict-memory-safety/)的 Swift,并将在 2025 年秋季发布版中推出。除了内存安全,我们还提升了性能:平均而言,我们的 Swift 解释器比它所取代的 C 解释器快 13%。伴随这篇文章,我们还发布了**Swift TrueType 提示解释器的源代码**(https://github.com/apple/truetype-hinting-interpreter-example)。我们希望分享我们的经验能帮助其他人在 Swift 中进行类似的工作。 ## TrueType 与提示引擎 苹果公司在 20 世纪 80 年代末开发了 TrueType,并于 1991 年随 System 7 的发布一同推出。TrueType 在当时是一项重大突破:它让字体开发者能够极大地控制字形的显示方式,借助先进的网格适配算法和围绕专用字节码解释器构建的精巧提示引擎。TrueType 在当时远不如今天强大的计算机上完成所有这些工作,因此必须在性能上做极致的调优。 随后,互联网彻底改变了字体的使用方式。TrueType 在 1994 年变得可嵌入 PDF 文件,2008 年可嵌入网页,并且至今仍像以往一样重要。然而,这些新的使用场景带来了额外的风险:TrueType 现在可能暴露于来自互联网任何地方的不可信字体。TrueType 字体可能包含程序,提示解释器通过字节码解释器运行这些程序。这个解释器涉及输入驱动的控制流、复杂的数据结构以及精细的内存管理——这恰恰是那种很难做到完美、内存错误更容易被利用的代码类型。这种高内在复杂性也使得正确性尤为重要。 重写需要一种内存安全的语言,既能集成到现有代码库中,又能提供与所取代实现相当的性能水平。Swift 自然是这项任务的选择。 二进制兼容性对这个项目的成功至关重要:现有程序必须像以前一样继续运行,实际上不会察觉到有了新的实现。这意味着不仅要有接口兼容性,还要在字形渲染上与 C 实现实现像素级一致。提示可以彻底改变字形在屏幕上的外观,因此解释器行为的微小变化都可能导致用户可见的显著变化。对于本项目,我们将**正确性**定义为与 C 实现输出的精确兼容。 为了确保正确性,我们开发了两套测试套件。第一套是单元测试套件,可以针对两种实现运行,对两者都提供详尽的(99.7%)代码覆盖率。该套件包含在 Swift 解释器的开源版本中。然后,为了代表真实世界的工作负载,我们使用模糊测试器将包含 1000 万个 PDF 文件的语料库缩减到 4200 个,且不损失任何代码覆盖率。缩减后的语料库中的文档嵌入了 25,572 种字体,共计 2700 万个字形,我们使用四种不同的变换对每个字形进行渲染,并将生成的位图与参考解释器进行比较。这让我们对新解释器的兼容性充满信心。 到项目结束时,我们编写的测试代码行数几乎是 Swift 解释器本身的四倍。在我们新的实现通过所有测试后,我们将注意力转向了性能。我们使用 PDF 渲染时间在高层面上评估性能,然后根据渲染三种不同字体中所有字形的基准测试进行迭代改进。这些改进主要分为四个类别。 Swift 使用**自动引用计数**(https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/)来管理共享引用类型的生命周期,并使用运行时独占性检查来防止对数据结构的重叠访问。这些开销来源通常会被别名引用放大,而解释器规范中本就存在一定数量的不可消除的别名。通过放弃可复制性的便利,在整个架构中采用 `~Copyable`(https://developer.apple.com/documentation/Swift/Copyable)值类型(另见:使用 `struct` 而非 `class`(https://developer.apple.com/documentation/swift/choosing-between-structures-and-classes)),并将引用类型保留给高层抽象,可以消除这些开销来源。Swift 6.2 中引入的 **`Span`**(https://developer.apple.com/documentation/swift/span)支持向后部署至 macOS 10.14.4 和 iOS 12.2,使我们能够高效地操作这些类型的序列。 有时,我们需要在跨语言边界时改变结构化数据的“形状”,以更好地匹配另一侧的惯用法。在 Swift 中,字形轮廓由一系列点表示,每个点都带有一个“是否在曲线上”的标志、每个轴“是否被触碰”的标志,以及三个坐标对:原始坐标(字体基本单位)、缩放坐标(到所需字号)、提示坐标(解释器程序的输出)。原始的 C 代码将这些点存储在一个包含八个数组的结构体中。从性能角度看,这样做很好,因为它对缓存友好:你可以对许多点的某个维度进行长时间操作,速度很快。但在 Swift 中将数据暴露为点元素的集合,使得源代码更容易理解。 我们最初编写的跨语言桥接代码优先考虑了快速性、安全性和简单性,方法是将字形的数据从其 C 结构体复制到 Swift,然后在程序完成后复制回去。最初,这些复制操作占用了新解释器运行时的大约 20%。最终,我们使用了投影类型来提供对底层 C 结构的安全访问。这样,Swift 在无需复制或转换底层数据结构的情况下提供了可读性。 遵循 WebKit 的**更安全的 Swift 指南**(https://github.com/WebKit/WebKit/wiki/Safer-Swift-Guidelines),下面的示例演示了如何将来自 C 的桥接结构体包装在投影类型中,该类型使用 **`Ref`**(https://github.com/apple/swift-collections/blob/main/Sources/ContainersPreview/Types/Ref.swift)确保生命周期安全,中介对底层数据的边界安全访问,并向调用者返回符合 Swift 风格的类型。所有 `unsafe` 表达式都带有 `// SAFETY:` 注释,记录安全不变量以及保证其成立的推理。 ```swift @safe struct Zone: ~Copyable, ~Escapable { let _element: Ref @_lifetime(copy element) init(wrapping element: Ref) { // SAFETY: 调用者传入的 `fnt_ElementType` 必须满足: // * `sp`、`ep` 指向长度 ≥ `maxContourCount` 的数组。 unsafe _element = element } func readContour(index: Int) -> ClosedRange { precondition(0..<contourCount ~= index) let i: Int = index return unsafe ClosedRange( uncheckedBounds: ( _element.pointee.sp.advanced(by: i).pointee, _element.pointee.ep.advanced(by: i).pointee ) ) } } ``` ## 安全抽象的基础 TrueType 格式规范了一个堆栈,指令从中弹出操作数并将结果推回。我们的 C 解释器将这个堆栈表示为一个包含 65536 个 `uint16_t` 元素的固定大小数组,但我们希望避免在 Swift 中为堆栈分配最大容量,因为对于我们关心的字体来说,这太多了。在 Swift 中,堆栈被表示为一个可增长的存储元素类型 `~Copyable` 的数组,这种类型有特定的生命周期要求。没有可复制性意味着不能使用大多数集合类型,但我们可以使用操作系统内存映射器,它允许我们创建初始用途的虚拟地址空间映射,仅在需要时提交物理页面。在典型字体中,实际的堆叠深度约为 20 个元素。 我们遇到的第一个值得注意的性能问题来自一个简单的辅助方法: ```swift mutating func pop(count n: Int) -> [Element] { defer { items.removeLast(n) } return Array(items.suffix(n)) } ``` 这种方法会分配一个数组并复制元素,即使调用者只是想立即检查一些已知为 `~Copyable` 的类型的值。在优化这个操作而不牺牲安全性之后,我们最终采用了一种延续传递的方式:调用者传入一个闭包,该闭包可以在元素被移除之前对堆栈的一个切片进行操作。Swift 的编译时独占性检查确保在闭包内部无法修改堆栈,并且这种接口在结构上消除了任何堆分配或元素复制的需要。 ```swift mutating func pop( count n: Int, _ op: (borrowing Span) throws(E) -> R ) throws(E) -> R { defer { items.removeLast(n) } return try op(items.span.extracting(last: n)) } ``` 像协议、泛型和继承这样的抽象机制非常强大,但它们会引入方法调用间接性,可能在运行时表现为动态派发。这种开销通常可以被优化器消除,但在所有条件下并非总是可能。在我们的例子中,不让抽象变得比必要的更泛化,并鼓励工具链进行内联,就足以让优化器提升边界检查并专门化所有泛型上下文。当你分析代码时,如果在热路径中看到未专门化的泛型或协议分发表,这表明优化器没有足够的可见性来优化调用点,你的实现可能受益于内联。 此时,习惯于在项目中苦求性能改进的人可能会合理地认为,我们的优化可能会以可读性为代价,但实际上 Swift 的类型系统和优化器使我们能够采用那些产生高度可读代码的抽象。例如: - **`FixedPoint`** 类型提供了与整数类型相同的人体工程学,封装了复杂的舍入和移位运算。 - **`StackElement`** 提供了对 32 位值的访问,并内置了对所有八种支持数值类型的转换。 - 我们的投影类型为那些没有考虑这些因素而结构化的数据提供了安全且自然的访问。 Swift 的类型系统使得定义强大且富有表现力的抽象成为可能。在开启优化的构建中,我们所有的抽象都增加了零成本,同时显著提高了可读性。 ## 内存安全且比 C 更快 我们这个项目的目标是让 TrueType 提示解释器完全内存安全,具有与 C 实现相同的可观察渲染行为,并且达到不退化任何用户可见基准的性能水平。我们实现了这些目标。Swift 解释器在语言互操作边界处包含少量经过彻底验证的 `unsafe` 语句;自启用以来,还没有针对它报告过错误;而且它**更快**。平均而言,Swift 解释器比它所取代的 C 解释器快 13%。下面的图表显示了在 macOS 上附带的所有提示字体以及部分非系统字体的采样中,Swift 实现与 C 实现中每个字形所花费的平均 CPU 百万周期数: **● 系统字体** · **● 非系统字体** · 线以下:Swift 更快 · 线以上:C 更快 尽管整体性能有所提升,但我们并没有对所有内容进行优化!新解释器的所有内部状态都是用不可复制结构体编写的,这些结构体通过其操作借用,但顶层类型本身是一个 `@objc class`,它通过模块边界从 Objective-C++ 文件中被调用。热路径很快,冷路径很方便。Swift 语言使得这个项目成为可能。Swift 是内存安全的,具有良好的人体工程学,并且可以像精心编写的 C 语言一样快。这使得它非常适合应用程序和系统开发。使用不可复制类型、值类型和 `Span` 的代码默认既安全又快速,而模块私有类型可以用来共同定义一个架构,且无需额外成本。再加上详尽的测试覆盖率,这些定义良好的内部接口边界使重构变得容易得多,这反过来加速了“测量-修复”的优化循环,同时最大限度地降低了引入错误的风险。 这次迁移工作加深了我们对 Swift 的专业知识,并为我们提供了进一步构建的基础。完成迁移后,我们将所学到的经验提炼为 LLM 编码助手的指令,并随后在其他项目中成功使用。LLM 提高了我们团队将 C/C++ 转换为 Swift 的工作效率,并在执行此类代码转换方面证明很有价值。 伴随这篇文章,我们在 GitHub 上发布了**Swift TrueType 提示解释器的源代码**(https://github.com/apple/truetype-hinting-interpreter-example)。这是生产级代码,旨在作为参考实现,而非持续的开源项目。我们希望看到这些技术在实际中的应用能帮助其他人取得类似的结果。 --- ## 继续阅读 - ### 宣布网络工作组 2026年6月4日 Swift 生态系统指导委员会很高兴地宣布创建…… 更多(https://www.swift.org/blog/announcing-networking-workgroup/)

相似文章

Apple 内部:内核中的 Swift

Lobsters Hottest

Apple 已开始通过一项名为 KernelKit 的新工作将 Swift 集成到内核中,嵌入式 Swift 运行时出现在 macOS 和 iOS 中,标志着向内存安全内核扩展迈出了一步。

为我的离线渲染器制作一个着色语言

Hacker News Top

作者详细介绍了为其离线CPU渲染器SORT创建的自定义着色语言库——微型着色语言(TSL),并解释了其动机,包括学习、灵活性、Apple Silicon支持以及相比使用OSL减少依赖等。