コンパイラソースの高レベルな概要
コンパイラが何をするのかを見たので、
rustc のソースコードが置かれている rust-lang/rust リポジトリの構造を見てみましょう。
この章の前に、コンパイラがどのように動作するかを紹介している 「コンパイラの概要」の章を読むと役に立つかもしれません。
ワークスペースの構造
rust-lang/rust リポジトリは、単一の大きな Cargo ワークスペースで構成されており、
コンパイラ、標準ライブラリ(core、alloc、std、
proc_macro、etc)、rustdoc に加えて、ビルドシステムや、
完全な Rust ディストリビューションをビルドするための多数のツールとサブモジュールを含んでいます。
リポジトリは 3 つの主要なディレクトリで構成されています。
-
compiler/にはrustcのソースコードが含まれています。これは多数のクレートで構成されており、 それらが合わさってコンパイラを形成しています。 -
library/には標準ライブラリ(core、alloc、std、proc_macro、test)と、Rust ランタイム(backtrace、rtstartup、lang_start)が含まれています。 -
tests/にはコンパイラテストが含まれています。 -
src/にはrustdoc、clippy、cargo、ビルドシステム、 言語ドキュメントなどのソースコードが含まれています。
コンパイラ
コンパイラはさまざまな compiler/ クレートで実装されています。
compiler/ クレートはすべて rustc_* で始まる名前を持ちます。これらは、
小さなものから巨大なものまで、およそ 50 個の相互依存するクレートの集合です。
また、実際のバイナリ(つまり main 関数)である rustc クレートもあります。
これは実際には、他のクレート内のコンパイルのさまざまな部分を駆動する
rustc_driver クレートを呼び出す以外には何もしません。
これらのクレートの依存関係の順序は複雑ですが、おおよそ次のようなものです。
rustc(バイナリ)がrustc_driver::mainを呼び出します。rustc_driverは多くの他のクレートに依存していますが、主なものはrustc_interfaceです。rustc_interfaceは、他のほとんどのコンパイラクレートに依存しています。これは、 コンパイル全体を駆動するためのかなり汎用的なインターフェイスです。- 他のほとんどの
rustc_*クレートはrustc_middleに依存しており、これは コンパイラ内の多くの中核的なデータ構造を定義しています。 rustc_middleと他のほとんどのクレートは、コンパイラの初期部分(たとえばパーサー)、 基本的なデータ構造(たとえばSpan)、またはエラー報告を表すいくつかのクレートに依存しています。rustc_data_structures、rustc_span、rustc_errorsなどです。
他の Rust パッケージの場合と同じように、cargo tree を実行することで
正確な依存関係を確認できます。
cargo tree --package rustc_driver
最後にもう 1 つ、src/llvm-project は、私たちの LLVM フォーク用のサブモジュールです。
ブートストラップ中に LLVM がビルドされ、compiler/rustc_llvm クレートには
LLVM(C++ で書かれています)を包む Rust ラッパーが含まれているため、
コンパイラは LLVM とインターフェイスできます。
この本の大部分はコンパイラについて扱っているため、ここではこれらのクレートについて これ以上説明しません。
全体像
コンパイラの依存関係構造は、主に 2 つの要因の影響を受けています。
- 構成。コンパイラは 巨大な コードベースです。1 つのクレートとしては、 ありえないほど大きくなってしまうでしょう。依存関係構造は、ある程度、 コンパイラのコード構造を反映しています。
- コンパイル時間。コンパイラを複数のクレートに分割することで、Cargo を使った インクリメンタル/並列コンパイルをより有効に活用できます。特に、1 つを変更したときに 再ビルドしなければならないクレートの数を増やさないよう、クレート間の依存関係を できるだけ少なくするよう努めています。
依存関係ツリーの一番下には、コンパイラ全体で使われるいくつかのクレート
(たとえば rustc_span)があります。コンパイルプロセスの非常に初期の部分
(たとえばパースと抽象構文木(AST))は、これらのみに依存しています。
AST が構築され、他の初期解析が完了すると、コンパイラの
クエリシステム がセットアップされます。クエリシステムは、関数ポインタを使った
巧妙な方法でセットアップされます。これにより、クレート間の依存関係を切り離し、
より多くの並列コンパイルを可能にしています。クエリシステムは rustc_middle で定義されているため、
コンパイラの後続部分のほぼすべてがこのクレートに依存しています。これは本当に大きなクレートであり、
コンパイル時間が長くなる原因になっています。さまざまな成果の差はありますが、
そこからものを移動する取り組みも行われてきました。もう 1 つの副作用として、
関連する機能が異なるクレートに分散してしまうことがあります。たとえば、lint 機能は、
クレートの初期部分、rustc_lint、rustc_middle、その他の場所にまたがって存在します。
理想的には、より少数でより凝集度の高いクレートがあり、インクリメンタルコンパイルと
並列コンパイルによってコンパイル時間が妥当な範囲に保たれるべきです。しかし、
インクリメンタルコンパイルと並列コンパイルはまだそのために十分な水準には達していないため、
現時点では、物事を別々のクレートに分割することが私たちの解決策となっています。
依存関係ツリーの最上位には rustc_driver と rustc_interface があり、
rustc_interface はコンパイルのさまざまな段階を進行させるのに役立つ、クエリシステムを包む不安定なラッパーです。
コンパイラの他の利用者は、このインターフェイスを別の方法で使用する場合があります(例: rustdoc、あるいは最終的には rust-analyzer など)。
rustc_driver クレートは、まずコマンドライン引数を解析し、その後
rustc_interface を使用してコンパイルを完了まで進めます。
rustdoc
rustdoc の大部分は librustdoc にあります。ただし、rustdoc バイナリ
自体は src/tools/rustdoc であり、rustdoc::main を呼び出す以外のことは何もしません。
ドキュメント用の JavaScript と CSS も src/tools/rustdoc-js
および src/tools/rustdoc-themes にあります。--output-format=json
の型定義は、src/rustdoc-json-types にある別のクレートにあります。
rustdoc について詳しくは、この章を読むことができます。
テスト
上記すべてのテストスイートは tests/ にあります。テストスイートについて詳しくは
この章を読むことができます。
テストハーネスは src/tools/compiletest/ にあります。
ビルドシステム
リポジトリには、コンパイラ、標準ライブラリ、rustdoc などのビルドや、
テスト、完全な Rust ディストリビューションのビルドなどのためだけに用意された多数のツールがあります。
主要なツールの 1 つが src/bootstrap/ です。ブートストラップについて詳しくは
この章を読むことができます。このプロセスでは、tidy/ や compiletest/
など、src/tools/ の他のツールも使用する場合があります。
標準ライブラリ
このコードは、他のほとんどの Rust クレートとかなり似ていますが、不安定な(nightly)
機能を使用できるため、特別な方法でビルドする必要があります。
標準ライブラリは、libstd or the "standard facade" と呼ばれることもあります。
その他
rust-lang/rust リポジトリには、完全な Rust ディストリビューションのビルドに関連する
他のものが多数あります。ほとんどの場合、それらについて心配する必要はありません。
これらには以下が含まれます。
src/ci: CI 構成。多くのプラットフォームで多数のテストを実行するため、これは実際にはかなり大規模です。- ほかにもあります…