Next: Insn Splitting, Previous: Insn Canonicalizations, Up: Machine Desc
在一些目标机上,一些用于RTL生成的标准指令模式名无法通过单个insn来处理,
但是可以用一个RTL insn序列来表示它们。对于这些目标机,
你可以写一个define_expand来指定如何生成RTL序列。
 
define_expand为一个RTL表达式,看起来非常像define_insn;
但是不同之处为,define_expand只用于RTL生成,并且可以产生多个RTL insn。
 
define_expand RTX具有4个操作数:
     
define_expand必须具有一个名字,
因为必须通过对名字的引用才能使用它。
     
     
     
     
     define_insn的条件类似。因此,
条件(如果存在)可以不依赖于所匹配的insn的数据,而只是依赖于target机器类型标号。
编译器需要在初始化时测试这些条件,以便确切的知道在一次特定的运行时,
哪些命名指令有效。
     
     
     
     
     
     
     
     
     
     emit_insn等,直接生成RTL指令。任何这些生成的指令都在RTL模板中的指令前。
每个由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可以找到它们。
 
有两个特定的宏,用于准备语句中:DONE和FAIL。
在其后面跟随一个分号,以作为一条语句来使用。
     
DONEDONE宏来结束该指令模式的RTL生成。这种情况下,
由该指令模式生成的唯一的RTL insn将为在准备语句中显示调用emit_insn生成
的insn;RTL模板将不被生成。
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     FAIL目前,FAIL操作只支持二元(加法,乘法,移位等)和
位域(extv, extzv和insv)操作。
如果准备语句即没有调用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,
barrier或note。其必须为一个insn,
jump_insn或call_insn。如果你在结尾处不需要实际的insn,
则可以生成一条将操作数的结果复制到其本身的insn。这样的insn将不会生成代码,
但可以避免编译器中的问题。