MIR 模式在 TableGen 中的應用

使用者指南

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

NOTE:此功能仍在積極開發中。本文檔可能會隨著時間推移而過時。如果您看到任何不正確的地方,請更新它。

使用案例

以下位置支援 MIR 模式

  • GlobalISel GICombineRule

  • GlobalISel GICombinePatFrag

語法

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

(inst operand0, operand1, ...)

inst 必須是一個繼承自 Instruction (例如 G_FADD), IntrinsicGICombinePatFrag 的 def。

運算元基本上分為兩類

  • 立即值

    • 未類型化、未命名:0

    • 未類型化、已命名:0:$y

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

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

  • 機器運算元

    • 未類型化:$x

    • 已類型化:i32:$x

語意

  • 已類型化的運算元總是會為匹配器添加運算元類型檢查。

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

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

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

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

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

運算元的順序與它們在 MachineInstr 中的順序相同:defs(輸出)在前,然後是 uses(輸入)。

模式通常被分組到另一個具有虛擬運算子的 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,允許建立與另一個(暫存器)運算元類型相同的暫存器或立即值。

類型參數

  • 作為字串的運算元名稱,以 $ 作為前綴。

語意

  • 只能出現在 '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<> 是一個 GISpecialType,允許匹配指令上剩餘的一個或多個運算元。

類型參數

  • 要匹配的額外運算元的最小數量。必須大於零。

    • 預設值為 1。

  • 要匹配的額外運算元的最大數量。必須嚴格大於最小值。

    • 0 可用於表示沒有上限。

    • 預設值為 0。

語意

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

  • GIVariadic<> 運算元不能是 defs。

  • GIVariadic<> 運算元只能作為 'match' 模式中的最後一個運算元出現。

  • 'match' 模式中的每個實例都必須具有唯一的名稱。

  • 在 'apply' 模式中重複使用 GIVariadic<> 運算元將導致從原始指令複製所有匹配的運算元。

  • min/max 運算元將導致匹配器檢查運算元數量是否在該範圍內。

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

  • 具有多個 defs 的指令不能作為 GICombinePatFrag 的根。

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

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

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

  • 目前沒有辦法約束兩個暫存器/立即值類型以進行匹配。例如,如果模式需要在 i32 和 i64 上工作,您需要保持未類型化並在 C++ 中檢查類型,或複製模式。

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

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

GICombineRule

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

規則的 root 可以是指令的 def,也可以是已命名的模式。當您要匹配的指令沒有 defs 時,後者很有用。前者通常更受歡迎,因為它不那麼冗長。

列表 10 組合規則根是一個 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 組合規則根是一個已命名的模式
def Foo : GICombineRule<
  (defs root:$root),
  (match (G_ZEXT $tmp, (i32 0)),
         (G_STORE $tmp, $ptr):$root),
  (apply (G_STORE (i32 0), $ptr):$root)>;

組合規則還允許將 C++ 程式碼與 MIR 模式混合使用,以便您可以在匹配時執行額外的檢查,或在匹配後執行 C++ 動作。

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

列表 12 具有 C++ 程式碼的應用模式範例
// 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 &)

  • 模式名稱 (MachineInstr * 用於 match, MachineInstrBuilder & 用於 apply)

列表 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:移除無定義的根

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

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

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

當立即值運算元出現在 'apply' 模式中時,行為取決於它是否已類型化。

  • 如果立即值已類型化,則使用 MachineIRBuilder::buildConstant 來建立 G_CONSTANTG_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))

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

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

GICombinePatFrag 的使用方式與任何其他指令相同。請注意,out 運算元是 defs,因此它們在運算元列表中排在最前面。

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