苹果的 Swift:迁移 TrueType 微调解释器
Swift at Apple: Migrating the TrueType hinting interpreter

原始链接: https://www.swift.org/blog/migrating-truetype-hinting-to-swift/

为了提升安全性和性能,苹果已将旗下的 TrueType 指令解释器从 C 语言重写为内存安全的 Swift,该版本将于 2025 年秋季发布。 TrueType 字体使用复杂的字节码解释器进行渲染,在处理不可信的网页或 PDF 内容时,往往是重要的攻击面。此次转向 Swift 不仅消除了内存安全漏洞,还确保了渲染效果与原有 C 语言实现保持像素级一致。 该项目优先考虑了高性能与安全性,最终运行速度平均比 C 版本提升了 13%。关键优化技术包括:采用不可复制值类型(non-copyable value types)以减少开销;利用 `Span` 实现内存安全的数据访问;以及使用高效的延续传递模式(continuation-passing patterns)取代堆内存分配。这些改进在规避手动内存管理风险的同时,兼顾了代码的可读性与性能。 苹果通过详尽的单元测试及超过 25,000 种字体的精简语料库,对新实现进行了验证。最终的代码库证明了在底层系统编程中,Swift 完全可以作为 C 语言的高性能且内存安全的替代方案。苹果现已开源相关代码,旨在为进行类似迁移的开发者提供参考。

苹果公司近期详细介绍了将 TrueType 提示(hinting)解释器迁移至 Swift 的过程,凸显了其将操作系统级组件转向内存安全语言的内部大举措。此举旨在提升安全性和性能,并强调了苹果在整个软件堆栈中采用 Swift 的承诺。 此次讨论的要点如下: * **内存安全与性能:** 苹果正优先将 Swift 用于底层系统软件,以消除内存相关的漏洞。TrueType 项目证明,通过严谨的测试与规划,Swift 完全能够满足敏感系统组件的性能要求。 * **工程文化:** 苹果代表指出,团队正积极招聘系统级职位,并强调对操作系统架构的深入理解比 Swift 的预先经验更为重要。 * **“用 Swift 重写”(RIS)趋势:** 该讨论证实,Swift 正被应用于包括内核和用户空间在内的所有操作系统层级,且苹果正越来越多地利用 AI 工具(如 Claude)来辅助这些复杂的迁移工作。 * **辩论:** 社区在“Swift 与 Rust”之争上仍存在分歧,许多人指出,Swift 卓越的 ABI 稳定性和与 Objective-C 的互操作性,使其在苹果现有的生态系统中具有独特的优势。
相关文章

原文

TrueType is a widely used vector font standard for rendering text in web pages, PDFs, operating systems, and applications. Familiar fonts like Helvetica, Garamond, and Monaco are all built on TrueType outlines. The format specifies a hinting interpreter intended to help outlines rasterize faithfully on low-resolution displays. Modern high-resolution displays enable beautiful typography from outlines alone, but TrueType fonts that need hinting to render legibly remain in use and we continue to support them.

Font parsers process data from untrusted sources, making the TrueType hinting interpreter a security-critical attack surface. To make the format more resilient on Apple platforms, we rewrote its hinting interpreter from C to memory-safe Swift for the Fall 2025 releases. In addition to memory safety, we also improved performance: on average, our Swift interpreter runs 13% faster than the C interpreter it replaced.

To accompany this post, we’ve also published the source code of the Swift TrueType hinting interpreter. We hope sharing our experience helps others doing similar work in Swift.

TrueType and the hinting engine

Apple developed TrueType in the late 1980s and released it with the launch of System 7 in 1991. TrueType was a major breakthrough for the time: it gave font developers enormous control over how glyphs are displayed, with an advanced grid-fitting algorithm and a sophisticated hinting engine built around a special-purpose bytecode interpreter. TrueType did all this on computers that were vastly less powerful than today’s, so it had to be extremely well-tuned for performance.

Then the internet revolutionized how fonts were used. TrueType became embeddable in PDF files in 1994 and in web pages in 2008, and it remains as relevant as ever. However, these new use cases brought additional risk: TrueType could now be exposed to untrusted fonts from anywhere on the internet.

TrueType fonts may contain programs the hinting engine runs through a bytecode interpreter. This interpreter involves input-driven control flow, complex data structures, and careful memory management—exactly the kind of code that’s hard to make perfect and where memory errors are easier to exploit. This high inherent complexity also makes correctness especially important.

