Next: Calls, Previous: Debug Information, Up: RTL
一个函数的代码的RTL表示是一个被称作insns对象的双向链表。insn只不过是具有特定代码的表达式。有些insn是实际的指令;有些用来表示switch
语句的派遣表。有些用来表示要调转的标号或者不同类别的声明信息。
除了本身特定的数据,每个insn必须有一个唯一的id号用来区别当前函数中其它的insn(经过分支延迟调度之后,具有相同id号的一个insn 的拷贝,可能会出现在一个函数中的多个地方,但是这些拷贝总是同样的,并且只是出现在一个sequence
中),以及指向前面和后面insn的链表指针。这三个域在每个insn中占有相同的位置,并且独立于insn的表达式代码。它们可以通过XEXP
和XINT
来访问,不过,有三个特定的宏经常会被使用:
INSN_UID (
i)
PREV_INSN (
i)
NEXT_INSN (
i)
链表中的第一个insn可以通过调用get_insns
获得;最后一个insn可以通过调用get_last_insn
来获得。在由这些insn界定的链中,NEXT_INSN
和PREV_INSN
指针必须总是相当:如果insn 不是第一个insn,则
NEXT_INSN (PREV_INSN (insn)) == insn
总是真,并且如果insn不是最后一个insn,则
PREV_INSN (NEXT_INSN (insn)) == insn
总是真。
在延迟槽调度之后,在链中的一些insn可能为sequence
表达式,其包含了一个insn向量。这个向量中除了最后一个insn之外,其它insn的NEXT_INSN
的值都是向量中的下一个insn;向量中的最后一个insn的NEXT_INSN
的值,等于包含sequence
的insn的NEXT_INSN
的值。对于PREV_INSN
,也有类似的规则。
这意味着上面的恒等式,对于在sequence
表达式中的insn不需要成立。特别是,如果insn为sequence
中的第一个insn,则NEXT_INSN (PREV_INSN (
insn))
为包含sequence
表达式的insn,同样如果insn为sequence
中的最后一个insn,则PREV_INSN (NEXT_INSN (
insn))
的值也是如此。你可以使用这些表达式来查找包含sequence
的insn。
insn
insn
用于不进行跳转和函数调用的指令。sequence
表达式总是包含在表达式代码为insn
的insn中,即使它们中的一个insn是跳转或者函数调用。
表达式代码为insn
的insn,除了上面列出的三个必须的域以外,还具有四个额外的域。这四个域在后面的表中有描述。
jump_insn
jump_insn
用于可能执行跳转(或者,更一般的讲,指令中可能包含了label_ref
表达式,并用其来设置pc
)的指令。如果有一条从当前函数返回的指令,则其被记录为jump_insn
。
jump_insn
具有跟insn
相同的额外的域,并使用同样的方式来访问,除此之外,还包含了一个域JUMP_LABEL
,其当执行完跳转优化后被定义。
对于简单的条件跳转和无条件跳转,该域包含了该insn将(可能有条件的)分支跳转到的code_label
。在更复杂的跳转中,JUMP_LABEL
记录了insn引用的其中一个标号;其它跳转目标标号作为REG_LABEL_TARGET
注解来记录。addr_vec
和addr_diff_vec
是例外的情况,对此,JUMP_LABEL
为NULL_RTX
,而只有扫描整个insn体干才能找到标号。
返回指令insn作为跳转看待,但由于它们并不引用任何标号,所以它们的JUMP_LABEL
为NULL_RTX
。
call_insn
call_insn
用于可能执行函数调用的指令。区分这些指令是很重要的,因为它们意味着特定的寄存器和内存位置可以被不可预知的方式改变。
call_insn
具有与insn
相同的额外的域,并使用相同的方式访问,除此之外,还包含一个域CALL_INSN_FUNCTION_USAGE
,其包含了一个列表(expr_list
表达式链),包含了use
和clobber
表达式,表示了被调用函数使用和破坏的硬件寄存器和MEM
。
一个MEM
通常指向一个栈槽,参数在其中按照引用方式(参见TARGET_PASS_BY_REFERENCE)传递给libcall。如果参数是caller-copied(参见TARGET_CALLEE_COPIES),则栈槽会在CLOBBER
和USE
中被提到;如果是callee-copied,则只会出现USE
,并且MEM
可能指向不是栈槽的地址。
在列表中,被CLOBBER
的寄存器,增加了在CALL_USED_REGISTERS
中描述的寄存器(参见Register Basics)。
code_label
code_label
insn表示一个跳转insn可以跳转到的标号。除了三个标准的域以为,其还包含两个特定的域。CODE_LABEL_NUMBER
用于存放label number,在编译过程中,唯一标识该标号。最终,标号在汇编输出中作为汇编标号来表示,通常的形式为‘Ln’,其中n为标号编号。
当code_label
出现在RTL表达式中,其通常出现在label_ref
中,其表示了标号的地址,为一个编号。
除了作为code_label
以外,标号还可以作为类型为NOTE_INSN_DELETED_LABEL
的note
来表示。
域LABEL_NUSES
只当完成跳转优化过程后才被定义。其包含了在当前函数中,该标号被引用的次数。
域LABEL_KIND
用来区分四种不同类型的标号:LABEL_NORMAL
,LABEL_STATIC_ENTRY
,LABEL_GLOBAL_ENTRY
和LABEL_WEAK_ENTRY
。唯一不具有类型LABEL_NORMAL
的标号,为当前函数的alternate entry points。这些可以为static(只在当前转换单元中可见),global(对所有的转换单元可见)或者weak(全局的,但是可以被另一个具有相同名字的符号覆盖)。
编译器大多将所有四种标号同等对待。有些地方需要知道标号是否为候选入口点;为此,提供了宏LABEL_ALT_ENTRY_P
。其等价于测试是否‘LABEL_KIND (label) == LABEL_NORMAL’。除了前端创建static,global和weak alternate entry points的代码以外,其它唯一关心它们的区别的地方是final.c文件中的函数output_alternate_entry_point
。
使用宏SET_LABEL_KIND
来设置标号的种类。
barrier
volatile
函数的调用之后,表示不会返回(例如,exit
)。除了三个标准的域以外,不包含其它信息。
note
note
insns用于表示额外的调试和说明信息。它们包含两个非标准的域,一个使用宏NOTE_LINE_NUMBER
访问的整数,以及一个使用NOTE_SOURCE_FILE
访问的字符串。
如果NOTE_LINE_NUMBER
是正的,则注解表示源文件行号,并且NOTE_SOURCE_FILE
为源文件名。这些注解控制在汇编输出中的生成行号数据。
否则,NOTE_LINE_NUMBER
不是一个行号,而是一个具有下列值之一的代码(并且NOTE_SOURCE_FILE
必须包含一个空指针):
NOTE_INSN_DELETED
NOTE_INSN_DELETED_LABEL
code_label
,但现在只用于获得其地址,并且没有代码会跳转到这里。
NOTE_INSN_BLOCK_BEG
NOTE_INSN_BLOCK_END
NOTE_INSN_EH_REGION_BEG
NOTE_INSN_EH_REGION_END
NOTE_BLOCK_NUMBER
标识了哪一个类型为NOTE_INSN_DELETED_LABEL
的CODE_LABEL
或note
与给定的区域相关联。
NOTE_INSN_LOOP_BEG
NOTE_INSN_LOOP_END
while
或者for
循环的起始和结束。它们使得循环优化可以快速的发现循环。
NOTE_INSN_LOOP_CONT
continue
语句跳转的地方。
NOTE_INSN_LOOP_VTOP
NOTE_INSN_FUNCTION_BEG
NOTE_INSN_VAR_LOCATION
在调试转储中,这些代码被符号化的打印。
debug_insn
debug_insn
用于存放赋值中变量跟踪(variable tracking)调试信息的伪指令(参见-fvar-tracking-assignments选项)。它们是GIMPLE_DEBUG
语句的RTL表示(GIMPLE_DEBUG
),使用VAR_LOCATION
操作数将用户变量tree与相应语句中value
的RTL表示绑定在一起。其中的DEBUG_EXPR
表示绑定到相应DEBUG_EXPR_DECL
的值。
在整个优化passes中,绑定信息是按照伪指令的形式进行保存,所以,不像注解(notes),它与常规指令具有相同的待遇。变量跟踪pass会将这些伪指令转换为变量位置注解,会分析控制流,分析值等价信息和值表达式中引用的寄存器和内存的变化,传播调试临时变量的值,并确定可以用来计算程序中尽可能多的点(实际上是域)上的每个用户变量的值的表达式。
不像NOTE_INSN_VAR_LOCATION
,INSN_VAR_LOCATION
中的值表达式是表示程序中指定点的值,不可以在该点之后,VAR_LOCATION
之前的任意点上进行求值。例如,如果一个用户变量绑定到一个REG
,然后后续的insn修改了REG
,则注解位置会继续将用户变量映射到寄存器,而insn位置则会继续将变量绑定到值上,所以,变量跟踪pass会为变量产成另一个位置注解,指定寄存器被修改的点。
insn的机器模式通常为VOIDmode
,但有些阶段出于不同的目的而使用其它机器模式。
公共子表达式消除过程将一个insn的机器模式设为QImode
,当其为已经被处理过的块中的第一个insn时。
第二次Haifa调度过程中,对于可以多发射的目标机,当insn被认为是一个发射组合中的起始指令时,将其机器模式设为TImode
。也就是说,该指令不能和之前的指令同时发射。这可以在后面的过程中用到,特别是机器特定的reorg。
下面的表中列出了insn
, jump_insn
和call_insn
的其它域:
PATTERN (
i)
set
, call
, use
, clobber
, return
, asm_input
, asm_output
, addr_vec
, addr_diff_vec
, trap_if
, unspec
, unspec_volatile
, parallel
, cond_exec
或sequence
。如果其为parallel
,则parallel
中的每个元素必须是这些代码中的一个,并且,parallel
表达式不能被嵌套,addr_vec
和addr_diff_vec
不允许在parallel
表达式中。
INSN_CODE (
i)
对于指令模式由单个use
, clobber
, asm_input
, addr_vec
或 addr_diff_vec
表达式组成的insn,则不会进行这样的匹配,并且该域保持为−1。
对于来自asm
语句的insn,也不会进行指令模式匹配。这些至少包含了一个asm_operands
表达式。函数asm_noperands
为这样的insn返回一个非负的值。
在调试输出中,该域被打印成一个数字,紧随一个符号表示,用来定位在md中的指令模式,数字表示相对命名指令模式的正的或者负的偏移量。
LOG_LINKS (
i)
insn_list
表达式链),给出了基本块中指令之间的依赖信息。相关联的insn之间不会有跳转或者标号。这些只被用于指令调度和组合。这是一个不被推荐的数据结构。现在推荐使用def-use和use-def链。
REG_NOTES (
i)
expr_list
和insn_list
表达式链),给出了insn的其它信息。通常为从属于该insn使用的寄存器的信息。
insn的LOG_LINKS
域为insn_list
表达式链。每一个都具有两个操作数:第一个为insn,第二个为另一个insn_list
表达式(链中的下一个)。链中的最后一个insn_list
的第二个操作数为空指针。对于表达式链,重要的是有哪些insn(insn_list
表达式的第一个操作数)。它们的顺序并不重要。
该列表最初由流分析过程建立;在此之前还只是空指针。流分析只将那些可以用于指令合并的数据依赖,加入到列表中。
insn的REG_NOTES
域是一个类似于LOG_LINKS
域的链,不过除了insn_list
表达式,其还包含expr_list
表达式。有多种寄存器注解,其通过机器模式区分。注解的第一个操作数op的含义依赖注解的种类。
宏REG_NOTE_KIND (
x)
返回寄存器注解的种类。宏PUT_REG_NOTE_KIND (
x,
newkind)
将x的寄存器注解类型设置为newkind。
寄存器注解有三种类别:可以用来说明insn的输入,可以用来说明insn的输出,或者可以用来创建两个insn之间的连接。还有一个值集,只用于LOG_LINKS
中。
REG_DEAD
这并不是说从该insn之后,寄存器op就没有有用的值了。而是说,后续的指令不会用到op的内容。
REG_UNUSED
REG_DEAD
注解不同,后者表示输入中的值将不会被后续insn使用。这两个注解是不相关的;可能会都出现在同一个寄存器中。
REG_INC
post_inc
, pre_inc
, post_dec
或pre_dec
表达式中。
REG_NONNEG
REG_NONNEG
注解,只有当机器描述具有‘decrement_and_branch_until_zero’指令模式的时候,才被加到insn中。
REG_LABEL_OPERAND
NOTE_INSN_DELETED_LABEL
的code_label
或者note
,但是不为jump_insn
。或者,其为一个将操作数作为普通操作数的jump_insn
。标号最终也可以为跳转目标,但这是在后续insn的间接跳转中。该注解使得跳转优化知道op实际上被使用了,从而流优化可以创建一个精确的流图。
REG_LABEL_TARGET
jump_insn
,但不是addr_vec
和addr_diff_vec
。其使用op,一个code_label
,作为直接或间接跳转的目标。其用途与REG_LABEL_OPERAND
类似。该注解只存在于当insn具有多个目标的时候;insn中的最后一个标号(在最高编号的insn域中),放到JUMP_LABEL
域中,并且没有REG_LABEL_TARGET
。参见JUMP_LABEL.
REG_CROSSING_JUMP
REG_SETJMP
setjmp
或者相关的函数的CALL_INSN
上。
REG_EQUIV
REG_EQUAL
set
的输出为一个strict_low_part
表达式,则注解是用于subreg
表达式SUBREG_REG
所包含的寄存器。
对于REG_EQUIV
,在整个函数中,寄存器都等价于op,并且可以在其所有出现的地方被op有效替换。(有效,这里是指程序的数据流;简单的替换可能会使得某些insn无效。)例如,当一个常量被加载到一个寄存器中,并且寄存器不再被赋予任何其它值,则会使用这种注解。
当在函数入口处,一个参数被复制到一个伪寄存器中时,这种的注解会用来记录该寄存器等价于传递参数的栈槽。虽然,这种情况下,寄存器可能被其它的insn设置,其也可以在整个函数中被栈槽来替换。
REG_EQUIV
注解还用于,在函数入口处,将一个寄存器参数复制到一个伪寄存器中的指令,如果存在一个参数本来应该被存放的栈槽。虽然其它insn可以设置该伪寄存器,但编译器还是可以在整个函数中,使用栈槽来替换伪寄存器,假设编译器可以确保栈槽被适当的初始化。这被用于调用约定为寄存器参数分配栈空间的机器上。参见Stack Arguments中的REG_PARM_STACK_SPACE
。
对于REG_EQUAL
的情况,被该insn设置的寄存器,将在运行时,在该insn的结尾处,但不必要是函数的其它地方,等价与op。这种情况下,op通常为一个算术表达式。例如,当一个库调用的insn序列,被用在一个算术运算上,则该类的注解将被附加在产生或者复制最终值的insn上。
这两个注解在编译器过程中,按照不同的方法来使用。REG_EQUAL
用于寄存器分配之前的过程中(例如公共子表达式消除和循环优化),来告诉它们如何考虑那个值。REG_EQUIV
注解用于寄存器分配,来表示存在一个可用的替换表达式(为栈上一个参数位置的常量或者mem
表达式),其可以用在没有足够寄存器的地方。
除了为参数提供地方的栈以外,其它所有等值最初都是通过附加一个REG_EQUAL
注解来表示。在寄存器分配的早期阶段,如果op是一个常量并且insn只表示对其目的寄存器进行设置,则REG_EQUAL
被改变成REG_EQUIV
注解。
因此,寄存器分配之前的编译过程,只需要检查REG_EQUAL
注解,而之后的编译过程只需要检查REG_EQUIV
注解。
这些注解描述了insn之间的联系。它们成对的出现:一个insn具有一对注解,其中之一用来指向第二个insn,并且第二个insn也由一个反过来指向第一个insn的注解。
REG_CC_SETTER
REG_CC_USER
cc0
的机器上,设置和使用cc0
的insns是相邻的。然而,当做完分支延迟槽填充之后,就不一定是这样的了。这种情况下,REG_CC_USER
注解将被放在设置cc0
的insn上,来指向使用cc0
的insn,并且REG_CC_SETTER
注解将被放在使用cc0
的insn上,来指向设置cc0
的insn。
这些值只用在LOG_LINKS
域,用来表示每个链接表示的依赖类型。表示一个数据依赖(写后读依赖)的链接,不使用任何代码,它们只是简单的具有VOIDmode
模式,并在打印输出中没有任何描述文本。
这些注解描述了从gcov profile数据中搜集的信息。它们作为expr_list
存储在insn的REG_NOTES
域中。
REG_BR_PROB
REG_BR_PRED
REG_FRAME_RELATED_EXPR
为方便起见,在insn_list
或者expr_list
中的机器模式,在调试转储中使用这些符号化的代码来打印。
表达式代码insn_list
和expr_list
之间的唯一区别是,insn_list
的第一个操作数被假设为一个insn,并在调试转储中作为insn的唯一id来打印;而expr_list
的第一个操作数作为表达式,按照普通的方式来打印。