Next: , Previous: RTL Declarations, Up: RTL


10.15 副作用表达式

目前为止,所描述的表达式代码都是用来表示一个值,而不是操作。但是机器指令是不会产生值的,而只是通过副作用来改变机器状态。特定的表达式代码被用来表示副作用。

一条指令的主体,总是这些副作用代码之一;上面描述的表示值的代码,只是作为操作数出现在其中。

(set lval x)
表示将x的值存放到由lval表示的地方。lval必须是表示可以用来存放的地方的表达式:reg(或者subregstrict_low_part或者zero_extract),mempcparallel或者cc0

如果lval是一个regsubreg或者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是一个subregstrict_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的模式有效。

lval通常通过SET_DEST宏来访问,x通常使用SET_SRC宏。


(return)
在指令模式中作为单独的表达式,表示从当前函数的一个返回,在一些机器上,可以使用一条指令来完成,例如VAXen。在一些机器上,为了从函数中返回,包括多条指令的尾声必须被执行,则返回操作,通过跳转到一个位于尾声之前的标号来完成,并且不使用return表达式代码。

if_then_else表达式中,表示放在pc中的,返回给调用者的值。

注意,指令模式为(return)的insn,在逻辑上等价于(set (pc) (return)),但是不使用后者的形式。


(call function nargs)
表示一个函数调用。function为一个mem表达式,其地址为被调用的函数的地址。nargs为一个表达式,其可以用于两个目的:在一些机器上,其表示栈参数的字节数目;在其它机器上,其表示参数寄存器的数目。

每个机器具有一个标准的,function必须具有的机器模式。机器描述定义了宏FUNCTION_MODE,来扩展为需要的模式名。在一些机器上,所允许的寻址方式取决于被寻址的机器模式,则该机器模式的用途是来描述,允许什么样的寻址。


(clobber x)
表示一个不可预期的存储或者可能的存储,将不可描述的值存储到x,其必须为一个regscratch, 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)
表示对x值的使用。其表示x中的值在程序的这个点上是被需要的,即使可能不清楚为什么。因此,如果先前的执行的作用只是将一个值存储在x中,则编译器将不会尝试将其删除。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, clobberuse

“并行”意味着,首先所有在单个副作用中使用的值将被计算,然后,所有实际的副作用被执行。例如,

          (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])
表示一个条件执行表达式。只有当cond为非零时,expr才被执行。cond表达式不能具有副作用,但是expr可以。


(sequence [insns ...])
表示一个insn序列。每个出现在向量中的insns,都适合出现在insn链中,所以其必须为insn, jump_insn, call_insn, code_label, barriernote

在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)
表示文字的汇编代码,通过字符串s来描述。


(unspec [operands ...] index)
(unspec_volatile [operands ...] index)
表示一个机器特定的针对operands的操作。index在多个机器特定的操作之间进行选择。unspec_volatile用于volatile操作,并且可以有陷阱;unspec用于其它操作。

这些代码可以出现在insn的pattern中,parallel中,或者表达式中。


(addr_vec:m [lr0 lr1 ...])
表示跳转地址表。向量元素lr0等等,为label_ref表达式。机器模式m描述了为每个地址给定了多少空间;通常mPmode


(addr_diff_vec:m base [lr0 lr1 ...] min max flags)
表示一个跳转地址表,表示为base的偏移量。向量元素lr0等等,为label_ref表达式,base也是。机器模式m描述了为每个地址偏移给定的空间大小。minmax由分支缩短过程设置,分别存放了一个具有最小地址和最大地址的标号。详情参见rtl.def。


(prefetch:m addr rw locality)
表示对地址为addr的内存进行预取。如果预取的数据将被写,则操作数为rw,否则为0;不支持写预取的目标机,应该将其作为一个普通的预取。操作数locality描述了时间局部性的数量;如果没有,则为0,否则按照时间局部性的递增级别,依次为1,2或者3;不支持局部性暗示的目标机,应该忽略该项。

该insn用于最小化cache-miss的延迟,通过在访问数据之前将其移送到cache中。其应该只用于非故障的数据预取指令。