MCJIT の設計と実装¶
はじめに¶
このドキュメントでは、MCJIT実行エンジンとRuntimeDyldコンポーネントの内部動作について説明します。これは、コード生成と動的ローディングプロセス全体でのオブジェクトの流れと相互作用を示す、実装の概要として意図されています。
エンジンの作成¶
ほとんどの場合、EngineBuilderオブジェクトを使用してMCJIT実行エンジンのインスタンスを作成します。EngineBuilderは、llvm::Moduleオブジェクトを引数としてコンストラクターに取ります。クライアントは、作成するエンジンタイプとしてMCJITの選択を含め、後でMCJITエンジンに渡されるさまざまなオプションを設定できます。特に重要なのは、EngineBuilder::setMCJITMemoryManager関数です。クライアントがこの時点でメモリマネージャーを明示的に作成しない場合、MCJITエンジンがインスタンス化されるときに、デフォルトのメモリマネージャー(特にSectionMemoryManager)が作成されます。
オプションが設定されると、クライアントはEngineBuilder::createを呼び出してMCJITエンジンのインスタンスを作成します。クライアントがTargetMachineをパラメーターとして取るこの関数の形式を使用しない場合、EngineBuilderの作成に使用されたModuleに関連付けられたターゲットトリプルに基づいて、新しいTargetMachineが作成されます。

EngineBuilder::createは、静的なMCJIT::createJIT関数を呼び出し、モジュール、メモリマネージャー、およびターゲットマシンオブジェクトへのポインターを渡します。これらはすべて、後でMCJITオブジェクトによって所有されます。
MCJITクラスには、RuntimeDyldラッパークラスのインスタンスを含むDyldというメンバー変数があります。このメンバーは、オブジェクトがロードされたときに作成される実際のRuntimeDyldImplオブジェクトとMCJIT間の通信に使用されます。

作成時に、MCJITはEngineBuilderから受け取ったModuleオブジェクトへのポインターを保持しますが、このモジュールのコードをすぐに生成しません。コード生成は、MCJIT::finalizeObjectメソッドが明示的に呼び出されるか、MCJIT::getPointerToFunctionなどのコード生成が必要な関数が呼び出されるまで延期されます。
コード生成¶
上記のようにコード生成がトリガーされると、MCJITは最初に、オブジェクトキャッシュメンバーが設定されている場合は、そこからオブジェクトイメージを取得しようとします。キャッシュされたオブジェクトイメージを取得できない場合、MCJITはemitObjectメソッドを呼び出します。MCJIT::emitObjectは、ローカルのPassManagerインスタンスを使用し、新しいObjectBufferStreamインスタンスを作成します。これらは両方とも、TargetMachine::addPassesToEmitMCに渡してから、作成に使用されたモジュールに対してPassManager::runを呼び出します。

PassManager::runの呼び出しにより、MCコード生成メカニズムは、完全な再配置可能なバイナリオブジェクトイメージ(ターゲットに応じてELFまたはMachO形式)をObjectBufferStreamオブジェクトに出力します。このオブジェクトは、プロセスを完了するためにフラッシュされます。ObjectCacheを使用している場合は、ここでイメージがObjectCacheに渡されます。
この時点で、ObjectBufferStreamには生のオブジェクトイメージが含まれています。コードを実行する前に、このイメージのコードセクションとデータセクションを適切なメモリにロードし、再配置を適用し、メモリのアクセス許可とコードキャッシュの無効化(必要な場合)を完了する必要があります。
オブジェクトのロード¶
コード生成によって、またはObjectCacheから取得したオブジェクトイメージを取得したら、それをRuntimeDyldに渡してロードします。RuntimeDyldラッパークラスは、オブジェクトを調べてファイル形式を判別し、RuntimeDyldELFまたはRuntimeDyldMachO(両方ともRuntimeDyldImpl基本クラスから派生)のインスタンスを作成し、RuntimeDyldImpl::loadObjectメソッドを呼び出して実際のロードを実行します。

