この記事の要点
- Apple は、TrueType フォントの hinting インタプリタ(低解像度ディスプレイでもアウトラインを忠実にラスタライズするためのバイトコードインタプリタ)を、C からメモリ安全な Swift に書き換えました。2025年秋のリリースから採用されています。
- フォントパーサは信頼できない入力を処理するためセキュリティ上のクリティカルな攻撃面です。書き換えの第一の動機は メモリ安全性の確保 でしたが、結果として平均で C 実装より 13% 高速 という性能改善も得られました。
- 既存プログラムから見て挙動を変えないこと、すなわち C 実装と ピクセル単位で同一のグリフ描画 を保つことを「正しさ」の定義とし、網羅的なユニットテストとファザーで検証しました。
~Copyableな値型、Span、projection type、継続渡し(continuation-passing)といった Swift の機能を使い、抽象化のコストをゼロに抑えながら可読性の高いコード を実現しています。インタプリタのソースコードはリファレンス実装として公開されています。
採用前の課題
TrueType は、Webページ・PDF・OS・アプリケーションでテキストを描画するために広く使われているベクトルフォント規格です。Helvetica や Garamond、Monaco といった馴染みのあるフォントも TrueType アウトラインで作られています。
この規格には hinting インタプリタ が定められています。アウトラインだけでも美しいタイポグラフィが得られる現代の高解像度ディスプレイとは異なり、低解像度では hinting によってグリフを読みやすく整える必要があります。TrueType フォントはこのインタプリタが実行するプログラムを内部に含むことができ、インタプリタは入力に応じた制御フロー、複雑なデータ構造、慎重なメモリ管理を伴います。これはまさに正しく書くのが難しく、メモリエラーが悪用されやすいコードです。
TrueType は1994年に PDF へ、2008年に Web ページへ埋め込めるようになり、インターネット上のどこからでも取得した 信頼できないフォント にさらされるようになりました。そのため hinting インタプリタはセキュリティ上クリティカルな攻撃面となっており、これをより堅牢にすることが書き換えの出発点でした。
Swiftでどう改善したのか
書き換えには、既存のコードベースに統合でき、置き換え対象と同等の性能を出せるメモリ安全な言語が必要でした。Swift はこの条件を満たす自然な選択でした。
「正しさ」をピクセル単位の互換性として定義する
このプロジェクトではバイナリ互換性が決定的に重要でした。既存プログラムは新実装の存在に気付かないまま、これまでと同じように動作し続けなければなりません。hinting はグリフの見た目を大きく変えるため、インタプリタの挙動がわずかにずれただけでもユーザーから見える違いが生じます。そこで C 実装の出力とピクセル単位で完全に一致すること を正しさの定義としました。
正しさは2つのテストスイートで検証しています。
- 両実装を対象にできるユニットテストスイート。両実装に対して 99.7% という網羅的なコードカバレッジを提供します(オープンソース版にも含まれています)。
- 実際のワークロードを代表させるためのファザー。1000万件の PDF をコードカバレッジを落とさずに 4,200 件まで絞り込み、そこに埋め込まれた 25,572 フォント・2700万グリフを4種類の変換で描画して、リファレンス実装のビットマップと突き合わせました。
最終的に、インタプリタ本体の約4倍の行数のテストコードを書いたとされています。
性能を引き出す4つの工夫
テストをすべて通過させたうえで性能改善に取り組み、その工夫は大きく4つに整理されています。
- 実行時オーバーヘッドの最小化。 Swift は参照型の生存期間管理に自動参照カウント(ARC)を、データへの重複アクセスの防止に実行時の排他的アクセスチェックを使います。これらのオーバーヘッドは、コピー可能であることの利便性を手放して
~Copyableな値型をアーキテクチャ全体に採用し、参照型は高レベルの抽象化に限定することで取り除けます。Swift 6.2 で導入されたSpan(macOS 10.14.4・iOS 12.2 までさかのぼってバックデプロイ可能)が、こうした型の列を効率的に扱うのに役立ちました。 - 言語境界をまたぐデータの受け渡し。 グリフのアウトラインを表す点列は、C 側では8本の配列を持つ1つの構造体(キャッシュに優しい配置)で保持されています。当初は C 構造体から Swift へデータをコピーし、処理後に書き戻していましたが、このコピーが実行時間の約20%を占めていました。最終的には、元の C 構造体へ安全にアクセスする projection type を使い、コピーや変換なしに Swift らしい可読性を得ています。
- 短命なアロケーションの削減。
filterやmapは値がエスケープする場合にのみアロケーションが必要です。反復するだけなら.lazy版やfor … in … whereを使ってローカル変数へ変換する方が効率的です。また、戻り値を呼び出し元に渡すためだけのアロケーションも避けられます。 - 動的ディスパッチの抑制。 プロトコル・ジェネリクス・継承による呼び出しの間接化は、実行時に動的ディスパッチとして現れることがあります。必要以上に汎用にせず、ツールチェインにインライン化を促すことで、最適化器が境界チェックを巻き上げ、すべてのジェネリックコンテキストを特殊化できました。ホットパスに未特殊化のジェネリクスや protocol witness table が見えたら、最適化器に十分な可視性がない兆候であり、インライン化が有効なサインです。
短命なアロケーションを避ける例として、インタプリタのスタックから末尾の n 要素を取り出す pop が紹介されています。素朴に書くと、取り出した要素を返すためにアロケーションが必要です。
mutating func pop(
count n: Int
) -> [Element] {
defer { items.removeLast(n) }
return Array(items.suffix(n))
}
これを、呼び出し元が渡したブロックに要素のスライスを渡して削除前に処理させる 継続渡し の形に変えると、ヒープへのアロケーションも要素のコピーも構造的に不要になります。コンパイル時の排他的アクセスチェックにより、ブロックの内側からスタックを変更できないことも保証されます。
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))
}
C 構造体を安全に包む projection type は、WebKit の Safer Swift Guidelines に倣って書かれています。生存期間の安全性を保ちつつ境界安全なアクセスを仲介し、すべての unsafe 式には安全性の根拠を示す // SAFETY: コメントを付けるという方針です。
実運用から得られる示唆
性能改善は可読性を犠牲にして得られた、と思われがちですが、実際には Swift の型システムと最適化器のおかげで、抽象化を使いながら可読性の高いコードを書けたと報告されています。FixedPoint 型は整数型と同じ使い勝手で複雑な丸めとシフトの演算をカプセル化し、projection type は元々その用途を想定していないデータ構造にも安全で自然なアクセスを与えました。最適化を有効にしてビルドすると、これらの抽象化は コストゼロ で可読性だけを大きく改善しています。
成果は当初の目標どおりでした。新しいインタプリタは言語間の境界にごく少数の入念に検証された unsafe 文を含むだけで、有効化以降バグの報告はなく、そのうえで 平均 13% 高速 です。すべてが速くなったわけではなく、内部状態は借用される non-copyable 構造で書かれている一方、トップレベルの型自体は Objective-C++ から呼ばれる @objc class のままにするなど、ホットパスを速く・コールドパスを書きやすく、という割り切りもしています。
non-copyable 型・値型・Span を使ったコードは、デフォルトで安全かつ高速です。モジュール内に閉じた型でアーキテクチャを定義でき、網羅的なテストカバレッジとあわせて、明確な内部境界がリファクタリングを容易にし、「計測して直す」最適化のループを加速させました。重い計算と信頼できない入力を伴うセキュリティクリティカルなコードでも、Swift がメモリ安全性・性能・保守性を両立できることを示す事例です。