TableGen 中的 MIR 模式

用户指南

本节面向希望在其 TableGen 文件中使用 MIR 模式的开发人员。

NOTE:此功能仍在积极开发中。本文档可能会随着时间的推移而过时。如果您发现任何不正确的内容,请更新它。

使用案例

MIR 模式在以下位置受支持

  • 全局指令选择 GICombineRule

  • 全局指令选择 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 中的顺序相同:定义(输出)首先出现,然后是使用(输入)。

模式通常分组到另一个 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<> 操作数只能作为“匹配”模式中的最后一个操作数出现。

  • “匹配”模式中的每个实例都必须具有唯一的名称。

  • 在“应用”模式中重新使用 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))

语义

  • 只能作为“应用”模式列表中唯一的模式出现。

  • 根节点不能有任何输出操作数。

  • 根节点必须是 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 不受支持。

  • 我们无法重写匹配的指令(根节点除外)。

  • 匹配/创建 (CImm) 立即数 > 64 位不受支持(请参阅 GIM_CheckConstantInt 中的注释)。

  • 目前无法约束两个寄存器/立即数类型进行匹配。例如,如果模式需要在 i32 和 i64 上工作,则您需要将其保留为无类型并在 C++ 中检查类型,或者复制模式。

  • GISpecialType 操作数不允许在 GICombinePatFrag 中使用。

  • GIVariadic<> 匹配的操作数必须每个都有唯一的名称。

GICombineRule

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

规则的root可以是指令的定义(def),也可以是命名的模式。当要匹配的指令没有定义时,后者很有用。前者通常更受欢迎,因为它更简洁。

清单 10 Combine Rule 的 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))
>;
清单 11 Combine Rule 的 root 是一个命名的模式
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 &

  • 模式名称(MachineInstr * 用于 matchMachineInstrBuilder & 用于 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:删除无 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)。

清单 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 引用、imm 等)。

    • 可以在 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 操作数是 def,因此它们在操作数列表中排在首位。

清单 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))>;