A rewrite required a memory-safe language that could integrate into the existing codebase and provide an equivalent level of performance to the implementation it was replacing. Swift was the obvious choice for the task.

Binary compatibility was crucial for this project to succeed: existing programs had to continue to function the same as they did before, effectively unaware that a new implementation was in place. This means not just interface compatibility but pixel-identical glyph rendering as well, relative to the C implementation. Hinting can radically change the on-screen appearance of glyphs, so a small change in the interpreter’s behavior could result in substantial user-visible changes. For this project, we defined correctness to mean exact compatibility with the C implementation’s outputs.

To ensure correctness, we developed two test suites. The first was a unit test suite that can target both implementations, providing exhaustive (99.7%) code coverage for both. This suite is included with the open source release of the Swift interpreter.

Then, to represent real-world workloads, we used a fuzzer to minimize a corpus of 10 million PDF files down to 4,200 without any loss of code coverage. The documents in the minimized corpus embedded 25,572 fonts with a total of 27 million glyphs that we rendered using four different transformations each, comparing the resulting bitmaps against the reference interpreter. This gave us confidence in the new interpreter’s compatibility.

By the end of the project, we wrote nearly four times as many lines of test code as we wrote for the Swift interpreter itself.

Once our new implementation passed all its tests, we turned our attention to performance. We assessed performance at a high level using PDF render time, and then iterated on improvements guided by benchmarks that rendered all of the glyphs from three different fonts. These improvements fell into four main categories.

Swift uses automatic reference counting for managing the lifetime of shared reference types, and runtime exclusivity checking for preventing overlapping access to data structures. These sources of overhead are often exacerbated by aliasing, an irreducible amount of which existed in the interpreter’s specification by design.

These sources of overhead can be eliminated by giving up the convenience of copyability, adopting ~Copyable value types (see also: struct instead of class) throughout the architecture, reserving reference types for high-level abstractions. Span, introduced in Swift 6.2 with back-deployment support all the way back to macOS 10.14.4 and iOS 12.2, allowed us to efficiently operate on sequences of these types.

Sometimes we want to change the “shape” of structured data when crossing language boundaries to better match the idioms on the other side. In Swift, glyph outlines are represented by a sequence of points, each of which carries a flag for whether it’s ‘on a curve’, flags for whether it’s been ‘touched’ on each axis, and three coordinate pairs: original (in the font’s base units), scaled (to the desired point size), and hinted (the interpreter program’s output).

The original C code stored these points in one struct with eight arrays. This is good from a performance perspective because it’s cache-friendly: you can operate on a dimension of many points in long runs, which is fast. But exposing the data in Swift as a collection of point elements resulted in source code that was easier to follow.

The initial cross-language bridging code we wrote prioritized expediency, safety, and simplicity by copying the glyph’s data from its C struct into Swift and then back after the program completed. Initially, those copies accounted for around 20% of the new interpreter’s runtime. In the end we wound up using projection types that provide safe access to the underlying C structure. Swift thus gives us readability without copying or otherwise transforming the underlying data structure.

Following WebKit’s Safer Swift Guidelines, the example below demonstrates how to wrap a bridged structure from C in a projection type that uses Ref for lifetime safety, brokers bounds-safe access to the underlying data, and returns idiomatic Swift types to its callers. All unsafe expressions bear // SAFETY: comments that document the safety invariants and the reasoning which guarantees that they are true.

@safe struct Zone: ~Copyable, ~Escapable {
  let _element: Ref<fnt_ElementType>

  @_lifetime(copy element)
  init(wrapping element: Ref<fnt_ElementType>) {
    // SAFETY: the `fnt_ElementType` passed by the caller must satisfy:
    // * `sp`, `ep` point at arrays of length ≥ `maxContourCount`.
    unsafe _element = element
  }

  func readContour(index: Int) -> ClosedRange<Int> {
    precondition(0..<contourCount ~= index)
    // SAFETY: `index` is bounds checked above; `_element.pointee.sp` and `_element.pointee.ep`
    // are live arrays of length `maxContourCount` per `init(wrapping:)` premises.
    return unsafe Int(_element.value.sp[index])...Int(_element.value.ep[index])
  }
}

While runtime overhead imposes its costs in aggregate, and copying data across the language border showed up as a specific hot spot, the costs of short-lived memory allocations can show up in both ways.

