Legalizer

このパスは、ジェネリックな機械命令を合法的なものに変換します。

合法的な命令は、以下のように定義されます。

  • 選択可能 — ターゲットは後でそれをターゲット固有(ジェネリックではない)命令に選択できるようになります。これは必ずしもInstructionSelectがそれを処理する必要があるという意味ではありません。単に何かがそれを処理する必要があるという意味です。

  • ロードおよびストア可能なvregsで動作する — 必要に応じて、ターゲットは各gvregオペランドのG_LOAD/G_STOREを選択できます。

SelectionDAGとは異なり、合法化フェーズはありません。「型」と「演算」の合法化は別々ではありません。

合法化は反復的であり、すべての状態はGMIRに含まれています。中間コードの有効性を維持するために、命令が導入されます。

  • G_MERGE_VALUES — 同じサイズの複数のレジスタを1つの幅の広いレジスタに連結します。

  • G_UNMERGE_VALUES — 1つの幅の広いレジスタから同じサイズの複数のレジスタを抽出します。

  • G_EXTRACT — 1つの幅の広いレジスタから単純なレジスタ(連続したビットのシーケンスとして)を抽出します。

これらは合法化プロセスのテンポラリな副産物であると予想されるため、Legalizerパスの最後に結合されます。残っている場合、必要に応じてロードとストアを使用して常に選択可能であると予想されます。

命令の合法性は、命令自体にのみ依存し、命令が使用されるコンテキストには依存してはなりません。ただし、命令が合法ではないと判断した後、命令のコンテキストを使用して命令を合法化する方法を決定することは許可されます。例として、G_FOO命令があるとします。

%1:_(s32) = G_CONSTANT i32 1
%2:_(s32) = G_FOO %0:_(s32), %1:_(s32)

%1が値1G_CONSTANTの場合に限りG_FOOが合法であると言うことは不可能です。ただし、次のようになります。

%2:_(s32) = G_FOO %0:_(s32), i32 1

オペランド2が値1のイミディエイトの場合に限り合法であると言うことができます。これは、その情報が単一の命令に完全に含まれているためです。

API: LegalizerInfo

推奨される[1] APIは次のようになります。

getActionDefinitionsBuilder({G_ADD, G_SUB, G_MUL, G_AND, G_OR, G_XOR, G_SHL})
    .legalFor({s32, s64, v2s32, v4s32, v2s64})
    .clampScalar(0, s32, s64)
    .widenScalarToNextPow2(0)
    .clampNumElements(0, v2s32, v4s32)
    .clampNumElements(0, v2s64, v2s64)
    .moreElementsToNextPow2(0);

そして、命令を合法と宣言するか、それをより合法的にするためのアクションを決定するかによって、一連のルールを記述します。

このルールセットの中核はLegalityQueryであり、命令を記述します。命令を作成することなく他のパスが合法性を決定できるようにし、また述語で使用できる情報を安全に依存できる情報に制限するために、命令ではなく記述を使用します。現在、合法性を決定する述語で使用できる情報には、以下が含まれています。

  • 命令のオペコード

  • 各型インデックスの型(type0type1など参照)

  • 各MachineMemOperandのバインドサイズ(バイト単位)とアトミック順序

注記

調査する価値のある代替案として、アクションを明示的な列挙トークン(LegalWidenScalar、…)ではなく、アクションを実装するstd::functionを使用して表すようにAPIを一般化することがあります。これにはいくつかの利点があり、最も注目すべきはCustomを削除できることです。

脚注

ルールの処理と宣言

getActionDefinitionsBuilder関数は、ルールを追加できるルールセットを生成します。複数のオペコードが指定されている場合、それらはすべて同じルールセットに永久的にバインドされます。ルールセット内のルールは上から下に実行され、命令がルールの結果として合法化されると、上から再び開始します。ルールセットが使い果たされてもルールを満たさない場合は、サポートされていないと見なされます。

命令を合法と宣言しない場合、ルールを繰り返し処理するたびに、ある型が別の型に変更されるように要求される場合があります。場合によっては、これにより複数の型が変更される可能性がありますが、複数の変更を行うと、たとえば、ある型を狭くすると別の型が小さくなりすぎ、その型を広げると最初の型が大きくなりすぎる無限ループを回避するのが困難になるため、できる限りこれを回避します。

一般的に、ルールの上部に近いほど命令を合法と宣言し、高価なルールをできるだけ低く配置することをお勧めします。これにより、合法性のテストは合法化よりも頻繁に行われ、合法化にはルールの複数回のパスの必要がありうるため、パフォーマンスが向上します。

具体的な例として、次のルールを考えてみましょう。

getActionDefinitionsBuilder({G_ADD, G_SUB, G_MUL, G_AND, G_OR, G_XOR, G_SHL})
    .legalFor({s32, s64, v2s32, v4s32, v2s64})
    .clampScalar(0, s32, s64)
    .widenScalarToNextPow2(0);