RuntimeDyldImpl::loadObjectは、受信したObjectBufferからObjectImageインスタンスを作成することから開始します。ObjectFileクラスをラップするObjectImageは、バイナリオブジェクトイメージを解析し、セクション、シンボル、再配置情報を含む形式固有のヘッダーに含まれる情報へのアクセスを提供するヘルパークラスです。
次に、RuntimeDyldImpl::loadObjectはイメージ内のシンボルを反復処理します。共通シンボルに関する情報は、後で使用するために収集されます。関数またはデータシンボルごとに、関連するセクションがメモリにロードされ、シンボルがシンボルテーブルマップデータ構造に格納されます。反復が完了すると、共通シンボルに対してセクションが出力されます。
次に、RuntimeDyldImpl::loadObjectはオブジェクトイメージ内のセクションを反復処理し、各セクションについてそのセクションの再配置を反復処理します。再配置ごとに、形式固有のprocessRelocationRefメソッドを呼び出します。このメソッドは、再配置を調べて、セクションベースの再配置リストマップと外部シンボル再配置マップの2つのデータ構造のいずれかに格納します。

RuntimeDyldImpl::loadObjectが戻るとき、オブジェクトのすべてのコードセクションとデータセクションは、メモリマネージャーによって割り当てられたメモリにロードされ、再配置情報が準備されていますが、再配置はまだ適用されておらず、生成されたコードはまだ実行の準備ができていません。
[現在(2013年8月現在)、MCJITエンジンはloadObjectが完了するとすぐに再配置を適用します。ただし、これは起こるべきではありません。コードがリモートターゲット用に生成されている可能性があるため、クライアントは再配置が適用される前にセクションアドレスを再マッピングする機会を与えられる必要があります。再配置を複数回適用することは可能ですが、アドレスを再マッピングする必要がある場合、この最初の適用は無駄な努力です。]
アドレスの再マッピング¶
最初のコードが生成された後、finalizeObjectが呼び出されるまでの間、クライアントはオブジェクト内のセクションのアドレスを再マッピングできます。通常、これは、コードが外部プロセス用に生成され、そのプロセスのアドレス空間にマッピングされているためです。クライアントは、MCJIT::mapSectionAddressを呼び出すことでセクションアドレスを再マッピングします。これは、セクションメモリが新しい場所にコピーされる前に行う必要があります。
MCJIT::mapSectionAddressが呼び出されると、MCJITは(Dyldメンバーを介して)RuntimeDyldImplに呼び出しを渡します。RuntimeDyldImplは、新しいアドレスを内部データ構造に格納しますが、他のセクションが変更される可能性があるため、この時点ではコードを更新しません。
クライアントがセクションアドレスの再マッピングを完了すると、MCJIT::finalizeObjectを呼び出して再マッピングプロセスを完了します。
最終準備¶
MCJIT::finalizeObjectが呼び出されると、MCJITはRuntimeDyld::resolveRelocationsを呼び出します。この関数は、外部シンボルを特定し、オブジェクトのすべての再配置を適用しようとします。
外部シンボルは、メモリマネージャーのgetPointerToNamedFunctionメソッドを呼び出すことで解決されます。メモリマネージャーは、ターゲットアドレス空間内の要求されたシンボルのアドレスを返します。(これはホストプロセス内の有効なポインターではない場合があります。)次に、RuntimeDyldはこのシンボルに関連付けられている保存された再配置のリストを反復処理し、形式固有の実装を介して、ロードされたセクションメモリに再配置を適用するresolveRelocationメソッドを呼び出します。
次に、RuntimeDyld::resolveRelocationsはセクションのリストを反復処理し、各セクションについてそのシンボルを参照して保存された再配置のリストを反復処理し、このリスト内の各エントリに対してresolveRelocationを呼び出します。ここでの再配置リストは、再配置に関連付けられているシンボルがリストに関連付けられているセクションにある再配置のリストです。これらの各ロケーションには、異なるセクションにある可能性が高い再配置が適用されるターゲットロケーションがあります。

上記のように再配置が適用されると、MCJITはRuntimeDyld::getEHFrameSectionを呼び出し、ゼロ以外の結果が返された場合は、セクションデータをメモリマネージャーのregisterEHFramesメソッドに渡します。これにより、メモリマネージャーは、デバッガーにEHフレーム情報を登録するなど、目的のターゲット固有の関数を呼び出すことができます。
最後に、MCJITはメモリマネージャーのfinalizeMemoryメソッドを呼び出します。このメソッドでは、メモリマネージャーは必要に応じてターゲットコードキャッシュを無効にし、コードおよびデータメモリ用に割り当てたメモリページに最終的なアクセス許可を適用します。