LLVM リンクタイムの最適化: 設計と実装¶
説明¶
LLVM には、リンク時に使用できる強力なインターモジュール最適化機能があります。リンクタイムの最適化 (LTO) は、リンク段階中に実行されるインターモジュール最適化の別称です。このドキュメントでは、LTO オプティマイザとリンカー間のインターフェイスと設計について説明します。
設計の哲学¶
LLVM リンクタイムオプティマイザは、コンパイラツールチェーンでインターモジュール最適化を実行しながら完全な透過性を提供します。その主な目的は、開発者が開発者の makefile やビルドシステムを大きく変更することなく、インターモジュール最適化を利用できるようにすることです。これはリンカーとの緊密な統合によって実現されます。このモデルでは、リンカーは LLVM ビットコードファイルをネイティブオブジェクトファイルのように扱い、それらの間で混合および照合を許可します。リンカーは、共有オブジェクトであるlibLTOを使用して、LLVM ビットコードファイルを処理します。リンカーと LLVM オプティマイザ間のこの緊密な統合により、他のモデルではできない最適化を実行できます。リンカーの入力により、オプティマイザは保守的なエスケープ解析に依存する必要がなくなります。
リンクタイムの最適化の例¶
次の例は、LTO の統合されたアプローチとクリーンなインターフェイスの利点を示しています。この例では、このドキュメントで説明されているインターフェイスを通じて LTO をサポートするシステムリンカーが必要です。ここでは、clang はシステムリンカーを透過的に呼び出します。
入力ソースファイル
a.c
が LLVM ビットコード形式にコンパイルされます。入力ソースファイル
main.c
はネイティブオブジェクトコードにコンパイルされます。
--- a.h ---
extern int foo1(void);
extern void foo2(void);
extern void foo4(void);
--- a.c ---
#include "a.h"
static signed int i = 0;
void foo2(void) {
i = -1;
}
static int foo3() {
foo4();
return 10;
}
int foo1(void) {
int data = 0;
if (i < 0)
data = foo3();
data = data + 42;
return data;
}
--- main.c ---
#include <stdio.h>
#include "a.h"
void foo4(void) {
printf("Hi\n");
}
int main() {
return foo1();
}
コンパイルするには、次のように実行します。
% clang -flto -c a.c -o a.o # <-- a.o is LLVM bitcode file
% clang -c main.c -o main.o # <-- main.o is native object file
% clang -flto a.o main.o -o main # <-- standard link command with -flto
この例で、リンカは
foo2()
がLLVMビットコードファイルで定義された外部から見えるシンボルであることを認識します。リンカは通常のシンボル解決パスを完了し、foo2()
がどこにも使用されていないことを確認します。この情報はLLVMのオプティマイザによって使用され、foo2()
を削除します。foo2()
が削除されるとすぐに、オプティマイザは条件i < 0
が常に偽であることを認識します。つまりfoo3()
は使用されません。したがって、オプティマイザはfoo3()
も削除します。今度は、これによってリンカが
foo4()
を削除できるようになります。
この例は、リンカと緊密に統合することの利点を示しています。ここでは、オプティマイザはリンカの入力なしでfoo3()
を削除できません。
代替アプローチ¶
- コンパイラドライバはリンクタイムオプティマイザを別個に呼び出します。
このモデルでは、リンクタイムオプティマイザはリンカの通常のシンボル解決フェーズ中に収集された情報の利点を得ることができません。上記の例では、
foo2()
は外部から見えるため、オプティマイザはリンカの入力なしでfoo2()
を削除できません。その結果、オプティマイザはfoo3()
を削除できなくなります。- すべてのオブジェクトファイルからシンボル情報を収集するために別のツールを使用します。
このモデルでは、新しい別のツールまたはライブラリは、リンクタイム最適化のために情報を収集するリンカの機能を複製します。このコードの重複が正当化されることはなく、他にもいくつかの欠点があります。たとえば、リンキングセマンティクスと、さまざまなプラットフォームでリンカによって提供される機能は、一意ではありません。つまり、この新しいツールはそれらすべての機能とプラットフォームを1つのスパーツールでサポートする必要があり、プラットフォームごとに別のツールが必要になります。これにより、リンクタイムオプティマイザのメンテナンスコストが大幅に増加しますが、これは必要ありません。このアプローチでは、さまざまなプラットフォームでのリンカの開発と同期する必要もありますが、それはリンクタイムオプティマイザの主な目的ではありません。最後に、このアプローチでは、この別のツールとリンカ自体によって行われる作業の重複により、エンドユーザーのビルド時間が増加します。
libLTO
とリンカ間の複数フェーズ通信¶
libLTO
リンカはシンボル定義と、他のツールが一般的なビルドサイクル中に収集した情報よりも正確な、さまざまなリンクオブジェクトでの使用に関する情報を収集します。リンカはこの情報をネイティブの.oファイル内のシンボルの定義と使用を確認し、シンボルの可視性情報を使用することで収集します。リンカは、エクスポートされたシンボルのリストなど、ユーザーが提供した情報も使用します。LLVMのオプティマイザは、制御フロー情報、データフロー情報を収集し、オプティマイザの観点からプログラム構造についてさらに多くの情報を認識しています。私たちの目標は、さまざまなリンキングフェーズ中にこの情報を共有することで、リンカとオプティマイザ間の密接な統合を活用することです。
第 1 段 : LLVM ビットコードファイルの読み取り¶
最初にリンカーは、すべてのオブジェクトファイルを自然な順序で読み取り、シンボル情報を収集します。これには、ネイティブのオブジェクトファイルと LLVM ビットコードファイルの両方が含まれています。すべての .o ファイルがネイティブのオブジェクトファイルである場合のリンカーのコストを最小限に抑えるために、リンカーは提供されたオブジェクトファイルがネイティブのオブジェクトファイルではないことが検出されたときにのみ、lto_module_create()
を呼び出します。lto_module_create()
が、ファイルが LLVM ビットコードファイルであると返した場合、リンカーはlto_module_get_symbol_name()
およびlto_module_get_symbol_attribute()
を使用してそのモジュールを反復処理し、定義または参照されているすべてのシンボルを取得します。この情報は、リンカーのグローバルシンボルテーブルに追加されます。
lto* 関数はすべて、共有オブジェクト libLTO で実装されています。これにより、LLVM LTO コードはリンカートールから独立してアップデートされるようになります。サポートされているプラットフォームでは、共有オブジェクトは遅延ロードされます。
第 2 段 : シンボル解決¶
この段階で、リンカーはグローバルシンボルテーブルを使用してシンボルを解決します。未定義のシンボルエラーを報告したり、アーカイブメンバーを読み取ったり、弱いシンボルを置換することなどができます。リンカーは、入力 LLVM ビットコードファイルの正確な内容は不明でも、効率的に実行できます。デッドコードの除去が有効な場合、リンカーは有効なシンボルのリストを収集します。
第 3 段 : ビットコードファイルの最適化¶
シンボル解決後、リンカーは LTO 共有オブジェクトに対して、ネイティブのオブジェクトファイルでどのシンボルが必要かを伝えます。上記の例では、リンカーはlto_codegen_add_must_preserve_symbol()
を使用して、ネイティブのオブジェクトファイルによってfoo1()
のみが使用されると報告します。次に、リンカーはlto_codegen_compile()
を使用して LLVM オプティマイザーおよびコードジェネレーターを呼び出します。この関数は、LLVM ビットコードファイルをマージし、さまざまな最適化パスを適用することによって作成されたネイティブのオブジェクトファイルを返します。
第 4 段 : 最適化後のシンボル解決¶
この段階で、リンカーは最適化されたネイティブのオブジェクトファイルを読み取り、すべての変更を反映するために内部グローバルシンボルテーブルをアップデートします。また、リンカーは LLVM ビットコードファイルによる外部シンボルの使用に関するすべての変更についての情報を収集します。上記の例では、リンカーはfoo4()
がもう使用されなくなったことを書き留めます。デッドコードの除去が有効な場合、リンカーは有効なシンボル情報を適切に更新し、デッドコードの除去を実行します。
この段階の後、リンカーは LLVM ビットコードファイルを見たことがなかったかのように、リンクを続行します。
libLTO
¶
libLTO
は、LLVM ツールの共有オブジェクトであり、リンカーで使用するように設計されています。libLTO
は、LLVM の内部構造を公開することなく、LLVM のインタープロシージャルオプティマイザーを使用するための抽象 C インターフェイスを提供します。この目的は、LLVM オプティマイザーが進化し続けても、インターフェイスを可能な限り安定した状態に保つことです。完全に異なるコンパイルテクノロジーでも、標準のリンカートールやオブジェクトファイルで動作する別の libLTO を提供できるはずです。
lto_module_t
¶
ネイティブ以外のオブジェクトファイルは、lto_module_t
を介して処理されます。次の関数は、リンカーがファイル (ディスク上またはメモリバッファー内) がlibLTOで処理できるファイルであるかどうかを確認することを可能にします
lto_module_is_object_file(const char*)
lto_module_is_object_file_for_target(const char*, const char*)
lto_module_is_object_file_in_memory(const void*, size_t)
lto_module_is_object_file_in_memory_for_target(const void*, size_t, const char*)
オブジェクトファイルをlibLTO
で処理できる場合は、リンカーは次のいずれかを使用してlto_module_t
を作成します
lto_module_create(const char*)
lto_module_create_from_memory(const void*, size_t)
完了したら、ハンドルは次の方法で解放します
lto_module_dispose(lto_module_t)
リンカーは、シンボルの数を取得し、次の方法で各シンボルの名前と属性を取得することによって、ネイティブ以外のオブジェクトファイルを内省できます
lto_module_get_num_symbols(lto_module_t)
lto_module_get_symbol_name(lto_module_t, unsigned int)
lto_module_get_symbol_attribute(lto_module_t, unsigned int)
シンボルの属性には、アライメント、可視性、種類が含まれます
Darwin上でオブジェクトファイルを使用するツール (例: lipo) は、CPUのタイプなどのプロパティを知る必要がある場合があります
lto_module_get_macho_cputype(lto_module_t mod, unsigned int *out_cputype, unsigned int *out_cpusubtype)
lto_code_gen_t
¶
リンカーが各ネイティブ以外のオブジェクトファイルをlto_module_t
にロードしたら、それらをすべて処理してネイティブオブジェクトファイルを生成するようにlibLTO
に要求できます。これは2段階で行われます。最初の段階として、コードジェネレーターは次の方法で作成されます
lto_codegen_create()
その後、各ネイティブ以外のオブジェクトファイルは、次の方法でコードジェネレーターに追加されます
lto_codegen_add_module(lto_code_gen_t, lto_module_t)
リンカーには、いくつかのコード生成オプションを設定するオプションがあります。DWARFデバッグ情報を作成するかどうかは、次の方法で設定します
lto_codegen_set_debug_model(lto_code_gen_t)
どの種類のポジションインディペンデントを設定するかは、次の方法で設定します
lto_codegen_set_pic_model(lto_code_gen_t)
ネイティブオブジェクトファイルによって参照されるか、それ以外の方法で最適化されないようにする必要があるシンボルは、次の方法で設定します
lto_codegen_add_must_preserve_symbol(lto_code_gen_t, const char*)
これらの設定がすべて完了したら、リンカーは、次の方法を使用して設定からネイティブオブジェクトファイルを作成するように要求します
lto_codegen_compile(lto_code_gen_t, size*)
生成されたネイティブオブジェクトファイルを含むバッファへのポインターを返します。その後、リンカーはそれを解析し、残りのネイティブオブジェクトファイルとリンクします