TableGen 中的 MIR 模式¶
使用者指南¶
本節適用於想要在其 TableGen 檔案中使用 MIR 模式的開發人員。
注意
:此功能仍在積極開發中。本文件可能會隨著時間過時。如果您發現任何錯誤,請更新它。
使用案例¶
MIR 模式在以下位置支援
GlobalISel
GICombineRule
GlobalISel
GICombinePatFrag
語法¶
MIR 模式在 TableGen 中使用 DAG 資料類型。
(inst operand0, operand1, ...)
inst
必須是繼承自 Instruction
(例如 G_FADD
)、Intrinsic
或 GICombinePatFrag
的定義。
運算元基本上屬於以下兩種類別之一
立即數
無類型、未命名:
0
無類型、已命名:
0:$y
有類型、未命名:
(i32 0)
有類型、已命名:
(i32 0):$y
機器運算元
無類型:
$x
有類型:
i32:$x
語義
有類型的運算元始終會向匹配器添加運算元類型檢查。
有一個簡單的類型推斷系統來傳播類型。
例如,您只需要在
GICombinePatFrag
替代方案或GICombineRule
的任何模式中使用一次i32:$x
,然後該規則/替代方案中的所有其他模式都可以簡單地使用$x
(i32:$x
是多餘的)。
已命名運算元的行為取決於之前是否看過該名稱。
對於匹配模式,重複使用操作元名稱會檢查操作元是否相同(請參閱下面的範例 2)。
對於應用模式,重複使用操作元名稱只會將該操作元複製到新的指令中(請參閱下面的範例 2)。
操作元的排序方式與其在 MachineInstr 中的排序方式相同:定義 (outs) 在前,使用 (ins) 在後。
模式通常會被分組到另一個具有虛擬運算元的 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
,它允許建立與另一個(暫存器)操作元類型相同的暫存器或立即數。
類型參數
以
$
為首碼的字串形式的操作元名稱。
語義
只能出現在「應用」模式中。
使用的操作元名稱必須出現在同一個
GICombineRule
的「匹配」模式中。
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<>
操作元不能是定義。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¶
(apply (GIReplaceReg $old, $new))
運算元
$old
(輸出)由匹配指令定義的寄存器$new
(輸入)寄存器
語義
只能出現在「應用」模式中。
如果 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
只能有一個根。具有多個定義的指令不能是
GICombinePatFrag
的根。不支援在
GICombineRule
的apply
模式中使用GICombinePatFrag
。除了根之外,我們無法重寫匹配的指令。
不支援匹配/建立大於 64 位元的 (CImm) 立即值(請參閱
GIM_CheckConstantInt
中的註解)目前沒有辦法限制兩個暫存器/立即值的類型要相符。例如,如果一個模式需要同時適用於 i32 和 i64,則需要將其保持未定義類型並在 C++ 中檢查類型,或者複製該模式。
不允許在
GICombinePatFrag
中使用GISpecialType
運算元。GIVariadic<>
匹配的運算元必須各自具有唯一的名稱。
GICombineRule¶
MIR 模式可以出現在 GICombineRule
的 match
或 apply
模式中。
規則的 root
可以是指令的定義,也可以是具名模式。當您想要匹配的指令沒有定義時,後者會很有幫助。前者通常是較好的選擇,因為它比較簡潔。
// 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)>;
Combine Rules 也允許將 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 &
)模式名稱 (用於
match
的MachineInstr *
,用於 apply 的MachineInstrBuilder &
)
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
)。
// 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))
)
out
運算元只能在 GICombinePatFrag
僅包含 C++ 代碼時為空。如果片段包含指令模式,則它必須至少具有一個類型為 root
的 out
運算元。
in
運算元的限制較少,但要記住一個重要的概念:您可以傳遞「未綁定」的運算元名稱,但前提是 GICombinePatFrag
有綁定它。請參閱下面的範例 3。
GICombinePatFrag
的使用方法與任何其他指令相同。請注意,out
運算元是定義,因此它們位於運算元列表的首位。
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}); }])>;