TableGen 中的 MIR 模式¶
用户指南¶
本节面向希望在其 TableGen 文件中使用 MIR 模式的开发人员。
NOTE
:此功能仍在积极开发中。本文档可能会随着时间的推移而过时。如果您发现任何不正确的内容,请更新它。
使用案例¶
MIR 模式在以下位置受支持
全局指令选择
GICombineRule
全局指令选择
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 中的顺序相同:定义(输出)首先出现,然后是使用(输入)。
模式通常分组到另一个 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<>
操作数只能作为“匹配”模式中的最后一个操作数出现。“匹配”模式中的每个实例都必须具有唯一的名称。
在“应用”模式中重新使用
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))
语义
只能作为“应用”模式列表中唯一的模式出现。
根节点不能有任何输出操作数。
根节点必须是 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
不受支持。我们无法重写匹配的指令(根节点除外)。
匹配/创建 (CImm) 立即数 > 64 位不受支持(请参阅
GIM_CheckConstantInt
中的注释)。目前无法约束两个寄存器/立即数类型进行匹配。例如,如果模式需要在 i32 和 i64 上工作,则您需要将其保留为无类型并在 C++ 中检查类型,或者复制模式。
GISpecialType
操作数不允许在GICombinePatFrag
中使用。GIVariadic<>
匹配的操作数必须每个都有唯一的名称。
GICombineRule¶
MIR 模式可以出现在 GICombineRule
的 match
或 apply
模式中。
规则的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))
>;
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 &
)模式名称(
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:删除无 def 的根¶
如果我们只想删除一个无 def 的匹配根,我们可以使用内置函数 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 引用、imm 等)。
可以在
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
操作数是 def,因此它们在操作数列表中排在首位。
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}); }])>;