Next: Incdec, Previous: RTL Declarations, Up: RTL
目前为止,所描述的表达式代码都是用来表示一个值,而不是操作。但是机器指令是不会产生值的,而只是通过副作用来改变机器状态。特定的表达式代码被用来表示副作用。
一条指令的主体,总是这些副作用代码之一;上面描述的表示值的代码,只是作为操作数出现在其中。
(set
lval x)
reg
(或者subreg
,strict_low_part
或者zero_extract
),mem
,pc
,parallel
或者cc0
。
如果lval是一个reg
,subreg
或者mem
,其具有一个机器模式;则x必须对这种模式有效。
If lval is a reg
whose machine mode is less than the full
width of the register, then it means that the part of the register
specified by the machine mode is given the specified value and the
rest of the register receives an undefined value. Likewise, if
lval is a subreg
whose machine mode is narrower than
the mode of the register, the rest of the register can be changed in
an undefined way.
如果lval是一个subreg
的strict_low_part
,则由subreg
的机器模式所指定的寄存器的那部分被赋予值x,而寄存器的其它部分不变。
如果lval是一个zero_extract
,则由zero_extract
指定的相关位域(内存或者寄存器相关的),被赋予值x,而其它位域不变。注意sign_extract
不能出现在lval中。
如果lval是(cc0)
,其没有机器模式,并且x可以为一个compare
表达式或者任意模式的值。后者情况表示是一个“test”指令。表达式(set (cc0) (reg:
m n))
等价于(set (cc0) (compare (reg:
m n)))
。在编译过程中可以使用前一个表达式来节省空间。
如果lval是一个parallel
,其用来表示一个函数通过多个寄存器来返回一个结构体的情况。parallel
中的每一个元素是一个expr_list
,其第一个操作数是一个reg
,并且第二个操作数是一个const_int
,表示相应寄存器的数据在结构体中的偏移量(以字节为单位)。第一个元素也可能为null,用来指示结构体也有一部分是在内存中传递的。
如果lval是(pc)
,则为一个跳转指令,并且x只有几种可能。其可能为一个label_ref
表达式(无条件跳转)。可能为一个if_then_else
(条件跳转),这种情况下,第二个或者第三个操作数必须是(pc)
(用于不进行跳转的情况),并且另外两个必须是一个label_ref
(用于进行跳转的情况)。x也可以是一个mem
或者(plus:SI (pc)
y)
, 其中y可以为一个reg
或者mem
;这些独特的模式用来表示通过分支表来进行跳转。
如果lval即不是(cc0)
也不是(pc)
,则lval的模式一定不是VOIDmode
,并且x的模式必须对于lval的模式有效。
(return)
return
表达式代码。
在if_then_else
表达式中,表示放在pc
中的,返回给调用者的值。
注意,指令模式为(return)
的insn,在逻辑上等价于(set (pc) (return))
,但是不使用后者的形式。
(call
function nargs)
mem
表达式,其地址为被调用的函数的地址。nargs为一个表达式,其可以用于两个目的:在一些机器上,其表示栈参数的字节数目;在其它机器上,其表示参数寄存器的数目。
每个机器具有一个标准的,function必须具有的机器模式。机器描述定义了宏FUNCTION_MODE
,来扩展为需要的模式名。在一些机器上,所允许的寻址方式取决于被寻址的机器模式,则该机器模式的用途是来描述,允许什么样的寻址。
(clobber
x)
reg
,scratch
, parallel
或者 mem
表达式。
可以用在字符串指令中,将标准的值存储到特定的硬件寄存器中。不需要去描述被存储的值,只用来告诉编译器寄存器被修改了,以免其尝试在字符串指令中保持数据。
如果x为(mem:BLK (const_int 0))
或者(mem:BLK (scratch))
,则意味着所有的内存位置必须假设被破坏。如果x为一个parallel
,其具有与set
表达式中的parallel
相同的含义。
注意,机器描述将特定的硬件寄存器归类为“call-clobbered”。所有函数调用指令都被假设为,缺省的,会破坏这些寄存器,所以不需要使用clobber
表达式来表示这些。而且,每个函数调用都被假设为潜在的修改任何内存位置,除非函数被声明为const
。
如果在parallel
中的最后一组表达式为clobber
表达式,其参数为reg
或者match_scratch
(参见RTL Template)表达式,则合并阶段可以向构建的insn中增加适当的clobber
表达式,当这样可以使得指令模式被匹配。
例如,该特点可以用在,乘法和加法指令不使用MQ寄存器,但具有一个加法累加指令,而且破坏MQ寄存器的机器上。类似的,被合并的指令可能需要临时的寄存器,而成员指令则不需要。
当寄存器的clobber
表达式,出现在具有其它副作用的parallel
中,如果是硬件寄存器,则寄存器分配者来确保在insn之前和之后,该寄存器都不会被占用。对于伪寄存器的破坏,寄存器分配者和重载过程,不对clobber分配相同的硬件寄存器,以及输入操作数。你可以破坏一个特定的硬件寄存器,一个伪寄存器,或者一个scratch
表达式;在后两种情况下,GCC将会分配一个硬件寄存器,临时使用。
对于需要临时寄存器的指令,应该使用scratch
,而不是伪寄存器,因为这将使得合并阶段可以在需要的时候增加clobber
。方式为(clobber
(match_scratch
...))。如果确实是破坏了一个伪寄存器,则使用没有出现在其它地方的伪寄存器,每次生成一个新的。否则,你可能会使CSE(公共子表达式消除)迷惑。
还有一种在parallel
中破坏伪寄存器的用法:当insn的输入操作数也被insn破坏。这种情况下,使用相同的伪寄存器。
(use
x)
reg
表达式。
在一些情况下,可能会想到,在parallel
中增加一个对寄存器的use
,来描述特定寄存器的值将会影响指令的行为。一个假定的例子为,对于一个加法指令模式,其可以根据特定的控制寄存器的值来执行环绕或者饱和加法:
(parallel [(set (reg:SI 2) (unspec:SI [(reg:SI 3) (reg:SI 4)] 0)) (use (reg:SI 1))])
这将不会工作,一些优化器将只查看局部的表达式;很可能如果你有多个具有针对unspec
相同输入的insn,它们将被优化掉,即使寄存器1中间有所改变。
这意味着,use
只能被用于描述寄存器是活跃的。在增加use
语句时,你应该多思考一下,通常,你将会使用unspec
来替代。use
RTX最常用于描述一个隐式的用于insn的固定寄存器。还可以安全的用于,编译器知道整个指令模式的结果是可变的,这样的指令模式中,例如‘movmemm’或者‘call’。
在重载阶段,具有use
指令模式的insn可以附带一个reg_equal注解。这些use
insn将在重载阶段退出之前被删除。
在延迟分支调度阶段,x可以为一个insn。这表示x之前曾经在该位置被定位,它的数据依赖需要被考虑。这些use
insn将在延迟分支调度阶段退出之前被删除。
(parallel [
x0 x1 ...])
parallel
的操作数为向量表达式。x0, x1等等为单独的副作用表达式,set
, call
, return
, clobber
或 use
。
“并行”意味着,首先所有在单个副作用中使用的值将被计算,然后,所有实际的副作用被执行。例如,
(parallel [(set (reg:SI 1) (mem:SI (reg:SI 1))) (set (mem:SI (reg:SI 1)) (reg:SI 1))])
清楚的说明了,将硬件寄存器1的值与其所寻址的内存中的值进行交换。在(reg:SI 1)
作为内存地址出现的两个地方,其都是使用执行insn之前,在寄存器1中的值。
从而,如果使用parallel
,并且期望set
的值,可以用于下一个set
,则是不正确的。例如,人们有时候尝试用这种方式来表示,为零则跳转的指令:
(parallel [(set (cc0) (reg:SI 34)) (set (pc) (if_then_else (eq (cc0) (const_int 0)) (label_ref ...) (pc)))])
但这是不正确的,因为其说明了跳转条件取决于,该指令之前的条件代码的值,而不是被该指令设置后的新值。
与最后的汇编代码输出一起执行的窥孔优化,可以产生由parallel
组成的insn,其元素为需要输出汇编代码的操作数,通常为reg
, mem
或者常量表达式。这在其它编译阶段,将不是一个好的RTL形式,但是在这里是可以的,因为已经没有其它的优化了。然而,宏NOTICE_UPDATE_CC
的定义,如果存在,如果定义了窥孔优化,则需要处理这样的insn。
(cond_exec [
cond expr])
(sequence [
insns ...])
insn
, jump_insn
, call_insn
, code_label
, barrier
或 note
。
在RTL生成过程中,不会在实际的insn中放入sequence
RTX。其表示define_expand
产生的insn序列,用来传递给emit_insn
,从而将它们插入到insn链中。当实际被插入的时候,单独的子insn将被分离出来,sequence
将被忽略掉。
当延迟槽调度完成之后,insn和所有位于其延迟槽中的insn被组成一个sequence
。需要延迟槽的insn为向量中的第一个insn;后续的insn为将被放在延迟槽中的insn。
INSN_ANNULLED_BRANCH_P
用来表示分支insn将会有条件的取消延迟槽中的insn的效果。这种情况下,INSN_FROM_TARGET_P
表示insn是来自分支的目标,并且只有当进行分支时,其才被执行;否则,insn只有当不进行分支时才被执行。参见Delay Slots.
这些表达式代码出现在副作用的地方,作为insn的主体,虽然严格的讲,它们并不总是描述副作用:
(asm_input
s)
(unspec [
operands ...]
index)
(unspec_volatile [
operands ...]
index)
unspec_volatile
用于volatile操作,并且可以有陷阱;unspec
用于其它操作。
这些代码可以出现在insn的pattern
中,parallel
中,或者表达式中。
(addr_vec:
m [
lr0 lr1 ...])
label_ref
表达式。机器模式m描述了为每个地址给定了多少空间;通常m为Pmode
。
(addr_diff_vec:
m base [
lr0 lr1 ...]
min max flags)
label_ref
表达式,base也是。机器模式m描述了为每个地址偏移给定的空间大小。min和max由分支缩短过程设置,分别存放了一个具有最小地址和最大地址的标号。详情参见rtl.def。
(prefetch:
m addr rw locality)
该insn用于最小化cache-miss的延迟,通过在访问数据之前将其移送到cache中。其应该只用于非故障的数据预取指令。