Next: , Previous: Insn Canonicalizations, Up: Machine Desc


16.15 为代码生成定义RTL序列

在一些目标机上,一些用于RTL生成的标准指令模式名无法通过单个insn来处理, 但是可以用一个RTL insn序列来表示它们。对于这些目标机, 你可以写一个define_expand来指定如何生成RTL序列。

define_expand为一个RTL表达式,看起来非常像define_insn; 但是不同之处为,define_expand只用于RTL生成,并且可以产生多个RTL insn。

define_expand RTX具有4个操作数:

每个由define_expand生成的RTL insn必须匹配机器描述中的某个 define_insn。否则,编译器在尝试为insn生成代码或者试图对其进行优化的时候, 将会崩溃。

RTL模板,除了控制RTL insn的生成,还描述了当使用该指令模式时, 所需要指定的操作数。特别是,它为每个操作数给出了断言。

需要被指定的由指令模式生成RTL的,真正的操作数, 在RTL模板中它第一次出现的位置使用match_operand来描述。 这将把对于操作数的断言信息放入记录该事情的表中。 如果操作数被引用多次,则后续的引用应该使用match_dup

RTL模板还可以引用内部操作数, 其为只在由define_expand生成的序列中使用的临时寄存器或者标号。 内部操作数使用match_dup来替换到RTL模板中,而不是match_operand。 内部操作数的值在编译器需要使用该指令模式时,不作为参数传入。替代的, 它们在指令模式中计算,在准备语句中。 这些语句计算值并将它们存入到合适的operands元素中, 以便match_dup可以找到它们。

有两个特定的宏,用于准备语句中:DONEFAIL。 在其后面跟随一个分号,以作为一条语句来使用。

DONE
使用DONE宏来结束该指令模式的RTL生成。这种情况下, 由该指令模式生成的唯一的RTL insn将为在准备语句中显示调用emit_insn生成 的insn;RTL模板将不被生成。


FAIL
使指令模式对于这种情况失败。当指令模式失败时,这意味着指令模式实际上无效。编译器中的调用程序将会尝试其它策略,使用其它指令模式来进行代码生成。

目前,FAIL操作只支持二元(加法,乘法,移位等)和 位域(extv, extzvinsv)操作。

如果准备语句即没有调用DONE,也没有调用FAIL, 则define_expand的行为便跟define_insn一样,即RTL模板用于生成insn

RTL模板不用于匹配,只是用于生成最初的insn列表。 如果准备语句总是调用DONE或者FAIL, 则RTL模板可以简化为一个简单的操作数列表,例如:

     (define_expand "addsi3"
       [(match_operand:SI 0 "register_operand" "")
        (match_operand:SI 1 "register_operand" "")
        (match_operand:SI 2 "register_operand" "")]
       ""
       "
     {
       handle_add (operands[0], operands[1], operands[2]);
       DONE;
     }")

这里有一个例子,是为SPUR芯片定义的左移位:

     (define_expand "ashlsi3"
       [(set (match_operand:SI 0 "register_operand" "")
             (ashift:SI
               (match_operand:SI 1 "register_operand" "")
               (match_operand:SI 2 "nonmemory_operand" "")))]
       ""
       "
     {
       if (GET_CODE (operands[2]) != CONST_INT
           || (unsigned) INTVAL (operands[2]) > 3)
         FAIL;
     }")

这个例子使用了define_expand,使得当移位数在支持的0到3的范围内时, 便会生成移位RTL insn,而对于其它情况,则会失败。当其失败时, 编译器便会使用不同的指令模式(比如一个库调用)来尝试其它策略。

如果编译器能够处理具有名字的指令模式中的非平凡的条件字符串, 则对于这样情况也可以使用define_insn。这里有另一种情况(68000上的零扩展), 其使用了define_expand的更强大的功能:

     (define_expand "zero_extendhisi2"
       [(set (match_operand:SI 0 "general_operand" "")
             (const_int 0))
        (set (strict_low_part
               (subreg:HI
                 (match_dup 0)
                 0))
             (match_operand:HI 1 "general_operand" ""))]
       ""
       "operands[1] = make_safe_from (operands[1], operands[0]);")

这里将会生成两个RTL insn,一个用于清除整个输出操作数, 另一个用于将输入操作数复制到其低半部份。 该指令序列在输入操作数指向输出操作数(的旧值)时,是不正确的。 所以,准备语句用来确保不是这样。当其指向operands[0]时, 函数make_safe_from用来将operands[1]复制到临时寄存器中。 其通过生成另一个RTL insn来完成这件事。

最后,第三个例子显示了内部操作数的使用。 在SPUR芯片上的零扩展是通过将结果与上半字mask来完成的。 但是该mask不能通过一个const_int来表示,因为常量值太大, 无法在该机器上被合法化。所以其必须使用force_reg复制到寄存器中, 然后在and中使用该寄存器。

     (define_expand "zero_extendhisi2"
       [(set (match_operand:SI 0 "register_operand" "")
             (and:SI (subreg:SI
                       (match_operand:HI 1 "register_operand" "")
                       0)
                     (match_dup 2)))]
       ""
       "operands[2]
          = force_reg (SImode, GEN_INT (65535)); ")

注意:如果define_expand被用于一个标准的二元或者一元算数运算, 或者一个位域运算,则其生成的最后的insn一定不能为一个code_label, barriernote。其必须为一个insn, jump_insncall_insn。如果你在结尾处不需要实际的insn, 则可以生成一条将操作数的结果复制到其本身的insn。这样的insn将不会生成代码, 但可以避免编译器中的问题。