コアパイプライン

GlobalISelのコアパイプラインは次のとおりです

../_images/pipeline-overview.png

図に示されている4つのパスは、次のもので構成されています。

IRTranslator

LLVM-IRgMIR (Generic MIR) に変換します。これは主に直接的な変換であり、ターゲットのカスタマイズはほとんどありません。これは、SelectionDAGBuilderにいくらか似ていますが、特殊な表現ではなくgMIRと呼ばれるMIRのフレーバーを構築します。gMIRはMIRとまったく同じデータ構造を使用しますが、制約が緩くなっています。たとえば、仮想レジスタは特定のレジスタクラスに制約することなく、特定の型に制約することができます。

Legalizer

サポートされていない操作を、サポートされている操作に置き換えます。つまり、バックエンドがサポートできるものに合わせてgMIRを整形します。ターゲットがサポートする必要がある操作のセットは非常に小さいですが、それ以外の場合は、ターゲットはMIRを自由に整形できます。

レジスタバンクセレクター

仮想レジスタをレジスタバンクにバインドします。このパスは、MIRの一部をまとめることで、レジスタバンク間のコピーを最小限に抑えることを目的としています。

命令選択

gMIRを使用してターゲット命令を選択します。この時点で、gMIRはMIRになるのに十分な制約を受けています。

これらは個別のパスとして語られる傾向がありますが、ここにはかなりの柔軟性があり、以下で説明するよりも早く何かが起こっても問題ないことに注意する必要があります。たとえば、リーガライザーが組み込み関数をターゲット命令に直接リーガル化することは珍しくありません。具体的な要件は、以下の追加制約がこれらの各パスの後で保持されることです。

IRTranslator

表現は、このパスの後でgMIR、MIR、またはその2つの混合である必要があります。最初は大部分がgMIRになりますが、後のパスで徐々にgMIRがMIRに移行します。

Legalizer

このパスの後で、不正な操作が残ったり、導入されたりしてはなりません。

レジスタバンクセレクター

このパスの後、すべての仮想レジスタにレジスタバンクが割り当てられている必要があります。

命令選択

このパスの後、gMIRが残ったり、導入されたりしてはなりません。つまり、gMIRからMIRへの変換を完了している必要があります。

これらのパスに加えて、最適化を実行するオプションのパスもいくつかあります。現在のオプションのパスは次のとおりです。

コンバイナー

命令パターンをより良い代替案に置き換えます。通常、これは命令をより高速な代替案に置き換えることでランタイムパフォーマンスを向上させることを意味しますが、コンバイナーはコードサイズやその他のメトリックに焦点を当てることもできます。

これらの追加パスは、より高い最適化レベルやターゲット固有のニーズをサポートするために挿入できます。ありそうなパイプラインは次のとおりです。

../_images/pipeline-overview-with-combiners.png

もちろん、コンバイナーは他の場所にも挿入できます。また、パスは、この(よりカスタマイズされた)パイプライン例に示すように、タスクが完了している限り、完全に置き換えることもできます。

../_images/pipeline-overview-customized.png

MachineVerifier

パスアプローチにより、パイプラインの特定のポイントを超えて必要な不変性を強制するためにMachineVerifierを使用できます。たとえば、legalizedプロパティを持つ関数は、MachineVerifierで不正な命令が発生しないように強制できます。同様に、regBankSelected関数には、レジスタバンクが割り当てられていない仮想レジスタがあってはなりません。

注記

レイヤー化の理由から、MachineVerifierはGlobalISelで唯一の検証ツールになることができません。現在、この問題を解決する方法を見つけるために、一部のパスも検証を実行しています。

主な問題は、GlobalISelが別のライブラリであるため、CodeGenから直接参照できないことです。

テスト

GlobalISelをテストする機能は、SelectionDAGよりも大幅に改善されています。SelectionDAGは一種のブラックボックスであり、内部では多くのことが行われています。これにより、その動作の特定の側面を確実にテストするテストを作成することが困難になっています。比較のために、次の図を参照してください。

../_images/testing-pass-level.png

灰色のボックスはそれぞれ、現在の状態をシリアル化し、パイプラインの2つのポイント間の動作をテストする機会を示しています。現在の状態は、-stop-beforeまたは-stop-afterを使用してシリアル化し、-start-before-start-after、および-run-passを使用してロードできます。

さらに、GlobalISelのパスの多くはすぐにユニットテスト可能であるため、さらに進むこともできます。

../_images/testing-unit-level.png

LegalizerHelperTest.cppのように、仮想ターゲットを作成し、アルゴリズムの単一ステップを実行して結果を確認することができます。MIRおよびFileCheckディレクティブは文字列を使用して埋め込むことができるため、llvm-litで利用可能な利便性にも引き続きアクセスできます。

デバッグ

特に価値があることが証明されている1つのデバッグ手法は、BlockExtractorを使用して基本ブロックを新しい関数に抽出することです。これは、正当性のバグを追跡するために使用でき、パフォーマンスの低下を追跡するためにも使用できます。また、関数属性と組み合わせて、抽出された1つ以上の関数に対してGlobalISelを無効にすることもできます。

../_images/block-extract.png

抽出を実行するコマンドは次のとおりです。

./bin/llvm-extract -o - -S -b ‘foo:bb1;bb4’ <input> > extracted.ll

この特定の例では、fooという名前の関数から2つの基本ブロックを抽出します。次に、新しいLLVM-IRを変更して、bb4を含む抽出された関数にfailedISel属性を追加し、その関数がSelectionDAGを使用するようにすることができます。

GlobalISelは通常一度に1つの関数で動作できるため、これにより一部の最適化を妨げることができます。この手法は、バグに関係するクリティカルブロックを特定するまで、基本ブロックのさまざまな組み合わせに対して繰り返すことができます。

クリティカルブロックが特定されたら、次のようにブロックを分割することで、クリティカル命令への解像度をさらに上げることができます。

bb1:
  ... instructions group 1 ...
  ... instructions group 2 ...

から

bb1:
  ... instructions group 1 ...
  br %bb2

bb2:
  ... instructions group 2 ...

また、この手法を、メイン関数がGlobalISelでコンパイルされ、抽出された基本ブロックがSelectionDAGでコンパイルされる(またはその逆)モードで使用して、他のコードジェネレーターの既存の品質を活用してバグを追跡することもできます。この手法は、パフォーマンスの低下を追跡する際に高速コードと低速コードの類似性を向上させ、低下の特定の原因を絞り込むのにも役立ちます。