MIR 模式在 TableGen 中的應用¶
使用者指南¶
本節適用於想要在其 TableGen 檔案中使用 MIR 模式的開發人員。
NOTE
:此功能仍在積極開發中。本文檔可能會隨著時間推移而過時。如果您看到任何不正確的地方,請更新它。
使用案例¶
以下位置支援 MIR 模式
GlobalISel
GICombineRule
GlobalISel
GICombinePatFrag
語法¶
MIR 模式在 TableGen 中使用 DAG 資料類型。
(inst operand0, operand1, ...)
inst
必須是一個繼承自 Instruction
(例如 G_FADD
), Intrinsic
或 GICombinePatFrag
的 def。
運算元基本上分為兩類
立即值
未類型化、未命名:
0
未類型化、已命名:
0:$y
已類型化、未命名:
(i32 0)
已類型化、已命名:
(i32 0):$y
機器運算元
未類型化:
$x
已類型化:
i32:$x
語意
已類型化的運算元總是會為匹配器添加運算元類型檢查。
有一個簡單的類型推斷系統來傳播類型。
例如,您只需要在
GICombinePatFrag
替代方案或GICombineRule
的任何模式中使用一次i32:$x
,然後該規則/替代方案中的所有其他模式都可以簡單地使用$x
(i32:$x
是多餘的)。
已命名的運算元的行為取決於名稱是否已被看過。
對於匹配模式,重複使用運算元名稱會檢查運算元是否相同(請參見下面的範例 2)。
對於應用模式,重複使用運算元名稱只會將該運算元複製到新指令中(請參見下面的範例 2)。
運算元的順序與它們在 MachineInstr 中的順序相同:defs(輸出)在前,然後是 uses(輸入)。
模式通常被分組到另一個具有虛擬運算子的 DAG 資料類型中,例如 match
、apply
或 pattern
。
最後,TableGen 中的任何 DAG 資料類型都可以被命名。模式也是如此。例如,以下是有效的:(G_FOO $root, (i32 0):$cst):$mypat
。這也可能有助於調試問題。模式總是被命名的,如果它們沒有名稱,則會給它們一個“匿名”名稱。如果您正在嘗試調試與 MIR 模式相關的錯誤,但錯誤訊息中提到了匿名模式,您可以嘗試命名您的模式,以確切了解問題所在。
// Match
// %imp = G_IMPLICIT_DEF
// %root = G_MUL %x, %imp
(match (G_IMPLICIT_DEF $imp),
(G_MUL $root, $x, $imp))
// 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' 模式中。
def mul_by_neg_one: GICombineRule <
(defs root:$root),
(match (G_MUL $dst, $x, -1)),
(apply (G_SUB $dst, (GITypeOf<"$x"> 0), $x))
>;
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¶
(apply (GIReplaceReg $old, $new))
運算元
$old
(out) 由匹配的指令定義的暫存器$new
(in) 暫存器
語意
只能出現在 'apply' 模式中。
如果 old/new 都是匹配指令的運算元,則在應用規則之前會檢查
canReplaceReg
。
GIEraseRoot¶
(apply (GIEraseRoot))
語意
只能作為 'apply' 模式列表中的唯一模式出現。
根不能有任何輸出運算元。
根必須是 CodeGenInstruction
指令旗標¶
MIR 模式支援匹配和寫入 MIFlags
。
def Test : GICombineRule<
(defs root:$dst),
(match (G_FOO $dst, $src, (MIFlags FmNoNans, FmNoInfs))),
(apply (G_BAR $dst, $src, (MIFlags FmReassoc)))>;
在 apply
模式中,我們也支援引用匹配的指令來“取得”其 MIFlags。
; 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
運算子可用於檢查旗標是否未出現在匹配的指令上,以及從產生的指令中移除旗標。
; 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
的根。不支援在
GICombineRule
的apply
模式中使用GICombinePatFrag
。我們無法重寫匹配的指令,除了根指令之外。
不支援匹配/建立 >64 位元的 (CImm) 立即值(請參閱
GIM_CheckConstantInt
中的註解)目前沒有辦法約束兩個暫存器/立即值類型以進行匹配。例如,如果模式需要在 i32 和 i64 上工作,您需要保持未類型化並在 C++ 中檢查類型,或複製模式。
不允許在
GICombinePatFrag
中使用GISpecialType
運算元。GIVariadic<>
匹配的運算元都必須具有唯一的名稱。
GICombineRule¶
MIR 模式可以出現在 GICombineRule
的 match
或 apply
模式中。
規則的 root
可以是指令的 def,也可以是已命名的模式。當您要匹配的指令沒有 defs 時,後者很有用。前者通常更受歡迎,因為它不那麼冗長。
// 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))
>;
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++ 程式碼始終在所有其他模式匹配後最後運行。
// 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)
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_CONSTANT
。G_BUILD_VECTOR
將用於向量。如果立即值未類型化,則添加一個簡單的立即值 (
MachineInstrBuilder::addImm
)。
當然,G_CONSTANT
有一個特殊情況。G_CONSTANT
的立即值必須始終是類型化的,並且添加 CImm (MachineInstrBuilder::addCImm
)。
// 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 引用、立即值等)。
可以在
in
和out
參數中使用。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
運算元只能為空。如果片段包含指令模式,則它必須至少有一個類型為 root
的 out
運算元。
in
運算元受到的限制較少,但有一個重要的概念要記住:您可以傳遞“未綁定”的運算元名稱,但前提是 GICombinePatFrag
綁定了它。請參閱下面的範例 3。
GICombinePatFrag
的使用方式與任何其他指令相同。請注意,out
運算元是 defs,因此它們在運算元列表中排在最前面。
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)>;
// 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))>;
// 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))>;
範例展示¶
我們應該使用精確的模式來聲明我們的意圖。請避免在模式中使用 wip_match_opcode。它可能導致不精確的模式。
// Imprecise: matches any G_ZEXT
def zext : GICombineRule<
(defs root:$root),
(match (wip_match_opcode G_ZEXT):$root,
[{ return Helper.matchZextOfTrunc(*${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFn(*${root}, ${matchinfo}); }])>;
// Imprecise: matches G_ZEXT of G_TRUNC
def zext_of_trunc : GICombineRule<
(defs root:$root),
(match (G_TRUNC $src, $x),
(G_ZEXT $root, $src),
[{ return Helper.matchZextOfTrunc(${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFnMO(${root}, ${matchinfo}); }])>;
// Precise: matches G_ZEXT of G_TRUNC with nuw flag
def zext_of_trunc_nuw : GICombineRule<
(defs root:$root),
(match (G_TRUNC $src, $x, (MIFlags NoUWrap)),
(G_ZEXT $root, $src),
[{ return Helper.matchZextOfTrunc(${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFnMO(${root}, ${matchinfo}); }])>;
// Precise: lists all combine combinations
class ext_of_ext_opcodes<Instruction ext1Opcode, Instruction ext2Opcode> : GICombineRule <
(defs root:$root, build_fn_matchinfo:$matchinfo),
(match (ext2Opcode $second, $src):$Second,
(ext1Opcode $root, $second):$First,
[{ return Helper.matchExtOfExt(*${First}, *${Second}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFn(*${First}, ${matchinfo}); }])>;
def zext_of_zext : ext_of_ext_opcodes<G_ZEXT, G_ZEXT>;
def zext_of_anyext : ext_of_ext_opcodes<G_ZEXT, G_ANYEXT>;
def sext_of_sext : ext_of_ext_opcodes<G_SEXT, G_SEXT>;
def sext_of_anyext : ext_of_ext_opcodes<G_SEXT, G_ANYEXT>;
def anyext_of_anyext : ext_of_ext_opcodes<G_ANYEXT, G_ANYEXT>;
def anyext_of_zext : ext_of_ext_opcodes<G_ANYEXT, G_ZEXT>;
def anyext_of_sext : ext_of_ext_opcodes<G_ANYEXT, G_SEXT>;