そして、命令

%2:_(s7) = G_ADD %0:_(s7), %1:_(s7)

s7はリストされた型のいずれでもないため、.legalFor()の述語を満たしません。そのため、.clampScalar()にフォールスルーします。このルールの述語を満たすため、型はs32よりも小さく、このルールは合法化に型0をs32に変更するように指示します。その後、上から再開します。今回は.legalFor()を満たし、結果の出力は次のようになります。

%3:_(s32) = G_ANYEXT %0:_(s7)
%4:_(s32) = G_ANYEXT %1:_(s7)
%5:_(s32) = G_ADD %3:_(s32), %4:_(s32)
%2:_(s7) = G_TRUNC %5:_(s32)

G_ADDは合法であり、他の命令は合法化によって処理されるようにスケジュールされています。

ルールの動作

ルールセットにルールを追加するさまざまなルールのファクトリがありますが、いくつかの共通のアクションがあります。

  • legalIf()legalFor()などは、述語が満たされている場合、命令を合法と宣言します。

  • narrowScalarIf()narrowScalarFor()などは、述語が満たされていて、型の1つにあるスカラーを特定の型に狭めると、より合法になることを示している場合、命令を非合法と宣言します。このアクションは、スカラーとベクターの両方をサポートします。

  • widenScalarIf()widenScalarFor()などは、述語が満たされていて、型の1つにあるスカラーを特定の型に広げると、より合法になることを示している場合、命令を非合法と宣言します。このアクションは、スカラーとベクターの両方をサポートします。

  • fewerElementsIf()fewerElementsFor()などは、述語が満たされていて、型の1つにあるベクター要素の数を特定の型に減らすと、より合法になることを示している場合、命令を非合法と宣言します。このアクションはベクターをサポートします。

  • moreElementsIf()moreElementsFor()などは、述語が満たされていて、型の1つにあるベクター要素の数を特定の型に増やすと、より合法になることを示している場合、命令を非合法と宣言します。このアクションはベクターをサポートします。

  • lowerIf()lowerFor()などは、述語が満たされていて、同等の命令に置き換えると、より合法になることを示している場合、命令を非合法と宣言します。このアクションのサポートは、オペコードごとに異なります。これらは、別の型で展開を試みる型を含むオプションのLegalizeMutationを提供する場合があります。

  • libcallIf()libcallFor()などは、述語が満たされていて、libcallに置き換えるとより合法になることを示している場合、命令を非合法と宣言します。このアクションのサポートは、オペコードごとに異なります。

  • customIf()customFor()などは、述語が満たされていて、バックエンド開発者がそれをより合法的にするための手段を提供することを示している場合、命令を非合法と宣言します。

  • unsupportedIf()unsupportedFor()などは、述語が真である場合に命令を不正と宣言し、それを合法にする方法がなく、コンパイラが失敗する必要があることを示します。

  • fallback()は古いAPIにフォールバックし、既存のコードをそのAPIから移植している間のみ使用してください。

ルール述語

ルールファクトリは共通の述語も持っています。

  • legal()lower()などは常に満たされます。

  • legalIf()narrowScalarIf()などは、ユーザーが提供したLegalityPredicate関数がtrueを返す場合に満たされます。この述語は、LegalityQueryの情報にアクセスして決定を行います。ユーザーが提供した述語は、all(P0, P1, ...)を使用して組み合わせることもできます。

  • legalFor()narrowScalarFor()などは、型が与えられた型の集合のいずれかに一致する場合に満たされます。例えば、.legalFor({s16, s32})は、型0がs16またはs32のいずれかの場合に命令を合法と宣言します。2つまたは3つの型インデックスに対する追加のバージョンが一般的に利用可能です。これらについては、考慮されるすべての型インデックスをまとめて、タプルのいずれかのすべての型と一致させる必要があります。.legalFor({{s16, s32}, {s32, s64}}){s16, s32}または{s32, s64}のみを受け入れますが、{s16, s64}は受け入れません。

  • legalForTypesWithMemSize()narrowScalarForTypesWithMemSize()などは、legalFor()narrowScalarFor()などと同じですが、さらにMachineMemOperandが各タプルで指定されたサイズを持つことを要求します。

  • legalForCartesianProduct()narrowScalarForCartesianProduct()などは、各型インデックスが独立した集合の各要素の1つと一致する場合に満たされます。そのため、.legalForCartesianProduct({s16, s32}, {s32, s64}){s16, s32}{s16, s64}{s32, s32}、および{s32, s64}を受け入れます。

複合ルール

