Next: , Previous: Debug Information, Up: RTL


10.19 Insns

一个函数的代码的RTL表示是一个被称作insns对象的双向链表。insn只不过是具有特定代码的表达式。有些insn是实际的指令;有些用来表示switch语句的派遣表。有些用来表示要调转的标号或者不同类别的声明信息。

除了本身特定的数据,每个insn必须有一个唯一的id号用来区别当前函数中其它的insn(经过分支延迟调度之后,具有相同id号的一个insn 的拷贝,可能会出现在一个函数中的多个地方,但是这些拷贝总是同样的,并且只是出现在一个sequence中),以及指向前面和后面insn的链表指针。这三个域在每个insn中占有相同的位置,并且独立于insn的表达式代码。它们可以通过XEXPXINT来访问,不过,有三个特定的宏经常会被使用:

INSN_UID (i)
访问insn i的唯一id。


PREV_INSN (i)
访问指向i之前的insn的链表指针。如果i是第一个insn,则是一个null指针。


NEXT_INSN (i)
访问指向i之后的insn的链表指针。如果i是最后一个insn,则是一个null指针。

链表中的第一个insn可以通过调用get_insns获得;最后一个insn可以通过调用get_last_insn来获得。在由这些insn界定的链中,NEXT_INSNPREV_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不需要成立。特别是,如果insnsequence中的第一个insn,则NEXT_INSN (PREV_INSN (insn))为包含sequence表达式的insn,同样如果insnsequence中的最后一个insn,则PREV_INSN (NEXT_INSN (insn))的值也是如此。你可以使用这些表达式来查找包含sequence的insn。

每个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_vecaddr_diff_vec是例外的情况,对此,JUMP_LABELNULL_RTX,而只有扫描整个insn体干才能找到标号。

返回指令insn作为跳转看待,但由于它们并不引用任何标号,所以它们的JUMP_LABELNULL_RTX


call_insn
表达式代码call_insn用于可能执行函数调用的指令。区分这些指令是很重要的,因为它们意味着特定的寄存器和内存位置可以被不可预知的方式改变。

call_insn具有与insn相同的额外的域,并使用相同的方式访问,除此之外,还包含一个域CALL_INSN_FUNCTION_USAGE,其包含了一个列表(expr_list表达式链),包含了useclobber表达式,表示了被调用函数使用和破坏的硬件寄存器和MEM

一个MEM通常指向一个栈槽,参数在其中按照引用方式(参见TARGET_PASS_BY_REFERENCE)传递给libcall。如果参数是caller-copied(参见TARGET_CALLEE_COPIES),则栈槽会在CLOBBERUSE中被提到;如果是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_LABELnote来表示。

LABEL_NUSES只当完成跳转优化过程后才被定义。其包含了在当前函数中,该标号被引用的次数。

LABEL_KIND用来区分四种不同类型的标号:LABEL_NORMALLABEL_STATIC_ENTRYLABEL_GLOBAL_ENTRYLABEL_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
这样的注解被完全忽略掉。编译器的一些过程会通过将insn修改成这种类型的注解,来删除insn。


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_LABELCODE_LABELnote与给定的区域相关联。


NOTE_INSN_LOOP_BEG
NOTE_INSN_LOOP_END
这些类型的注解表示处于while或者for循环的起始和结束。它们使得循环优化可以快速的发现循环。


NOTE_INSN_LOOP_CONT
出现在循环中continue语句跳转的地方。


NOTE_INSN_LOOP_VTOP
该注解表示循环中退出测试(exit test)起始的地方,并且退出测试在循环中被复制。当考虑循环不变量时,该位置为循环的另一个虚拟起始点。


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_LOCATIONINSN_VAR_LOCATION中的值表达式是表示程序中指定点的值,不可以在该点之后,VAR_LOCATION之前的任意点上进行求值。例如,如果一个用户变量绑定到一个REG,然后后续的insn修改了REG,则注解位置会继续将用户变量映射到寄存器,而insn位置则会继续将变量绑定到值上,所以,变量跟踪pass会为变量产成另一个位置注解,指定寄存器被修改的点。

insn的机器模式通常为VOIDmode,但有些阶段出于不同的目的而使用其它机器模式。

公共子表达式消除过程将一个insn的机器模式设为QImode,当其为已经被处理过的块中的第一个insn时。

第二次Haifa调度过程中,对于可以多发射的目标机,当insn被认为是一个发射组合中的起始指令时,将其机器模式设为TImode。也就是说,该指令不能和之前的指令同时发射。这可以在后面的过程中用到,特别是机器特定的reorg。

下面的表中列出了insn, jump_insncall_insn的其它域:

PATTERN (i)
一个表达式,为该insn执行的副作用。必须为下列代码中的一个:set, call, use, clobber, return, asm_input, asm_output, addr_vec, addr_diff_vec, trap_if, unspec, unspec_volatile, parallel, cond_execsequence。如果其为parallel,则parallel中的每个元素必须是这些代码中的一个,并且,parallel表达式不能被嵌套,addr_vecaddr_diff_vec不允许在parallel表达式中。


INSN_CODE (i)
一个整数,说明机器描述中的哪一个指令模式匹配该insn,或者,如果还没有进行匹配,则为−1。

对于指令模式由单个use, clobber, asm_input, addr_vecaddr_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_listinsn_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中。

这些注解用来说明insn的输入:

REG_DEAD
op中的值在该insn中死掉;也就是说,紧接这个insn之后,修改该值将不会影响程序将来的行为。

这并不是说从该insn之后,寄存器op就没有有用的值了。而是说,后续的指令不会用到op的内容。


REG_UNUSED
被该insn设置的寄存器op,将不会在后续的insn中使用。这与REG_DEAD注解不同,后者表示输入中的值将不会被后续insn使用。这两个注解是不相关的;可能会都出现在同一个寄存器中。


REG_INC
寄存器op由于insn中嵌入的副作用,而被递增(或递减)。这意味着其出现在post_inc, pre_inc, post_decpre_dec表达式中。


REG_NONNEG
寄存器op在到达该insn的时候,被已知为具有一个非负的值。对于递减并分支跳转,直到为零的指令,例如m68k dbra,可以用来进行匹配。

REG_NONNEG注解,只有当机器描述具有‘decrement_and_branch_until_zero’指令模式的时候,才被加到insn中。


REG_LABEL_OPERAND
该insn使用op,一个类型为NOTE_INSN_DELETED_LABELcode_label或者note,但是不为jump_insn。或者,其为一个将操作数作为普通操作数的jump_insn。标号最终也可以为跳转目标,但这是在后续insn的间接跳转中。该注解使得跳转优化知道op实际上被使用了,从而流优化可以创建一个精确的流图。


REG_LABEL_TARGET
该insn为一个jump_insn,但不是addr_vecaddr_diff_vec。其使用op,一个code_label,作为直接或间接跳转的目标。其用途与REG_LABEL_OPERAND类似。该注解只存在于当insn具有多个目标的时候;insn中的最后一个标号(在最高编号的insn域中),放到JUMP_LABEL域中,并且没有REG_LABEL_TARGET。参见JUMP_LABEL.


REG_CROSSING_JUMP
该insn为一个分支指令(无条件跳转或者间接跳转),其穿越了热代码段和冷代码段,并可能潜在的位于可执行程序中非常远的部分。该注解用来指示其它优化,表示该分支指令不应该被折叠为简单的分支结构。其用于当优化将基本块分成热代码段和冷代码段的时候。


REG_SETJMP
附加在每个针对setjmp或者相关的函数的CALL_INSN上。

下列注解描述了有关insn的输出的属性:

REG_EQUIV
REG_EQUAL
该注解只用在只设置一个寄存器的insn上,用来表示那个寄存器在运行时等价于op;该等值的作用域根据两种类型的注解而有所不同。insn显式的复制进寄存器的值可能看起来与op不同,但它们将在运行时相等。如果单个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模式,并在打印输出中没有任何描述文本。

REG_DEP_TRUE
这表示一个真依赖(写后读依赖)。


REG_DEP_OUTPUT
这表示一个输出依赖(写后写依赖)。


REG_DEP_ANTI
这表示一个反依赖(读后写依赖)。

这些注解描述了从gcov profile数据中搜集的信息。它们作为expr_list存储在insn的REG_NOTES域中。

REG_BR_PROB
用于指定分支跳转率,根据profile数据。值位于0和REG_BR_PROB_BASE之间;较大的值表示该分支更可能会被执行。


REG_BR_PRED
这些注解在JUMP insn中,并出现在延迟分支调度之后。它们表示JUMP的方向和可能性。格式为ATTR_FLAG_*值的掩码。


REG_FRAME_RELATED_EXPR
用在RTX_FRAME_RELATED_P insn上,其附加的表达式被用在实际的insn模式上。这用于指令模式过于复杂或者产生误解的情况。

为方便起见,在insn_list或者expr_list中的机器模式,在调试转储中使用这些符号化的代码来打印。

表达式代码insn_listexpr_list之间的唯一区别是,insn_list的第一个操作数被假设为一个insn,并在调试转储中作为insn的唯一id来打印;而expr_list的第一个操作数作为表达式,按照普通的方式来打印。