TableGen 中的 MIR 模式

使用者指南

本節適用於想要在其 TableGen 檔案中使用 MIR 模式的開發人員。

注意:此功能仍在積極開發中。本文件可能會隨著時間過時。如果您發現任何錯誤,請更新它。

使用案例

MIR 模式在以下位置支援

  • GlobalISel GICombineRule

  • GlobalISel GICombinePatFrag

語法

MIR 模式在 TableGen 中使用 DAG 資料類型。

(inst operand0, operand1, ...)

inst 必須是繼承自 Instruction(例如 G_FADD)、IntrinsicGICombinePatFrag 的定義。

運算元基本上屬於以下兩種類別之一

  • 立即數

    • 無類型、未命名:0

    • 無類型、已命名:0:$y

    • 有類型、未命名:(i32 0)

    • 有類型、已命名:(i32 0):$y

  • 機器運算元

    • 無類型:$x

    • 有類型:i32:$x

語義

  • 有類型的運算元始終會向匹配器添加運算元類型檢查。

  • 有一個簡單的類型推斷系統來傳播類型。

    • 例如,您只需要在 GICombinePatFrag 替代方案或 GICombineRule 的任何模式中使用一次 i32:$x,然後該規則/替代方案中的所有其他模式都可以簡單地使用 $xi32:$x 是多餘的)。

  • 已命名運算元的行為取決於之前是否看過該名稱。

    • 對於匹配模式,重複使用操作元名稱會檢查操作元是否相同(請參閱下面的範例 2)。

    • 對於應用模式,重複使用操作元名稱只會將該操作元複製到新的指令中(請參閱下面的範例 2)。

操作元的排序方式與其在 MachineInstr 中的排序方式相同:定義 (outs) 在前,使用 (ins) 在後。

模式通常會被分組到另一個具有虛擬運算元的 DAG 資料類型中,例如 matchapplypattern

最後,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,它允許建立與另一個(暫存器)操作元類型相同的暫存器或立即數。

類型參數

  • $ 為首碼的字串形式的操作元名稱。

語義

  • 只能出現在「應用」模式中。

  • 使用的操作元名稱必須出現在同一個 GICombineRule 的「匹配」模式中。

程式碼範例 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<> 是一種 GISpecialType,它允許匹配指令中剩餘的一個或多個操作元。

類型參數

  • 要匹配的最小額外操作元數。必須大於零。

    • 預設值為 1。

  • 要匹配的最大額外操作元數。必須嚴格大於最小值。

    • 可以使用 0 表示沒有上限。

    • 預設值為 0。

語義

  • GIVariadic<> 操作元只能出現在可變參數指令上。

  • GIVariadic<> 操作元不能是定義。

  • GIVariadic<> 操作元只能作為「匹配」模式中的最後一個操作元出現。

  • 「匹配」模式中的每個實例都必須具有唯一的名稱。

  • 在「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 (輸出)由匹配指令定義的寄存器

  • $new (輸入)寄存器

語義

  • 只能出現在「應用」模式中。

  • 如果 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 只能有一個根。

  • 具有多個定義的指令不能是 GICombinePatFrag 的根。

  • 不支援在 GICombineRuleapply 模式中使用 GICombinePatFrag

  • 除了根之外,我們無法重寫匹配的指令。

  • 不支援匹配/建立大於 64 位元的 (CImm) 立即值(請參閱 GIM_CheckConstantInt 中的註解)

  • 目前沒有辦法限制兩個暫存器/立即值的類型要相符。例如,如果一個模式需要同時適用於 i32 和 i64,則需要將其保持未定義類型並在 C++ 中檢查類型,或者複製該模式。

  • 不允許在 GICombinePatFrag 中使用 GISpecialType 運算元。

  • GIVariadic<> 匹配的運算元必須各自具有唯一的名稱。

GICombineRule

MIR 模式可以出現在 GICombineRulematchapply 模式中。

規則的 root 可以是指令的定義,也可以是具名模式。當您想要匹配的指令沒有定義時,後者會很有幫助。前者通常是較好的選擇,因為它比較簡潔。

清單 10 Combine Rule 的根是一個定義
// 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 的根是一個具名模式
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 Rules 也允許將 C++ 程式碼與 MIR 模式混合使用,以便您可以在匹配時執行額外的檢查,或在匹配後執行 C++ 動作。

請注意,apply 模式中的 C++ 程式碼與其他模式互斥。但是,您可以在 match 模式中自由混合 C++ 程式碼與其他類型的模式。match 模式中的 C++ 程式碼總是在所有其他模式匹配後最後執行。

清單 12 使用 C++ 程式碼的 Apply Pattern 範例
// 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 &)

  • 模式名稱 (用於 matchMachineInstr *,用於 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」模式必須始終重新定義匹配根所定義的所有運算元。有時,我們不需要建立指令,只需將一個定義替換為另一個匹配的暫存器即可。內建的 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:刪除無定義的根

如果我們只想刪除無定義的匹配根,可以使用內建的 GIEraseRoot

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

常見模式 #3:發出常數值

當一個立即運算元出現在「apply」模式中時,行為會根據它是否有類型而有所不同。

  • 如果立即運算元有類型,則會使用 MachineIRBuilder::buildConstant 來建立 G_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。它們有兩個主要用例

  • 透過為常見模式建立 GICombinePatFrag 來減少重複(請參閱範例 1)。

  • 針對模式的多個變體隱式複製 CombineRule(請參閱範例 2)。

GICombinePatFrag 由三個元素組成

  • 零個或多個 in (def) 參數

  • 零個或多個 out 參數

  • 可以匹配的 MIR 模式清單。

    • 當在模式中使用 GICombinePatFrag 時,會為每個可以匹配的替代方案複製一次模式。

參數可以具有以下類型

  • gi_mo,這是隱式預設值(無類型 = gi_mo)。

    • 指涉指令的任何運算元(暫存器、BB 引用、立即運算元等)。

    • 可以在 inout 參數中使用。

    • PatFrag 的使用者只能對此參數使用運算元名稱(例如 (my_pat_frag $foo))。

  • root

    • 這與 gi_mo 相同。

    • 只能在 out 參數中使用,以宣告模式的根。

    • 非空的 out 參數清單必須始終只有一個 root

  • gi_imm

    • 指涉(可能已鍵入)的立即運算元。

    • 只能在 in 參數中使用。

    • PatFrag 的使用者只能對此參數使用立即運算元(例如 (my_pat_frag 0)(my_pat_frag (i32 0))

out 運算元只能在 GICombinePatFrag 僅包含 C++ 代碼時為空。如果片段包含指令模式,則它必須至少具有一個類型為 rootout 運算元。

in 運算元的限制較少,但要記住一個重要的概念:您可以傳遞「未綁定」的運算元名稱,但前提是 GICombinePatFrag 有綁定它。請參閱下面的範例 3。

GICombinePatFrag 的使用方法與任何其他指令相同。請注意,out 運算元是定義,因此它們位於運算元列表的首位。

列表 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))>;