TableGenにおけるMIRパターン

ユーザーガイド

このセクションは、TableGenファイルでMIRパターンを使用したい開発者向けです。

注意: この機能はまだ開発中です。このドキュメントは時間の経過とともに古くなる可能性があります。何か間違いを見つけたら、更新してください。

ユースケース

MIRパターンは、次の場所でサポートされています。

  • GlobalISel GICombineRule

  • GlobalISel GICombinePatFrag

構文

MIRパターンは、TableGenでDAGデータ型を使用します。

(inst operand0, operand1, ...)

instは、Instruction(例:G_FADD)、Intrinsic、またはGICombinePatFragを継承するdefである必要があります。

オペランドは基本的に次の2つのカテゴリのいずれかに分類されます。

  • 即値

    • 型なし、名前なし: 0

    • 型なし、名前付き: 0:$y

    • 型付き、名前なし: (i32 0)

    • 型付き、名前付き: (i32 0):$y

  • マシンオペランド

    • 型なし: $x

    • 型付き: i32:$x

セマンティクス

  • 型付きオペランドは、常にマッチャーにオペランド型チェックを追加します。

  • 型を伝播するための簡単な型推論システムがあります。

    • 例:i32:$xは、GICombinePatFragの代替パターンまたはGICombineRuleの任意のパターンで1回だけ使用する必要があります。その後、そのルール/代替の他のすべてのパターンは、単に$xを使用できます(i32:$xは冗長です)。

  • 名前付きオペランドの動作は、その名前が以前に表示されたかどうかによって異なります。

    • マッチパターンの場合、オペランド名を再利用すると、オペランドが同一であることが確認されます(以下の例2を参照)。

    • 適用パターンの場合、オペランド名を再利用すると、そのオペランドが新しい命令に単純にコピーされます(以下の例2を参照)。

オペランドは、MachineInstrの場合と同様に順序付けられます。def(out)が最初にあり、次にuse(in)があります。

パターンは通常、matchapply、またはpatternなどのダミー演算子を持つ別のDAGデータ型にグループ化されます。

最後に、TableGenの任意のDAGデータ型に名前を付けることができます。これはパターンにも当てはまります。例:次のものは有効です:(G_FOO $root, (i32 0):$cst):$mypat。これは、問題をデバッグするのに役立つ場合もあります。パターンは常に名前付きであり、名前がない場合は、「匿名」の名前が付けられます。MIRパターンに関連するエラーをデバッグしようとしているが、エラーが匿名パターンについて言及している場合は、パターンに名前を付けて、問題が正確にどこにあるかを確認できます。

リスト1 パターン例1
// Match
//    %imp = G_IMPLICIT_DEF
//    %root = G_MUL %x, %imp
(match (G_IMPLICIT_DEF $imp),
       (G_MUL $root, $x, $imp))
リスト2 パターン例2
// using $x twice here checks that the operand 1 and 2 of the G_AND are
// identical.
(match (G_AND $root, $x, $x))
// using $x again here copies operand 1 from G_AND into the new inst.
(apply (COPY $root, $x))

ValueType

ValueTypeのサブクラスは有効な型です。例:i32

GITypeOf

GITypeOf<"$x">は、別の(レジスタ)オペランドと同じ型のレジスタまたは即値を作成できるGISpecialTypeです。

型パラメーター

  • 文字列としてのオペランド名。プレフィックスは$

セマンティクス

  • 「apply」パターンでのみ出現できます。

  • 使用されるオペランド名は、同じGICombineRuleの「match」パターンに表示される必要があります。

リスト3 例: 即値
def mul_by_neg_one: GICombineRule <
  (defs root:$root),
  (match (G_MUL $dst, $x, -1)),
  (apply (G_SUB $dst, (GITypeOf<"$x"> 0), $x))
