Next: , Previous: Basic Blocks, Up: Control Flow


15.2 边

边表示从某个基本块A的结束到另一个基本块B的开头的可能的控制流转换。 我们称A是B的前驱,B是A的后继。在GCC中,边由edge数据类型表示。 每个edge作为两个基本块之间的链接: 一个edgesrc成员指向前驱dest基本块。 数据类型basic_block的成员predssuccs, 指向块的前驱和后继们的边的type-safe向量。

当在一个边向量中访问边时,应该使用边迭代器。 边迭代器由edge_iterator数据结构和一些可以使用的操作方法构成:

ei_start
该函数初始化一个指向边向量中第一个边的edge_iterator
ei_last
该函数初始化一个指向边向量中最后一个边的edge_iterator
ei_end_p
如果edge_iterator表示边向量中的最后一个边,则该断言为true
ei_one_before_end_p
如果edge_iterator表示边向量中的倒数第二个边,则该断言为true
ei_next
该函数接受一个指向edge_iterator的指针,并使其指向序列中的下一个边。
ei_prev
该函数接受一个指向edge_iterator的指针,并使其指向序列中的上一个边。
ei_edge
该函数返回由edge_iterator当前指向的edge
ei_safe_safe
该函数返回由edge_iterator当前指向的edge, 但是如果迭代器指向序列的结尾时,则返回NULL。 该函数是为现有的代码提供的,即代码假设用NULL边来表示序列的结尾。

FOR_EACH_EDGE可以方便的用来访问前驱边或后继边序列。 当在遍历中会移除元素时,不要使用该宏,否则会错过这些元素。 这里有一个如何使用该宏的例子:

     edge e;
     edge_iterator ei;
     
     FOR_EACH_EDGE (e, ei, bb->succs)
       {
          if (e->flags & EDGE_FALLTHRU)
            break;
       }

有许多原因会导致控制流从一个块传递到另一个。 一种可能是某条指令,例如CODE_LABEL,在一个线形的指令流中, 总是起始一个新基本块。在这种情况下, 一个fall-thru边将基本块与随后的第一个比本块相连。 但是有许多其它原因会导致边被创建。 edge的数据类型的flags域用于存储我们处理的边的类型信息。 每个边都具有下列类型之一:

jump
与跳转指令相关的边没有被设置类型标识。这些边用于无条件或有条件跳转, 以及RTL中还有表跳转。它们是最容易操作的,因为当流图不为SSA形式的时候, 可以自由重定向。
fall-thru
这些边的标志设为EDGE_FALLTHRU。不像其它类型的边, 这些边必须直接进入基本块的指令流中。 函数force_nonfallthru可以用于在需要重定向时插入一个无条件跳转。 注意这可能需要创建一个新基本块。
exception handling
异常处理边表示可能的控制转移,从一个陷门指令到一个异常处理器。 关于“trapping”定义不尽相同。在C++中,只有函数调用能够抛出异常, 但是对于Java,像除0或者段错误都被定义为异常, 并且因此每条指令都可能抛出这种需要处理的异常。 异常边设置了EDGE_ABNORMALEDGE_EH标识。

当更新指令流时,能够容易的将可能trapping的指令转换成non-traaping, 通过简单的将异常边移除。相反的转换比较困难,但是是不会发生的。 可以通过调用purge_dead_edges来消除边。

在RTL表示中,异常边的目的地由附加在insn上的注解REG_EH_REGION来指定。 在trapping调用的情况下,还设置了EDGE_ABNORMAL_CALL标识。 在tree表示中,该额外的标识没有被设置。

在RTL表示中,断言may_trap_p可以用来检测指令是否还可能trap。 对于tree表示,可以用tree_could_trap_p, 不过该断言只检测可能的内存trap,像在废除一个无效的指针地址。

sibling calls
兄弟调用或者尾调用以非标准的方式终止函数, 并且因此必须存在一个引向出口的边。 EDGE_SIBCALLEDGE_ABNORMAL在这种情况下被设置。 这些边只存在于RTL表示中。
computed jumps
计算跳转包含了引向函数中代码引用的所有标号的边。 所有这些边都设置了EDGE_ABNORMAL标识。 用来表示计算跳转的边通常会造成编译时间性能问题, 因为函数有许多标号组成,许多计算跳转可能具有密集的流图, 所以这些边需要特别仔细的处理。在编译过程的早期阶段, GCC尝试避免这样的密集流图,通过因子化计算跳转。 例如,给定下列跳转,
            goto *x;
            [ ... ]
          
            goto *x;
            [ ... ]
          
            goto *x;
            [ ... ]

将计算跳转提取公因子,会产生具有比较简单流图的代码序列:

            goto y;
            [ ... ]
          
            goto y;
            [ ... ]
          
            goto y;
            [ ... ]
          
          y:
            goto *x;

但是,这种转换的典型问题是产生的结果代码具有运行时代价: 一个额外的跳转。因此计算跳转在编译器之后的过程里被un-factored。 当你工作于这些过程上时,需要注意。曾有许多已存的例子, 即对未公因子化的计算跳转编译时造成的头痛之事。

nonlocal goto handlers
GCC允许嵌套函数使用goto到一个通过参数传给被调用者的标号的方式来返回到调用者那里。 传给嵌套函数的标号包含了特定的代码用来在函数调用之后进行清理工作。 这段代码被称为“nonlocal goto receivers”。 如果一个函数包含这样的非局部goto接受者,一个从调用到标号的边被创建, 并设置了EDGE_ABNORMALEDGE_ABNORMAL_CALL标识。
function entry points
根据定义,函数执行起始于基本块0, 所以总有一个边从ENTRY_BLOCK_PTR到基本块0。 目前,对备用入口点没有tree表示。在RTL里, 备用入口点通过定义了LABEL_ALTERNATE_NAMECODE_LABEL指定。 这能够被后端用于为通过不同上下文调用函数而生成备用prologues。 将来,Fortran90定义的多入口函数的完全支持需要被实现。
function exits
在pre-reload表示中,函数终止于insn链中的最后一条指令, 并且没有显示的返回指令。这对应于由fall-thru引向出口块。 reload之后,最佳的RTL epilogues被用于显示的(有条件的)返回指令中。