Clangは、GCCやMicrosoftのコンパイラと同様に、成熟したCおよびC++コンパイラであることが証明されています。しかし、Clangを特別なものにしているのは、単なるコンパイラではないという点です。それはツールを構築するための基盤でもあります。ライブラリベースのアーキテクチャのおかげで、コンポーネントをより簡単に再利用でき、新機能を他のプロジェクトへより柔軟に統合できます。
Clangの設計
他の多くのコンパイラ設計と同様に、Clangコンパイラには3つのフェーズがあります:
- フロントエンドはソースコードを解析し、エラーをチェックし、入力コードを表す言語固有の抽象構文木(AST)を構築します。
- オプティマイザは、フロントエンドが生成した表現に対して最適化を実行します。
- バックエンドは、マシンが実行する最終コードを生成します。これはターゲットに依存します。

Clangと他のコンパイラの違いは何でしょうか?
設計上の最も重要な違いは、ClangがLLVMを基盤としていることです。LLVMの背後にある考え方は、JavaにおけるバイトコードのようなLLVM中間表現(IR)を使用することです。LLVM IRは、コンパイラのオプティマイザ部分にある中間レベルの解析と変換を収容するように設計されています。軽量な実行時最適化、関数間/手続き間最適化、プログラム全体の解析、積極的な再構成変換など、多くの具体的な目標を念頭に設計されました。しかし最も重要な側面は、LLVM IR自体が、明確に定義された意味論を持つ第一級の言語として定義されていることです。
この設計により、コンパイラの大部分を再利用して他のコンパイラを作成できます。たとえば、フロントエンドだけを変更して、他の言語を処理できます。

I. フロントエンド
Clangはモジュール式に設計されており、各コンパイルフェーズは特定のモジュールによって実行されます。フロントエンドフェーズに関係するプロジェクトの一部は次のとおりです:

あらゆるフロントエンドパーサーと同様に、レクサーと意味解析が必要です。Clangフロントエンドは、-cc1引数を渡すことで実行できます。AST生成を含む、いくつかの機能をサポートしています:
clang -cc1 -ast-dump test.c
このコマンドラインはcc1_main関数によって処理されます。実行される興味深いメソッドの一部のシーケンスは次のとおりです:

ExecuteActionメソッドにはFrontendAction型のパラメータがあり、その目的は実行するフロントエンドアクションを指定することです。FrontendActionは抽象クラスであるため、具象的なフロントエンドアクションを実装するには、それを継承する必要があります。
Clangが実装しているすべてのフロントエンドアクションを CQLinqで調べてみましょう。そのためには、FrontendActionを直接または間接的に継承しているすべてのクラスを検索します。
from t in Types
let depth0 = t.DepthOfDeriveFrom(“clang.FrontendAction”)
where depth0 >= 0 orderby depth0
select new { t, depth0 }

多くのフロントエンドアクションが利用可能です。たとえば、ASTDumpActionを使用すると、最終実行ファイルを作成せずにASTを生成できます。ほぼすべてのフロントエンドアクションはASTFrontendActionを継承しており、これは生成されたASTを操作することを意味します。
この設計の興味深い点は、独自のFrontendActionを簡単に組み込めることです。新しいものを実装するだけで済みます。
ASTに対して何らかの処理を実行するにはどうすればよいでしょうか?
各ASTFrontendActionは1つ以上のASTConsumerを作成します。ASTConsumerクラスは抽象クラスであり、特定のニーズを満たすために独自のASTコンシューマーを実装する必要があります。
FrontendActionは、次のグラフに示すようにASTコンシューマーを呼び出します。

すべてのASTConsumerクラスを CQLinq:
from t in Types
let depth0 = t.DepthOfDeriveFrom(“clang.ASTConsumer”)
where depth0 == 1
select new { t, depth0 }

CodeGeneratorはASTコンシューマーの一例です
前述のとおり、LLVMの強みの1つはIRを使用することであり、それを生成するにはASTの処理が必要です。CodeGeneratorは、IR生成を担当するASTConsumerの派生クラスです。興味深いことに、この処理はClangCodeGenという別のプロジェクトに分離されています。
LLVM IR生成に関係するクラスの一部は次のとおりです:

II. オプティマイザ
このフェーズについては、LLVMの生みの親であるChris Lattnerがこちらの 記事:
「最適化がどのように機能するかを直感的に理解するには、いくつかの例を順に見るのが有用です。コンパイラ最適化にはさまざまな種類があるため、任意の問題を解決するための決まった手順を示すのは困難です。とはいえ、ほとんどの最適化は単純な3部構成に従います:
- 変換すべきパターンを探す。
- 一致したインスタンスに対して、その変換が安全/正しいことを確認する。
- コードを更新して変換を実行する。
「Pass」クラスを継承するクラスを検索して、LLVMCoreのパスを調べてみましょう。
from t in Types
let depth0 = t.DepthOfDeriveFrom(“llvm.Pass”)
where t.ParentProject.Name==”LLVMCore” && depth0 >= 0 orderby depth0
select new { t, depth0 }

もちろん、他のLLVMモジュールにも多くのパスが存在します。
III. バックエンド
他のフェーズと同様に、バックエンドは特定のターゲット向けの出力を生成する役割を担います。Clangでは、バックエンドは高度にモジュール化されています。例として、x86ターゲット向けのコードを生成するモジュールLLVMX86Targetを見てみましょう。
x86ターゲット向けバイナリの生成に関係するすべてのモジュールを示すグラフは次のとおりです。

このフェーズには多くのモジュールが関係しており、それぞれが特定の責務を持っています。これにより、凝集性、クリーンなAPI、関心の分離が促進され、開発者がアーキテクチャ全体の小さな部分に集中できるため、システムを理解しやすくなります。
結論
LLVM/Clangの組み合わせは単なるC/C++コンパイラではありません。ツールを構築するための基盤でもあり、その動作は容易に拡張できます。LLVM/Clangのソースコードには多くのツールが最初から含まれており、他にも多くのツールをウェブ上で見つけられます。
ツールを構築するためにC/C++パーサーが必要な場合、Clangは非常に優れた候補です。