>;
リスト4 例: 一時レジスタ
def Test0 : GICombineRule<
  (defs root:$dst),
  (match (G_FMUL $dst, $src, -1)),
  (apply (G_FSUB $dst, $src, $tmp),
         (G_FNEG GITypeOf<"$dst">:$tmp, $src))>;

GIVariadic

GIVariadic<>は、命令に残っている1つ以上のオペランドを照合できるGISpecialTypeです。

型パラメーター

  • 照合する追加オペランドの最小数。ゼロより大きくなければなりません。

    • デフォルトは1です。

  • 照合する追加オペランドの最大数。最小値より厳密に大きくなければなりません。

    • 0は、上限がないことを示すために使用できます。

    • デフォルトは0です。

セマンティクス

  • GIVariadic<>オペランドは、可変引数命令でのみ出現できます。

  • GIVariadic<>オペランドはdefにすることはできません。

  • GIVariadic<>オペランドは、「match」パターンの最後のオペランドとしてのみ出現できます。

  • 「match」パターン内の各インスタンスには、一意の名前を付ける必要があります。

  • 「apply」パターンでGIVariadic<>オペランドを再利用すると、照合されたすべてのオペランドが元の命令からコピーされます。

  • 最小/最大オペランドは、オペランドの数がその範囲内にあることをマッチャーがチェックする結果になります。

  • GIVariadic<>オペランドは、ルール内のC++コードで使用できます。これにより、オペランド名がArrayRef<MachineOperand>型の値に展開されます。

// bool checkBuildVectorToUnmerge(ArrayRef<MachineOperand>);

def build_vector_to_unmerge: GICombineRule <
  (defs root:$root),
  (match (G_BUILD_VECTOR $root, GIVariadic<>:$args),
         [{ return checkBuildVectorToUnmerge(${args}); }]),
  (apply (G_UNMERGE_VALUES $root, $args))
>;
// Will additionally check the number of operands is >= 3 and <= 5.
// ($root is one operand, then 2 to 4 variadic operands).
def build_vector_to_unmerge: GICombineRule <
  (defs root:$root),
  (match (G_BUILD_VECTOR $root, GIVariadic<2, 4>:$two_to_four),
         [{ return checkBuildVectorToUnmerge(${two_to_four}); }]),
  (apply (G_UNMERGE_VALUES $root, $two_to_four))
>;

組み込み操作

MIRパターンは、「組み込み命令」とも呼ばれる組み込み操作も提供します。これらは、そうでない場合はC++コードの使用が必要となる強力な機能を提供します。

GIReplaceReg

リスト5 使用法
(apply (GIReplaceReg $old, $new))

オペランド

  • $old(out)照合された命令によって定義されたレジスタ

  • $new(in)レジスタ

セマンティクス

  • 「apply」パターンでのみ出現できます。

  • old/newの両方が照合された命令のオペランドである場合、ルールを適用する前にcanReplaceRegがチェックされます。

GIEraseRoot

リスト6 使用法
(apply (GIEraseRoot))

セマンティクス

  • 「apply」パターンリストの唯一のパターンとしてのみ出現できます。

  • ルートに出力オペランドを含めることはできません。

  • ルートはCodeGenInstructionである必要があります

命令フラグ

MIRパターンは、MIFlagsの照合と書き込みの両方をサポートしています。

リスト7
def Test : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src, (MIFlags FmNoNans, FmNoInfs))),
  (apply (G_BAR $dst, $src, (MIFlags FmReassoc)))>;

applyパターンでは、照合された命令を参照してそのMIFlagsを「取得」することもサポートしています。

リスト8
; We match NoNans/NoInfs, but $zext may have more flags.
; Copy them all into the output instruction, and set Reassoc on the output inst.
def TestCpyFlags : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src, (MIFlags FmNoNans, FmNoInfs)):$zext),
  (apply (G_BAR $dst, $src, (MIFlags $zext, FmReassoc)))>;

not演算子を使用して、フラグが照合された命令に存在しないことを確認し、生成された命令からフラグを削除できます。

