Next: , Previous: Expander Definitions, Up: Machine Desc


16.16 定义如何拆分指令

有两种情况,你应该指定如何将一个指令模式拆分为多个insn。在一些机器上, 指令需要延迟槽(参见Delay Slots)或者指令的输出对于多周期 (参见Processor pipeline description)不可用, 则优化这些情况的编译器过程需要能够将insn移入延迟槽中。但是, 一些insn可能会生成不止一条机器指令。这些insn则不能被放入延迟槽。

通常你可以重写单个insn为单独的insn列表,每个对应于一条机器指令。 这样做的缺点是它将造成编译变慢并且需要更多的空间。如果结果insn太复杂, 则还会抑制一些优化。当编译器有理由相信可以改进指令或者延迟槽调度的时候, 则会拆分insn。

insn组合器阶段还拆分putative insns。 如果三个insn被合并到一个使用复杂表达式的insn, 其不能被某个define_insn模式匹配, 则组合器阶段尝试将复杂指令模式拆分为两个被识别的insn。通常, 它能够将复杂指令模式通过拆分某个子表达式来断开。但是,有些情况下, 像在一个RISC机器上执行一个大常量的加法,则拆分加法为两个insn的方式是机器相关的。

define_split定义告诉了编译器如何将一个复杂的insn拆分为多个简单的insn。 它的形式为:

     (define_split
       [insn-pattern]
       "condition"
       [new-insn-pattern-1
        new-insn-pattern-2
        ...]
       "preparation-statements")

insn-pattern为需要被拆分的指令模式,condition为要被测试的最终条件, 跟define_insn中的一样。当一个insn匹配insn-pattern, 并且满足条件condition,则它由insn列表new-insn-pattern-1, new-insn-pattern-2等来替换。

preparation-statements与那些为define_expand(参见Expander Definitions) 指定的语句类似,并且在生成新RTL之前被执行。 与define_expand中的不同之处为,这些语句不能生成任何新的伪寄存器。 一旦完成重载,它们则不能在栈帧中分配任何空间。

指令模式根据两种不同的环境来匹配insn-pattern。 如果需要为延迟槽调度或者insn调度来拆分insn,则insn已经是有效的, 这意味着它已经被一些define_insn匹配过, 并且如果reload_completed为非0,则已经满足那个define_insn的约束。 在那种情况下,新的insn模式必须也是匹配某个define_insn的insn, 并且如果reload_completed为非0,则也必须满足那些定义的约束。