上記機能から構築された一般的な状況に対する複合ルールがいくつかあります。

  • widenScalarToNextPow2()widenScalarIf()に似ていますが、型のサイズ(ビット単位)が2の累乗でない場合にのみ満たされ、次の最大の2の累乗であるターゲット型を選択します。

  • minScalar()widenScalarIf()に似ていますが、型のサイズ(ビット単位)が指定された最小値より小さい場合にのみ満たされ、最小値をターゲット型として選択します。同様に、最大値に対するmaxScalar()と、両方を行うclampScalar()もあります。

  • minScalarSameAs()minScalar()に似ていますが、最小値は別の型インデックスから取得されます。

  • moreElementsToNextMultiple()moreElementsToNextPow2()に似ていますが、2の累乗ではなくXの倍数に基づいています。

最小ルールセット

GlobalISelの合法化器は、特定のターゲットがバックエンドの残りの部分が処理しなければならないGMIRをどのように形成するかについて、非常に柔軟性があります。しかし、すべてのターゲットが満たす必要がある少数の要件があります。

最小要件について説明する前に、いくつかの用語が必要です。

生成元型集合

少なくとも1つの合法的な命令によって生成される可能性のあるすべての型の集合です。

使用先型集合

少なくとも1つの合法的な命令によって使用される可能性のあるすべての型の集合です。

両方の集合は多くの場合同一ですが、それを保証するものではありません。例えば、s64を使用できないが、いくつかの特定の命令に対してはそれを生成できることは珍しくありません。

スカラーの最小ルール

  • G_ANYEXTは、生成元型集合からのすべての入力と、使用先型集合からのすべてのより大きい出力に対して合法である必要があります。

  • G_TRUNCは、生成元型集合からのすべての入力と、使用先型集合からのすべてのより小さい出力に対して合法である必要があります。

G_ANYEXTとG_TRUNCは、GMIRは異なる型サイズの演算を接続する手段を必要とするため、必須の合法性を持っています。G_ANYEXTは追加ビットの値を定義せず、G_TRUNCはビットを破棄するため、通常はサポートするのは簡単です。他の変換は、さらに合法化の対象となる追加の演算を使用して、G_ANYEXT/G_TRUNCに下げることができます。例えば、G_SEXTは以下のように下げることができます。

%1 = G_ANYEXT %0
%2 = G_CONSTANT ...
%3 = G_SHL %1, %2
%4 = G_ASHR %3, %2

そして、G_CONSTANT/G_SHL/G_ASHRはさらに他の演算またはターゲット命令に下げることができます。同様に、G_FPEXTは、ターゲット命令に続くG_ANYEXTに下げることができるため、合法性の要件はありません。

G_MERGE_VALUESとG_UNMERGE_VALUESは、前者はG_ANYEXTといくつかの他の合法化可能な命令に下げることができ、後者はいくつかの合法化可能な命令に続くG_TRUNCに下げることができるため、合法性の要件はありません。

ベクトルの最小合法性

ベクトル型内では、ベクトルは多くの場合、ビットを再解釈するか、ベクトルを分解して異なる型として再構成することによって変換されるため、LLVM IRには定義済みの変換はありません。そのため、G_BITCASTは考慮すべき唯一の演算です。通常、COPY(またはreplaceAllUses()を使用して何も行わない)に下げることができるため、合法であることを要求するわけではありません。ただし、G_BITCASTが非自明な状況があります(例:ビッグエンディアンMIPS MSAやビッグエンディアンARM NEONなどのビッグエンディアンデータのリトルエンディアンベクトル、_i_bitcastを参照)。これを考慮して、G_BITCASTは、値のビットパターンを変更するすべての型の組み合わせに対して合法である必要があります。

G_BUILD_VECTORまたはG_BUILD_VECTOR_TRUNCには合法性の要件がありません。これらは次のように処理できます。* それらを合法と宣言する。* それらをスカラー化する。* それらをG_TRUNC+G_ANYEXTといくつかの合法化可能な命令に下げる。* 定義により合法的なターゲット命令に下げる。

同じ理由により、G_UNMERGE_VALUESはベクトル入力に対して合法性の要件がありません。

ポインタの最小合法性

G_INTTOPTRとG_PTRTOINTは、合法化器によってレジスタクラスから別のレジスタクラスへのCOPYに選択できるため、ポインタには最小ルールがありません。

演算の最小合法性

G_ANYEXT、G_MERGE_VALUES、G_BITCAST、G_BUILD_VECTOR、G_BUILD_VECTOR_TRUNC、G_CONCAT_VECTORS、G_UNMERGE_VALUES、G_PTRTOINT、およびG_INTTOPTRに関するルールは既に上記で述べられています。それらに加えて、次の演算には要件があります。

  • 少なくとも1つのG_IMPLICIT_DEFが合法である必要があります。これは通常、選択するコードを必要としないため簡単です。

  • G_PHIは、生成元型集合と使用先型集合のすべての型に対して合法である必要があります。これは通常、選択するコードを必要としないため簡単です。

  • 少なくとも1つのG_FRAME_INDEXが合法である必要があります。

  • 少なくとも1つのG_BLOCK_ADDRが合法である必要があります。

合法性の要件があると予想される他の多くの演算がありますが、それらは定義により合法的なターゲット命令に下げることができます。