リスト9
; We match NoInfs but we don't want NoNans/Reassoc to be set. $zext may have more flags.
; Copy them all into the output instruction but remove NoInfs on the output inst.
def TestNot : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src, (MIFlags FmNoInfs, (not FmNoNans, FmReassoc))):$zext),
  (apply (G_BAR $dst, $src, (MIFlags $zext, (not FmNoInfs))))>;

制限事項

これは、現時点でMIRパターンで既知の問題の非網羅的なリストです。

  • 別のGICombinePatFrag内でGICombinePatFragを使用することはサポートされていません。

  • GICombinePatFragは単一のルートしか持つことができません。

  • 複数のdefを持つ命令は、GICombinePatFragのルートにすることはできません。

  • GICombineRuleapplyパターンでGICombinePatFragを使用することはサポートされていません。

  • ルート以外の照合された命令を書き換えることはできません。

  • (CImm)即値> 64ビットの照合/作成はサポートされていません(GIM_CheckConstantIntのコメントを参照)。

  • 現在、2つのレジスタ/即値型を一致させる方法はありません。例えば、パターンが i32 と i64 の両方で動作する必要がある場合、型なしのままにして C++ で型をチェックするか、パターンを複製する必要があります。

  • GISpecialType オペランドは、GICombinePatFrag 内では許可されていません。

  • GIVariadic<> でマッチしたオペランドは、それぞれ一意の名前を持つ必要があります。

GICombineRule

MIR パターンは、GICombineRulematch または apply パターンに記述できます。

ルールの root は、命令の def、または名前付きパターンのいずれかになります。後者は、マッチさせたい命令に def がない場合に役立ちます。通常は前者の方が冗長でないため推奨されます。

リスト 10 Combine Rule の root が def の場合
// Fold x op 1 -> x
def right_identity_one: GICombineRule<
  (defs root:$dst),
  (match (G_MUL $dst, $x, 1)),
  // Note: Patterns always need to create something, we can't just replace $dst with $x, so we need a COPY.
  (apply (COPY $dst, $x))
>;
リスト 11 Combine Rule の root が名前付きパターンの場合
def Foo : GICombineRule<
  (defs root:$root),
  (match (G_ZEXT $tmp, (i32 0)),
         (G_STORE $tmp, $ptr):$root),
  (apply (G_STORE (i32 0), $ptr):$root)>;

Combine Rule では、C++ コードと MIR パターンを混在させることもできるため、マッチング時に追加のチェックを実行したり、マッチング後に C++ アクションを実行したりできます。

apply パターンの C++ コードは、他のパターンと相互に排他的であることに注意してください。ただし、match パターンでは、C++ コードを他の種類のパターンと自由に混在させることができます。match パターンの C++ コードは、常に他のすべてのパターンがマッチングされた後に最後に実行されます。

リスト 12 C++ コードを含む Apply パターンの例
// Valid
def Foo : GICombineRule<
  (defs root:$root),
  (match (G_ZEXT $tmp, (i32 0)),
         (G_STORE $tmp, $ptr):$root,
         "return myFinalCheck()"),
  (apply "runMyAction(${root})")>;

// error: 'apply' patterns cannot mix C++ code with other types of patterns
def Bar : GICombineRule<
  (defs root:$dst),
  (match (G_ZEXT $dst, $src):$mi),
  (apply (G_MUL $dst, $src, $src),
         "runMyAction(${root})")>;

MIR パターンでは、以下の展開が可能です。

  • オペランド名 (MachineOperand &)

  • パターン名 (match の場合は MachineInstr * 、apply の場合は MachineInstrBuilder &)

リスト 13 C++ 展開の例
def Foo : GICombineRule<
  (defs root:$root),
  (match (G_ZEXT $root, $src):$mi),
  (apply "foobar(${root}.getReg(), ${src}.getReg(), ${mi}->hasImplicitDef())")>;

共通パターン #1: レジスタを別のレジスタで置換する

