ほぼすべてのC++開発者は、LLVMインフラストラクチャとClangコンパイラを知っているでしょう。しかし、その生みの親がChris Lattnerであり、当時彼がわずか25歳だったことを知っている人はどれくらいいるでしょうか?どうやってそれが可能なのでしょうか?私は25歳のとき、C++の基本を理解しようとして時間を費やしていたのを覚えています :)
物語は一本の論文から始まる
2000年後半、Lattnerは研究助手兼修士課程の学生としてイリノイ大学アーバナ・シャンペーン校に入学しました。Vikram Adveと共に、彼は 最適化コンパイラのための革新的なインフラストラクチャであるLLVMを設計し、実装を開始しました。これが2002年の修士論文のテーマでした。2005年に博士課程を修了し、ポインタ集約型プログラムを最適化する新しい手法を研究し、それをLLVMに追加しました。
彼の論文の要旨から、LLVM設計の背後にある動機を紹介します。
現代のプログラミング言語とソフトウェアエンジニアリングの原則は、コンパイラシステムにますます多くの問題を引き起こしています。単純なコンパイル-リンク-実行モデルを使用する従来のアプローチは、新しい状況の要求の下で十分なアプリケーションパフォーマンスを提供できません。手続き間およびプロファイル駆動コンパイルへの従来のアプローチは、必要なアプリケーションパフォーマンスを提供できますが、アプリケーションの構築に非現実的な量のコンパイル時間を必要とします。この論文は、ユニークなマルチステージ最適化システムをサポートするコンパイラインフラストラクチャの設計と実装であるLLVMを提示します。このシステムは、広範な手続き間およびプロファイル駆動の 最適化を サポートするように設計されており、商用コンパイラシステムで使用できるほど効率的です。LLVM仮想命令セットは、システムをまとめる接着剤です。これは低レベルの表現ですが、高レベルの型情報を持っています。これにより、低レベル表現の利点(コンパクトな表現、 多様な 利用可能な変換など)が得られると同時に、 リンク時およびリンク後の積極的な手続き間最適化をサポートする高レベル情報も提供されます。特に、このシステムは、実行時およびマシンの それ以外の 未使用のアイドル時間の両方で、フィールドでの最適化をサポートするように設計されています。
要するに、LLVMの背後にあるアイデアは、Javaバイトコードや.NET ILに似たLLVM中間表現(IR)を使用することです。
LLVM IRは、コンパイラのオプティマイザセクションにある中レベルの解析と変換をホストするように設計されています。軽量なランタイム最適化、関数間/手続き間最適化、プログラム全体の解析、積極的な再構築変換など、多くの具体的な目標を念頭に設計されました。しかし、最も重要な側面は、明確に定義されたセマンティクスを持つファーストクラスの言語としてそれ自体が定義されていることです。
3つの整数パラメータを受け取り、それらの算術的な組み合わせを返す、比較的単純な関数を考えてみましょう。
int mul_add(int x, int y, int z) {
return x * y + z;
}
この関数のLLVM IRは次のようになります。
define i32 @mul_add(i32 %x, i32 %y, i32 %z) {
entry:
%tmp = mul i32 %x, %y
%tmp2 = add i32 %tmp, %z
ret i32 %tmp2
}
この論文の目標である最適化に焦点を戻りましょう。このコンパイラフェーズを説明するにあたって、LLVMの父であるChris Lattnerのこの 記事で説明されています。
「最適化がどのように機能するかを直感的に理解するために、いくつかの例をたどるのが役立ちます。コンパイラの最適化には多くの異なる種類があるため、任意の問題を解決するためのレシピを提供するのは困難です。とはいえ、ほとんどの最適化は単純な3部構成に従っています。
- 変換するパターンを探す。
- マッチしたインスタンスに対して変換が安全/正確であることを検証する。
- コードを更新しながら変換を実行する。
ツール構築のためのインフラストラクチャとしてのLLVMとClang
Clangの主要な設計概念は、ライブラリベースのアーキテクチャの使用です。この設計では、フロントエンドのさまざまな部分を別々のライブラリにきれいに分割でき、それらを異なるニーズや用途に合わせて組み合わせることができます。さらに、ライブラリベースのアプローチは、優れたインターフェースを促進し、新しい開発者が参加しやすくします(全体像の小さな部分だけを理解すればよいからです)。

LLVM/Clangの設計により、ライブラリベースのアーキテクチャのおかげで、コンポーネントの再利用や他のプロジェクトへの新機能の統合が容易になり、ツールを構築するための強力なインフラストラクチャとなっています。
clang-tidy、clang-query、CppDependなど、多くのツールがLLVMとClangをベースにしています。
Clangは常に新しい標準に対応
Clangは常に新しい標準に歩調を合わせています。さらに、今後の機能に対する実験的なサポートを常に提供しています。現在のバージョンでは、C++17に続くC++標準(仮称C++2a)のいくつかの提案機能に対する実験的なサポートがあります。これらの機能のサポートは、ドラフトC++2a標準の進化に伴い、予告なく変更または削除される可能性があることに注意してください。
次の -std=c++2a オプションで、C++2aモードでClangを使用できます。
| 言語機能 | C++2a提案 | Clangで利用可能? |
|---|---|---|
| ビットフィールドのデフォルトメンバー初期化子 | P0683R1 | Clang 6 |
| const&-qualified pointers to members | P0704R1 | Clang 6 |
| Allow ラムダキャプチャ の許可 | P0409R2 | Clang 6 |
| __VA_OPT__ プリプロセッサのカンマ省略 | P0306R4 | Clang 6 |
| 指定初期化子 | P0329R4 | Partial (extension) |
| クラステンプレート引数推論における初期化子リストコンストラクタ | P0702R1 | Clang 6 |
| 特殊化のアクセスチェック | P0692R1 | Partial |
Clangは現在、Google、Microsoft、そしてもちろん最初から使用しているAppleを含む多くの大企業に採用されています。
LLVMインフラストラクチャは、多くのプログラミング言語のコンパイラやインタプリタを実装するために使用できます。例えば、PostgreSQLは現在、クエリ実行を最適化するためにLLVM JITを使用しています。これは こちらで説明されています。
現在、PostgreSQLはインタプリタを使用してSQLクエリを実行しており、これはかなり低速です。しかし、LLVM JITでクエリを「その場で」コンパイルすることで、大幅な高速化を実現できます。この方法では、与えられたSQLクエリに対してより 効果的な コードを生成できます。これは 実行時に知られる情報を使用して特殊化され、その後LLVMでさらに最適化されます。このアプローチは、パフォーマンスがCPUバウンドである複雑なクエリで特に重要です。
すべてのLLVM/Clang開発者に感謝し、革命を起こし、意味のあるツールを開発するための強力なインフラストラクチャを提供してくれたChris Lattnerに特別な感謝を捧げます。
最後に、若い開発者へのメッセージです。次のChris Lattnerになって、プログラミングの世界に革命を起こしてください :)