Operations like filter and map allocate memory, but that allocation is only necessary if the value escapes. The Swift standard library provides .lazy.map and .lazy.filter, but they don’t work in every case. For logic that only iterates over the filter or map, it’s much more efficient to loop with continue (or use for … in … where) and transform elements into local variables as necessary.

Allocations that exist exclusively to pass results to a function’s caller can be elided too. For example, we often needed to pop the last n elements from the interpreter’s stack. The obvious implementation of this operation has to allocate space for the elements it returns (because the function simultaneously removes them) and looks something like:

mutating func pop(
  count n: Int
) -> [Element] {
  defer { items.removeLast(n) }
  return Array(items.suffix(n))
}

Optimizing this operation without compromising safety eventually led us to a continuation-passing approach where the caller passes a block that can operate on a slice of stack elements before they are removed. Swift’s compile-time exclusivity checking ensured that the stack could not be modified from inside the block, and the interface structurally eliminated any need for heap allocations or element copying.

mutating func pop<R, E: Error>(
  count n: Int,
  _ op: (borrowing Span<Element>) throws(E) -> R
) throws(E) -> R {
  defer { items.removeLast(n) }
  return try op(items.span.extracting(last: n))
}

Abstraction mechanisms like protocols, generics, and inheritance are very powerful, but they introduce method-call indirection that may manifest at runtime as dynamic dispatch. This overhead can often be eliminated by the optimizer, but that is not possible in all conditions. In our case, not making abstractions more generic than necessary and encouraging the toolchain to inline were sufficient for the optimizer to hoist bounds checks and specialize all of our generic contexts. When you’re profiling your code, if you see unspecialized generics or protocol witness tables in hot paths, that’s a sign that the optimizer does not have sufficient visibility to optimize the call sites and that your implementation may benefit from inlining.

At this point, anyone accustomed to grinding out performance improvements in a project might reasonably expect that our optimizations could come at the cost of readability, but in reality Swift’s type system and optimizer enabled us to employ abstractions that resulted in highly legible code. For example:

  • FixedPoint types provided the same ergonomics as integral types, encapsulating complex rounding and shifting arithmetic.
  • StackElement provided access to 32-bit values with built-in conversions for all eight of its supported numeric types.
  • Our projection types provided safe and natural access to data that was not structured with those considerations in mind.

Swift’s type system makes it possible to define powerful and expressive abstractions. When built with optimizations, all of our abstractions added zero cost while substantially improving readability.

Memory-safe and faster than C

Our goal for this project was to make the TrueType hinting interpreter fully memory-safe, to have the same observable rendering behavior as the C implementation, and to achieve a level of performance that did not regress any user-visible benchmarks.

We met these goals. The Swift interpreter includes a small number of thoroughly verified unsafe statements at the language interop boundary; there have been no bugs reported against it since it was enabled; and it’s faster.

On average, the Swift interpreter runs 13% faster than the C interpreter it replaced. Here is a chart showing the average CPU megacycles spent per glyph in the Swift implementation versus the C implementation for all of the hinted fonts that ship on macOS, plus a sampling of non-system fonts:

● System font · ● Non-system font · below the line: Swift faster · above: C faster

Despite the overall performance improvement, we did not optimize everything! All of the new interpreter’s internal state was written in terms of noncopyable structures that were borrowed by its operations, but the top-level type itself is an @objc class that gets called across a module boundary from an Objective-C++ file. The hot paths are fast, and the cold paths are convenient.

The Swift language made this project possible. Swift is memory-safe, has great ergonomics, and can be as fast as carefully written C. This makes it an excellent language for both application and systems development.

Code using noncopyable types, value types, and Span is both safe and fast by default, and module-private types can be used to collectively define an architecture at no additional cost. Together with exhaustive test coverage, these well-defined internal interface boundaries make refactoring substantially easier, which in turn accelerates the measure-and-fix optimization loop while minimizing the risk of introducing bugs.

This migration effort has deepened our Swift expertise and given us a foundation to build on. After completing the migration, we distilled what we learned into instructions for LLM coding assistants, and have since used them successfully in other projects. LLMs have improved the efficiency of our team’s work converting C/C++ to Swift, and have proven valuable in performing the kind of code transformations used in this effort.

To accompany this post, we’ve published the source code for the Swift TrueType hinting interpreter on GitHub. This is production code, intended as a reference implementation rather than an ongoing open source project. We hope seeing how these techniques work in practice helps others achieve similar results.


Continue Reading

联系我们 contact @ memedata.com