'apply' パターンは、マッチルートによって定義されたすべてのオペランドを常に再定義する必要があります。場合によっては、命令を作成する必要はなく、単に def を別のマッチしたレジスタで置換するだけで済みます。GIReplaceReg ビルトインは、まさにそれを行うことができます。

def Foo : GICombineRule<
  (defs root:$dst),
  (match (G_FNEG $tmp, $src), (G_FNEG $dst, $tmp)),
  (apply (GIReplaceReg $dst, $src))>;

置換レジスタが apply パターンからの一時レジスタである場合も、これは機能します。

def ReplaceTemp : GICombineRule<
  (defs root:$a),
  (match    (G_BUILD_VECTOR $tmp, $x, $y),
            (G_UNMERGE_VALUES $a, $b, $tmp)),
  (apply  (G_UNMERGE_VALUES $a, i32:$new, $y),
          (GIReplaceReg $b, $new))>

共通パターン #2: Def なしのルートを消去する

単に def のないマッチルートを消去したい場合は、GIEraseRoot ビルトインを使用できます。

def Foo : GICombineRule<
  (defs root:$mi),
  (match (G_STORE $a, $b):$mi),
  (apply (GIEraseRoot))>;

共通パターン #3: 定数値をエミットする

即値オペランドが 'apply' パターンに現れる場合、その動作は型付きであるかどうかに依存します。

  • 即値が型付きの場合、MachineIRBuilder::buildConstantG_CONSTANT を作成するために使用されます。G_BUILD_VECTOR はベクトルに使用されます。

  • 即値が型なしの場合、単純な即値が追加されます (MachineInstrBuilder::addImm)。

もちろん、G_CONSTANT には特別なケースがあります。G_CONSTANT の即値は常に型付きである必要があり、CImm が追加されます (MachineInstrBuilder::addCImm)。

リスト 14 定数エミッションの例:
// Example output:
//    %0 = G_CONSTANT i32 0
//    %dst = COPY %0
def Foo : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src)),
  (apply (COPY $dst, (i32 0)))>;

// Example output:
//    %dst = COPY 0
// Note that this would be ill-formed because COPY
// expects a register operand!
def Bar : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src)),
  (apply (COPY $dst, (i32 0)))>;

// Example output:
//    %dst = G_CONSTANT i32 0
def Bux : GICombineRule<
  (defs root:$dst),
  (match (G_FOO $dst, $src)),
  (apply (G_CONSTANT $dst, (i32 0)))>;

GICombinePatFrag

GICombinePatFrag は、MIR パターンの PatFrags に相当するものです。これらには、主に2つのユースケースがあります。

  • 共通パターンの GICombinePatFrag を作成することで、繰り返しを減らします(例1を参照)。

  • パターンの複数のバリアントに対して、CombineRule を暗黙的に複製します(例2を参照)。

GICombinePatFrag は、以下の3つの要素で構成されます。

  • 0個以上の in (def) パラメータ

  • 0個以上の out パラメータ

  • マッチング可能な MIR パターンのリスト。

    • GICombinePatFrag がパターン内で使用されると、マッチング可能な代替案ごとにパターンが一度複製されます。

パラメータは、以下の型を持つことができます。

  • gi_mo 。これは暗黙のデフォルトです(型なし = gi_mo)。

    • 命令の任意のオペランド(レジスタ、BB参照、即値など)を参照します。

    • in パラメータと out パラメータの両方で使用できます。

    • PatFrag のユーザーは、このパラメータにオペランド名のみを使用できます(例: (my_pat_frag $foo))。

  • root

    • これは gi_mo と同じです。

    • パターンのルートを宣言するために、out パラメータでのみ使用できます。

    • 空でない out パラメータリストは、常に正確に1つの root を持つ必要があります。

  • gi_imm

    • (型付きの可能性がある) 即値を参照します。

    • in パラメータでのみ使用できます。

    • PatFrag のユーザーは、このパラメータに即値のみを使用できます(例: (my_pat_frag 0) または (my_pat_frag (i32 0)))。

