Next: , Previous: Profile information, Up: Control Flow


15.4 维护CFG

每个编译器过程都具有的一个重要任务是保持控制流图和所有profile信息更新。 在每个过程之后都重建控制流图是不可能的,因为这样代价会很高, 而且丢失的profile信息是根本无法重建的。

GCC有两个主要的中间表示, 并且它们都使用basic_blockedge数据类型来表示控制流。 两种表示都尽可能多的共享CFG维护的代码。对于每一种表示, 都定义了一套hooks,以便于需要的时候可以提供自己的CFG维护函数的实现。 这些钩子定义在cfghooks.h中。这些钩子提供了几乎所有普通的CFG操作, 包括块分割和合并,边重定向,以及创建和删除基本块。 这些钩子应该提供所有需要的维护和操作RTL和tree表示下的CFG。

目前,基本块的边界在修改指令时会被透明的维护,因此很少需要手动移动它们 (比如当有人想要显式的输出基本块外面的指令的时候)。 将CFG看作指令链的组成部分,比看作建立在之上的结构,往往要更好些。 但是原则上,对于树表示的控制流图并不是数表示的必须部分。 函数树可以在不需要首先创建树表示的流图的情况下就被扩展。 这种情况在没有进行任何树优化的编译时会发生。当进行树优化时, 并且指令流被重写为SSA形式,CFG就和指令流非常紧密的联系起来了。 特别在语句插入和移除时要注意。实际上,如果没有同时对CFG进行恰当的维护, 整个树表示就很难使用和维护。

在RTL表示里,每条指令有一个BLOCK_FOR_INSN值用来表示指向包含该指令的基本块。 在tree表示里,函数bb_for_stmt返回一个指向包含所查询语句的基本块。

tree表示里,当需要对函数进行改动时, 应该使用块语句迭代器(block statement iterators)。 这些迭代器提供了流程图和指令流的整体抽象。 块语句迭代器由block_stmt_iterator数据结构和一些修改函数构成, 包括下面的:

bsi_start
该函数初始化一个block_stmt_iterator,使其指向基本块中第一条非空语句。
bsi_last
该函数初始化一个block_stmt_iterator,使其指向基本块中最后一条语句。
bsi_end_p
如果block_stmt_iterator表示基本块的结束,则为true
bsi_next
该函数接受一个block_stmt_iterator,并使其指向它的后继。
bsi_prev
该函数接受一个block_stmt_iterator,并使其指向它的前驱。
bsi_insert_after
该函数在block_stmt_iterator所在位置之后插入一条语句。 最后一个参数决定是否将语句迭代器更新指向新插入的语句, 还是保留指向原来的语句。
bsi_insert_before
该函数在block_stmt_iterator所在位置之前插入一条语句。 最后一个参数决定是否将语句迭代器更新指向新插入的语句, 还是保留指向原来的语句。
bsi_remove
该函数移除block_stmt_iterator所在位置的语句, 并且如果基本块中还有语句,则将剩余的语句重新链接。

在RTL表示里,宏BB_HEADBB_END可以用来获得基本块的 起始rtx和结束rtx。没有抽象迭代器被定义用来遍历insn链, 不过可以使用NEXT_INSNPREV_INSN替代。参见 Insns

通常一个代码操作过程将会简化指令流和控制流,也可能消除一些边。 例如当一个条件跳转被替换为非条件跳转,甚至在编译java时, 将可能的trapping指令简化为non-trapping。边的更新是不透明的, 每个优化过程都要求手动进行。不过,实际中这种情况很少发生。如果存在的话, 过程可以针对给定的基本块调用purge_dead_edges来移除多余的边。

另一个常见的情景是分支指令的重定向。 不过由于可以非常好的建模为控制流图里的边重定向, 因此应尽量使用redirect_edge_and_branch,而不是其它底层函数, 例如只是操作RTL链的redirect_jump。 定义在cfghooks.h中的CFG钩子应该提供了操作和维护CFG所需要的全部API。

有时候,一个过程可能不得不要向基本块的中间插入控制流指令,这样的话, 就在基本块中间产生一个入口点。根据定义,这是不可能的, 因此必须要将块分开以确保只含有一个入口点,也就是基本块的头。 当基本块中间的指令必须成为跳转或分支指令的目标时, 可以使用CFG钩子split_block

对一个全局优化,一个常用的操作是在流图中将边拆分,并插入指令。 在RTL表示里,可以很容易的实现, 通过使用insert_insn_on_edge函数来生成一条暂存的“on the edge”指令, 以便之后的commit_edge_insertions调用来将插入的指令从边上移到基本块的指令流里。 如果需要的话,还会生成新的基本块。在tree表示里, 等价的函数为bsi_insert_on_edge,用来在边上插入一个块语句迭代器, 以及bsi_commit_edge_inserts,将指令挪到实际的指令流里。

在调试优化过程时, 函数verify_flow_info可能有助于发现在控制流图的更新代码中的bug。

注意,目前在由树的表示扩展到RTL时,控制流的表示会被丢弃。 长远的看,CFG应给被维持并随着函数树本身被扩展到RTL表示。