単相化
おそらくご存じのとおり、Rust には非常に表現力豊かな型システムがあり、ジェネリック型を広範にサポートしています。しかし当然ながら、アセンブリはジェネリックではないため、コードを実行できるようにする前に、すべてのジェネリックの具体的な型を把握する必要があります。
言語によって、この問題の扱い方は異なります。たとえば Java などの一部の言語では、実行時まで値の最も正確な型がわからない場合があります。Java の場合、これは問題ありません。なぜなら、ほぼすべての変数はいずれにせよ参照値(つまり、ヒープに割り当てられたオブジェクトへのポインター)だからです。この柔軟性にはパフォーマンス上のコストが伴います。オブジェクトへのすべてのアクセスでポインターをデリファレンスする必要があるためです。
Rust は異なるアプローチを取ります。すべてのジェネリック型を_単相化_します。これは、必要とされる具体的な型ごとに、コンパイラがジェネリック関数のコードの異なるコピーを生成することを意味します。たとえば、コード内で Vec<u64> と Vec<String> を使う場合、生成されるバイナリには Vec の生成コードが 2 つ含まれます。1 つは Vec<u64> 用、もう 1 つは Vec<String> 用です。その結果、高速なプログラムになりますが、その代償としてコンパイル時間(これらすべてのコピーを作成するのに時間がかかる可能性があります)とバイナリサイズ(これらすべてのコピーが多くの領域を占める可能性があります)が増加します。
単相化は、Rust コンパイラのバックエンドにおける最初のステップです。
収集
まず、プログラム内のすべてのジェネリックなものについて、どの具体的な型が必要かを把握する必要があります。これは_収集_と呼ばれ、これを行うコードは_単相化コレクター_と呼ばれます。
次の例を見てください。
fn banana() {
peach::<u64>();
}
fn main() {
banana();
}
単相化コレクターは、[main, banana, peach::<u64>] というリストを返します。これらは、機械語コードが生成される関数です。コレクターは、statics のようなものもそのリストに追加します。
詳細については、コレクターの rustdocs を参照してください。
単相化コレクターは、MIR lowering と codegen の直前に実行されます。
rustc_codegen_ssa::base::codegen_crate は
collect_and_partition_mono_items クエリを呼び出します。このクエリは単相化の収集を行い、その後それらを codegen
units に分割します。
Codegen Unit (CGU) のパーティショニング
インクリメンタルビルド時間を改善するため、CGU パーティショナーはソースレベルの各モジュールに対して 2 つの CGU を作成します。一方は「stable」、つまり非ジェネリックコード用で、もう一方はより揮発的なコード、つまり単相化/特殊化されたインスタンス用です。
依存関係について、Crate B が Crate A に依存しているような Crate A と Crate B を考えます。 次の表は、Crate B の 1 つ以上のモジュールで使用される可能性がある Crate A の関数について、さまざまなシナリオを示しています。
| Crate A の関数 | 振る舞い |
|---|---|
| 非ジェネリック関数 | Crate A の関数は Crate B のどの codegen unit にも現れません |
非ジェネリックな #[inline] 関数 | Crate A の関数は Crate B の単一の CGU 内に現れ、post-inlining 段階の後でも存在します |
| ジェネリック関数 | インライン化の有無に関係なく、Crate A 由来のすべての単相化(特殊化)された関数は、 Crate B の単一の codegen unit 内に現れます。 その codegen unit は post inlining 段階の後でも存在します。 |
ジェネリックな #[inline] 関数 | - 同じ - |
パーティショナーの詳細については、モジュールレベルの[ドキュメント]を読んでください。