out オペランドは、GICombinePatFrag に C++ コードのみが含まれている場合にのみ空にできます。フラグメントに命令パターンが含まれている場合は、少なくとも1つの root 型の out オペランドを持つ必要があります。

in オペランドには制約が少ないですが、覚えておくべき重要な概念が1つあります。つまり、GICombinePatFrag がバインドする場合にのみ、「アンバウンド」のオペランド名を渡すことができます。以下の例 3 を参照してください。

GICombinePatFrag は、他の命令と同じように使用されます。out オペランドは def であるため、オペランドのリストの最初に現れることに注意してください。

リスト 15 例1: 繰り返しを減らす
def zext_cst : GICombinePatFrag<(outs root:$dst, $cst), (ins gi_imm:$val),
  [(pattern (G_CONSTANT $cst, $val),
            (G_ZEXT $dst, $cst))]
>;

def foo_to_impdef : GICombineRule<
 (defs root:$dst),
 (match (zext_cst $y, $cst, (i32 0))
        (G_FOO $dst, $y)),
 (apply (G_IMPLICIT_DEF $dst))>;

def store_ext_zero : GICombineRule<
 (defs root:$root),
 (match (zext_cst $y, $cst, (i32 0))
        (G_STORE $y, $ptr):$root),
 (apply (G_STORE $cst, $ptr):$root)>;
リスト 16 例2: 複数のルールを一度に生成する
// Fold (freeze (freeze x)) -> (freeze x).
// Fold (fabs (fabs x)) -> (fabs x).
// Fold (fcanonicalize (fcanonicalize x)) -> (fcanonicalize x).
def idempotent_prop_frags : GICombinePatFrag<(outs root:$dst, $src), (ins),
  [
    (pattern (G_FREEZE $dst, $src), (G_FREEZE $src, $x)),
    (pattern (G_FABS $dst, $src), (G_FABS $src, $x)),
    (pattern (G_FCANONICALIZE $dst, $src), (G_FCANONICALIZE $src, $x))
  ]
>;

def idempotent_prop : GICombineRule<
  (defs root:$dst),
  (match (idempotent_prop_frags $dst, $src)),
  (apply (COPY $dst, $src))>;
リスト 17 例3: アンバウンドオペランド名
// This fragment binds $x to an operand in all of its
// alternative patterns.
def always_binds : GICombinePatFrag<
  (outs root:$dst), (ins $x),
  [
    (pattern (G_FREEZE $dst, $x)),
    (pattern (G_FABS $dst, $x)),
  ]
>;

// This fragment does not bind $x to an operand in any
// of its alternative patterns.
def does_not_bind : GICombinePatFrag<
  (outs root:$dst), (ins $x),
  [
    (pattern (G_FREEZE $dst, $x)), // binds $x
    (pattern (G_FOO $dst (i32 0))), // does not bind $x
    (pattern "return myCheck(${x}.getReg())"), // does not bind $x
  ]
>;

// Here we pass $x, which is unbound, to always_binds.
// This works because if $x is unbound, always_binds will bind it for us.
def test0 : GICombineRule<
  (defs root:$dst),
  (match (always_binds $dst, $x)),
  (apply (COPY $dst, $x))>;

// Here we pass $x, which is unbound, to does_not_bind.
// This cannot work because $x may not have been initialized in 'apply'.
// error: operand 'x' (for parameter 'src' of 'does_not_bind') cannot be unbound
def test1 : GICombineRule<
  (defs root:$dst),
  (match (does_not_bind $dst, $x)),
  (apply (COPY $dst, $x))>;

// Here we pass $x, which is bound, to does_not_bind.
// This is fine because $x will always be bound when emitting does_not_bind
def test2 : GICombineRule<
  (defs root:$dst),
  (match (does_not_bind $tmp, $x)
         (G_MUL $dst, $x, $tmp)),
  (apply (COPY $dst, $x))>;