对于这种define_split用法的例子,考虑下面来自a29k.md的例子, 其将从HImodeSImodesign_extend拆分为一对shift insn:

     (define_split
       [(set (match_operand:SI 0 "gen_reg_operand" "")
             (sign_extend:SI (match_operand:HI 1 "gen_reg_operand" "")))]
       ""
       [(set (match_dup 0)
             (ashift:SI (match_dup 1)
                        (const_int 16)))
        (set (match_dup 0)
             (ashiftrt:SI (match_dup 0)
                          (const_int 16)))]
       "
     { operands[1] = gen_lowpart (SImode, operands[1]); }")

当组合器阶段尝试拆分一个insn模式时,则情况总是为, 指令模式没有被任何define_insn匹配。 组合器过程首先尝试将单个set表达式拆分, 然后是在parallel中的相同的set表达式, 不过跟随一个伪寄存器的clobber,以作为scratch寄存器来使用。 这这些情况下,组合器期望能够生成两个新的insn。 它将验证这些指令模式匹配某个define_insn定义, 所以你不需要在define_split中做这些测试(当然,there is no point in writing a define_split that will never produce insns that match).

     (define_split
       [(set (match_operand:SI 0 "gen_reg_operand" "")
             (plus:SI (match_operand:SI 1 "gen_reg_operand" "")
                      (match_operand:SI 2 "non_add_cint_operand" "")))]
       ""
       [(set (match_dup 0) (plus:SI (match_dup 1) (match_dup 3)))
        (set (match_dup 0) (plus:SI (match_dup 0) (match_dup 4)))]
     "
     {
       int low = INTVAL (operands[2]) & 0xffff;
       int high = (unsigned) INTVAL (operands[2]) >> 16;
     
       if (low & 0x8000)
         high++, low |= 0xffff0000;
     
       operands[3] = GEN_INT (high << 16);
       operands[4] = GEN_INT (low);
     }")

这里断言non_add_cint_operand匹配任何不是单个add insn的有效操作数的 const_int

使用scratch寄存器的例子,来自同一个文件, 用来生成等价的寄存器和大常量的比较运算:

     (define_split
       [(set (match_operand:CC 0 "cc_reg_operand" "")
             (compare:CC (match_operand:SI 1 "gen_reg_operand" "")
                         (match_operand:SI 2 "non_short_cint_operand" "")))
        (clobber (match_operand:SI 3 "gen_reg_operand" ""))]
       "find_single_use (operands[0], insn, 0)
        && (GET_CODE (*find_single_use (operands[0], insn, 0)) == EQ
            || GET_CODE (*find_single_use (operands[0], insn, 0)) == NE)"
       [(set (match_dup 3) (xor:SI (match_dup 1) (match_dup 4)))
        (set (match_dup 0) (compare:CC (match_dup 3) (match_dup 5)))]
       "
     {
       /* Get the constant we are comparing against, C, and see what it
          looks like sign-extended to 16 bits.  Then see what constant
          could be XOR'ed with C to get the sign-extended value.  */
     
       int c = INTVAL (operands[2]);
       int sextc = (c << 16) >> 16;
       int xorv = c ^ sextc;
     
       operands[4] = GEN_INT (xorv);
       operands[5] = GEN_INT (sextc);
     }")

为了避免混淆,不要写这样的define_split, 其接受匹配某个define_insn的一些insn,同时也接受不匹配的insn。替代的, 可以写两个分别的define_split定义,一个针对有效的insn, 一个针对无效的insn。

允许将跳转指令拆分为一个跳转序列或者在拆分非跳转指令时创建新的跳转。 由于控制流图和分支预测信息需要更新,所以会有一些限制。

将跳转指令拆分为由另一个跳转指令覆盖的指令序列,总是有效的, 因为编译器期望新的跳转具有相同的行为。当新的序列包含多个跳转指令或新的标号时, 则需要更多的辅助。只允许创建无条件跳转,或者简单的条件跳转指令。另外, 其必须为每个条件跳转附加一个REG_BR_PROB注解。 全局变量split_branch_probability保存了原始分支的可能性。 为了简化边频率的重新计算,新的序列要求只具有向前跳转。

对于通常的情况,define_split的模式完全匹配define_insn的模式, 则可以使用define_insn_and_split。其形式为:

     (define_insn_and_split
       [insn-pattern]
       "condition"
       "output-template"
       "split-condition"
       [new-insn-pattern-1
        new-insn-pattern-2
        ...]
       "preparation-statements"
       [insn-attributes])
     

insn-pattern, condition, output-templateinsn-attributes跟在define_insn中的用法一样. new-insn-pattern向量和preparation-statements跟在 define_split中的用法一样。 split-condition也跟在define_split中的用法一样, 不同之处是如果condition开始于‘&&’, 则用于拆分的条件将被构造为split condition和insn condition的逻辑“and”运算。 例如,在i386.md中:

     (define_insn_and_split "zero_extendhisi2_and"
       [(set (match_operand:SI 0 "register_operand" "=r")
          (zero_extend:SI (match_operand:HI 1 "register_operand" "0")))
        (clobber (reg:CC 17))]
       "TARGET_ZERO_EXTEND_WITH_AND && !optimize_size"
       "#"
       "&& reload_completed"
       [(parallel [(set (match_dup 0)
                        (and:SI (match_dup 0) (const_int 65535)))
                   (clobber (reg:CC 17))])]
       ""
       [(set_attr "type" "alu1")])
     

在这种情况下,实际的split condition将为‘TARGET_ZERO_EXTEND_WITH_AND && !optimize_size && reload_completed’。

define_insn_and_split结构提供了与两个单独的define_insndefine_split指令模式相同的功能. 其形式紧凑。