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
。
在其后面跟随一个分号,以作为一条语句来使用。
DONE
DONE
宏来结束该指令模式的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将不会生成代码,
但可以避免编译器中的问题。