LLVMにおけるDXILサポートのアーキテクチャと設計

序章

LLVMは、DirectX中間言語、またはDXILの読み書きをサポートします。DXILは、本質的にLLVM 3.7時代のビットコードにいくつかの制約とさまざまなセマンティックに重要な演算とメタデータを付加したものです。

LLVMのDXILサポート実装の哲学は、DXILを単なる表現フォーマットとして扱うことです。可能な限りDXILを読み取る場合、すべてを汎用LLVM構成に翻訳する必要があります。同様に、フォーマットへの変換処理の最終段階でDXIL固有の構成を導入する必要があります。

LLVMでは、3か所にDXIL関連のコードがあります。DXILを記述するDirectXバックエンド、読み取るDXILUpgradeパス、および読み書きで共有されるライブラリココードです。これらを順番に説明します。

共通の読み書きコード

コードの重複を避けるために、DXILの読み書きで共有する必要があるロジックが数多くあります。そのようなコードを格納する場所について厳密なルールはありませんが、通常、3つの適切な場所があります。DXILのABIに適合するように固定された列挙型の単純な定義と値はSupport/DXILABI.hに見つけることができ、DXILと最新のLLVM構成との間の双方向の変換機能はlib/Transforms/Utilsにあり、情報の派生または保持に必要なさらに多くの分析は、一般的なlib/Analysisパスとして実装されます。

DXILアップグレードパス

DXILをLLVM IRに変換する際には、DXILがLLVM 3.7ビットコードと互換性があり、最新のLLVMが古いビットコードを最新のIRに「アップグレード」できるという事実を利用しています。ただし、単にビットコードアップグレードプロセスに依存するだけでは不十分です。それにより、多くのDXIL固有の構成が残ってしまうからです。そこで、DXIL演算をLLVM演算に変換し、メタデータ表現の相違を滑らかにするDXILUpgradeパスがあります。このパスを「アップグレード」と呼ぶのは、LLVMの標準ビットコードアップグレードプロセスに続き、DXIL構成の処理を完了するからです。一方、「リーダー」または「リフティング」という名前も適切ですが、少しわかりにくい可能性があります。

パス「DXILUpgrade」自体は非常に軽量です。主に上記の「共通コード」に記載されているユーティリティを利用し、DirectXバックエンドとClangのHLSLサポートのコード生成との間でロジックを可能な限り共有するためです。

DirectX固有拡張パス

DXIL Opsに直接マップされない固有拡張があります。場合によっては、固有拡張をLLVM IR命令セットに展開する必要があります。それ以外の場合は、DXIL Opsの戻り値や引数に修正を加える必要があります。パス「DXILIntrinsicExpansion」は、固有拡張が1対1のマップに対応していないすべての事例に対処します。このパスは、展開がDXILに固有の場合、CodeGenから実装の詳細を排除するために使用することもできます。最後になりますが、このパスではベクタタイプを維持することが想定されています。そのため、このパスではスカラー化を避けることがベストプラクティスです。

DirectXバックエンド

DirectXバックエンドはLLVM IRをDXILにローワーします。特定のISAではなく中間フォーマットに変換するので、このバックエンドは他のバックエンドに慣れ親しんでいる可能性がある命令選択パターンに従いません。DXILのローワーには2つの部分があります。さまざまな構造を変換してDXILがそれらの構造を表す方法と一致する形式にするパスのセットで、制限付きのビットコード「ダウングレーダーパス」が続きます。

DXILを放出する前に、DirectXバックエンドは外部の演算、タイプ、メタデータをDXILが想定する方法で表現するようにLLVM IRを変更する必要があります。たとえば、DXILOpLoweringは固有拡張をdx.op呼び出しに変換します。これらのパスは本質的にパス「DXILUpgrade」の逆です。このダウングレードプロセスは、可能な限りIRからIRへのパスとして行うのが最適です。つまり、外部ツールを使用することなくoptFileCheckで簡単にテストできるためです。

DXIL放出の第2の部分は、多少LLVMビットコードダウングレーダーです。LLVM 3.7表現と一致するビットコードを放出する必要があります。そのため、LLVMのBitcodeWriterの代替バージョンであるDXILWriterがあります。現在、これによりLLVMの最新のビットコードライブラリを活用して多くの作業を行うことができますが、将来の時点で最新のLLVMビットコードが進化することに伴って、完全に分離される可能性があります。

DirectXバックエンドフロー

DXILのコード生成フローは、一連のパスに分割されます。パスは2つのフローにグループ化されています。

  1. DXIL IRの生成。

  2. DXILバイナリの生成。

DXIL IRを生成するパスは次のフローに従います。

DXILOpLowering -> DXILPrepare -> DXILTranslateMetadata

これらの各パスには、定義された責任があります。

  1. DXILOpLoweringは、LLVMの固有拡張呼び出しをdx.op呼び出しに変換します。

  2. DXILPrepareは、LLVM 3.7と互換性を持たせるためにDXIL IRを変換し、型付きポインタを挿入できるようにビットキャストを挿入します。

  3. DXILTranslateMetadataは、DXILメタデータ構造を放出します。

DXコンテナ内のDXILをバイナリにエンコードするパスは次のフローに従います。

DXILEmbedder -> DXContainerGlobals -> AsmPrinter

これらの各パスには、次のように定義された責任があります。

  1. DXILEmbedderは、DXILビットコードライターを実行してビットコードストリームを生成し、バイナリデータを元のモジュールのグローバル内に埋め込みます。

  2. DXContainerGlobals は、計算された分析パスに基づいて他の DX コンテナーのパーツ用のバイナリデータグローバルを生成します。

  3. AsmPrinter は、オブジェクトファイルをエミットするための標準的な LLVM インフラストラクチャーです。

DXIL を DX コンテナーファイルにエミットする場合、MC レイヤーは Clang の -fembed-bitcode オプションの動作と同様の方法で使用されます。DX コンテナーオブジェクトライターは、コンテナーのヘッダーと構造フィールドを構築する方法を知っており、モジュールからグローバル変数を読み出して残りのパートデータを埋め込みます。

DirectX コンテナー

DirectX コンテナーフォーマットは、LLVM 内ではオブジェクトファイルフォーマットとして扱われます。読み取りは BinaryFormat ライブラリと Object ライブラリの間で実装され、書き込みは MC レイヤーで実装されます。追加のテストおよび検査サポートは、ObjectYAML ライブラリとツールで実装されます。

テスト

多くの DXIL テストは、サポートの多くが前のセクションで説明したように IR レベルのパスに関して実装されているため、optFileCheck を使用する通常の IR から IR へのテストを使って行うことができます。この例は llvm/test/CodeGen/DirectXllvm/test/Transforms/DXILUpgrade で確認できます。このタイプのテストはできるだけ活用する必要があります。

ただし、DXIL 形式自体をテストする場合、ラウンドトリップテストには IR パスは不十分です。現時点では、利用可能な最善のオプションは、DXC プロジェクトのツールを使用してラウンドトリップすることです。これらのテストは現在 test/tools/dxil-dis にあり、LLVM_INCLUDE_DXIL_TESTS cmake オプションが設定されている場合にのみ利用できます。DXIL 読み取りパス用の同等のテストセットアップは現在ないことに注意してください。

可能になったら、自己整合性を確保し、dxil-dis が利用できない場合にテストカバレッジを取得するために、DXIL の書き込みと読み取りパスを使用してラウンドトリップを行うことも検討します。