Next: Edges, Up: Control Flow
基本块是一段直线性的代码序列,并且只有一个入口和一个出口。
在GCC中,基本块使用basic_block
数据类型来表示。
结构体basic_block
的两个指针成员,指针next_bb
和prev_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_PRT
和EXIT_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); } }