Next: Liveness information, Previous: Profile information, Up: Control Flow
每个编译器过程都具有的一个重要任务是保持控制流图和所有profile信息更新。 在每个过程之后都重建控制流图是不可能的,因为这样代价会很高, 而且丢失的profile信息是根本无法重建的。
GCC有两个主要的中间表示,
并且它们都使用basic_block
和edge
数据类型来表示控制流。
两种表示都尽可能多的共享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_HEAD
和BB_END
可以用来获得基本块的
起始rtx
和结束rtx
。没有抽象迭代器被定义用来遍历insn链,
不过可以使用NEXT_INSN
和PREV_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表示。