Next: , Up: Control Flow


15.1 基本块

基本块是一段直线性的代码序列,并且只有一个入口和一个出口。 在GCC中,基本块使用basic_block数据类型来表示。

结构体basic_block的两个指针成员,指针next_bbprev_bb, 用来维持与底层指令流顺序相同的基本块双向链表。 基本块之间的链,由给定的操作CFG的API,通过透明的方式进行更新。 宏FOR_EACH_BB可以用来按照词典顺序(lexicographical order) 访问所有基本块。 也可以使用walk_dominator_tree,进行支配遍历(dominator traversal)。 给定两个基本块A和B,如果A总是在B之前被执行, 则基本块A支配(dominate)基本块B。

BASIC_BLOCK数组包含了所有的基本块,并且顺序不固定。 每一个basic_block结构体都有一个域, 用来保留一个唯一的整数标识符index, 作为该基本块在BASIC_BLOCK数组中的索引。 函数中基本块的总数为n_basic_blocks。 由于中间过程(passes)可以重排,创建,复制和销毁基本块, 所以基本块的索引和总数在编译过程中都可能改变。 任何基本块块的索引都不应该比last_basic_block的大。

有专门的基本块来表示一个函数的可能的入口和出口。 这些基本块被称作ENTRY_BLOCK_PRTEXIT_BLOCK_PTR。 这些基本块不包含任何代码,并且不是BASIC_BLOCK数组的成员。 因此它们被赋予了唯一的负数索引。

每个basic_block还包含了指针,用来指向基本块中第一条指令 (head)和最后一条指令(tail), 或者在基本块中包含的指令流的结尾(end)。 实际上,由于basic_block数据类型在GCC的两个主要中间表示 (tree和RTL)中都被用来表示基本块, 因此具有针对这两种表示的指针,用来指向基本块的头和尾。

对于RTL,这些指针是rtx head, end。在RTL函数表示中, 头指针总是指向NOTE_INSN_BASIC_BLOCK或者CODE_LABEL。 在RTL函数表示中,指令流不仅包含“真正”的指令, 而且还有注解(notes)。 任何移动或者复制基本块的函数都需要注意更新这些注解。 许多这些注解都期望指令流是由线性区域组成的,所以这使得更新比较困难。 NOTE_INSN_BASIC_BLOCK注解是唯一类型的, 可以出现在基本块内包含的指令流中。 一个基本块的指令流总是跟随一个NOTE_INSN_BASIC_BLOCK, 但是基本块注解之前可以有0个或多个CODE_LABEL节点。 基本块结束于一条控制流指令, 或者后面是紧随CODE_LABEL或者NOTE_INSN_BASIC_BLOCK的最后一条指令。 CODE_LABEL不能出现在基本块中的指令流里。

除了注解之外,跳转表向量也被表示为insn流中的“伪指令”。 这些向量从不出现在基本块中,并应该总是被放在引用它们的 表跳转指令(tabel jump instructions)的后面。 在移除table-jump之后,通常很难消除计算地址和引用向量的代码, 所以对这些向量的清除工作被推迟到活跃分析之后。 这样,跳转表向量可能会在insn流中出现,但未被引用,没有任何用图。 在将任何边(edge)作为fall-thru之前, 都需要调用can_fallthru函数来检查这种构造方式是否可以。

对于tree的表示,基本块的头和尾由stmt_list域指向, 但是,决不要直接引用这些特定的tree。替代的,在树级别上, 使用抽象容器和迭代器来访问基本块中的语句和表达式。 这些迭代器被称作块语句迭代器(block statement iterators, BSI)。 可以在各种tree-*文件中使用grep来查找^bsi。 下面的片段可以打印(pretty-print)使用GIMPLE表示的程序的所有语句。

     FOR_EACH_BB (bb)
       {
          block_stmt_iterator si;
     
          for (si = bsi_start (bb); !bsi_end_p (si); bsi_next (&si))
            {
               tree stmt = bsi_stmt (si);
               print_generic_stmt (stderr, stmt, 0);
            }
       }