原文链接:http://blog.csdn.net/sonicling/article/details/7915301
一、前言
很忙,很久没更新博客了,继续没写完的gcc分析,争取在传说将要用C++重写的gcc 5出来之前初略分析完。
二、符号表(GENERIC)
前篇介绍了gcc的语法分析,在语法分析过程中,所有识别出来的语言部件都用一个叫TREE的变量保存着。这个TREE就是gcc语法树,叫做GENERIC。实际上它也是gcc的符号表,因为变量名、类型等等这些信息都由TREE关联起来。
GENERIC的节点都定义在gcc/tree.h头文件里。它将GENERIC按类别分为若干类:
- enum tree_code_class {
- tcc_exceptional, /* An exceptional code (fits no category). */
- tcc_constant, /* A constant. */
- /* Order of tcc_type and tcc_declaration is important. */
- tcc_type, /* A type object code. */
- tcc_declaration, /* A declaration (also serving as variable refs). */
- tcc_reference, /* A reference to storage. */
- tcc_comparison, /* A comparison expression. */
- tcc_unary, /* A unary arithmetic expression. */
- tcc_binary, /* A binary arithmetic expression. */
- tcc_statement, /* A statement expression, which have side effects
- but usually no interesting value. */
- tcc_vl_exp, /* A function call or other expression with a
- variable-length operand vector. */
- tcc_expression /* Any other expression. */
- };
每个TREE除了有类别,还有自己的类型:
- enum tree_code {
- #include "all-tree.def"
- MAX_TREE_CODES
- };
这个all-tree.def是编译期自动生成的文件,主要来源于tree.def文件,还包含一些其它语言特定的TREE类型。每个TREE变量代表一个节点。
每个TREE变量 t 都可以通过 TREE_CODE_CLASS(t) 宏获取类别,或者通过TREE_CODE(t) 宏获取类型,由此知道这个TREE是指的啥。gcc/tree.h里定义了绝大多数对TREE的操作(宏和函数),比如获取某个TREE的类型:TREE_TYPE(t),通过它可以获取函数的原型、原型的返回值、指针或数组的类型等等;还有const char *get_name(TREE t),获取TREE的名字,如果这个TREE代表一个变量,那么它就返回变量名。具体每种节点类型具有哪些属性可以去查GCC Internals。
三、控制流图(Control Flow Graph)
每个函数翻译为GENERIC的语法树之后,会进行gimplification(gimple化,gimple在下节介绍),在这一过程中函数的语法树被翻译为了控制流图的形式。每个函数对应一个控制流图。
控制流由基本块(Basic Block)组成。每个基本块具有一串指令序列,并且只能有一个入口和一个出口,因此在这个序列内部不允许存在跳转。gcc对基本块的操作主要定义在gcc/basic-block.h里,比如常用的基本块的遍历:
- /* For iterating over basic blocks. */
- #define FOR_BB_BETWEEN(BB, FROM, TO, DIR) \
- for (BB = FROM; BB != TO; BB = BB->DIR)
- #define FOR_EACH_BB_FN(BB, FN) \
- FOR_BB_BETWEEN (BB, (FN)->cfg->x_entry_block_ptr->next_bb, (FN)->cfg->x_exit_block_ptr, next_bb) // for循环遍历链表。
- #define FOR_EACH_BB(BB) FOR_EACH_BB_FN (BB, cfun) // cfun就是current_function_decl,是一个TREE
- #define FOR_EACH_EDGE(EDGE,ITER,EDGE_VEC) \ // 前两个参数的类型分别是edge和edge_iterator,是出参
- for ((ITER) = ei_start ((EDGE_VEC)); \ // 最后一个是入参,要么是bb->preds(入边集合),要么是bb->succs(出边集合)
- ei_cond ((ITER), &(EDGE)); \
- ei_next (&(ITER)))
四、GIMPLE和RTL
gimple和RTL是gcc用来表示指令的两种形式。因此每个基本块都包含有两组指令序列,一组是gimple指令,一组是RTL指令。每个函数将首先被gimple化,此时基本块里只包含gimple指令,之后由gimple生成RTL。
gimple是一种包含最多三个操作数的中间指令,也就是编译原理里讲的四元码(三个操作数,一个操作符),基本上也就是 dst = src1 @ src2 的这种形式。由于gimple最多只能对两个操作数进行计算,因此一个复杂的表达式会展开为一系列的gimple指令,这一过程就是gimple化。gimple化的代码实现在gcc/gimplify.c中,核心的思想就是对语法树进行后序遍历,对每个非叶子节点生成一条gimple指令,自动生成必要的中间变量,并正确识别出基本块,从而生成完整的控制流。
从源码来看,语法分析中,每分析完一个函数,就会调用finish_function,它又会调用cgraph_finalize_function将函数添加到cgraph里,只有这个函数被调用才会继续处理它。分析整个文件后,compile_file()函数会调用一个hook:
- /* This must also call cgraph_finalize_compilation_unit. */
- lang_hooks.decls.final_write_globals ();
这个hook实际上是write_global_declarations() (in gcc/langhooks.c),它会调用注释中提到的 cgraph_finalize_compilation_unit() 函数,接下来就是这样的调用关系:
write_global_declarations()
cgraph_finalize_compilation_unit()
cgraph_analyze_function()
gimplify_function_tree() -> gimplification。
cgraph_lower_function() -> lowering
cgraph_optimize() -> 优化
在所有针对gimple的优化完成后,有一个叫做pass_expand的步骤,它将gimple展开为RTL。RTL是一种相对底层的指令,如果说gimple的重点在于控制流和数据流这种逻辑结构的话,那么RTL的重点就在数据和控制的精确描述。通过RTL可以将操作数的长度、对齐、操作的类型、副作用等信息表述出来,从而有利于自动化地进行最后的指令生成。
RTL的指令在gcc中称之为insn,insn是有语法和语义的,它被gcc的生成工具所识别和处理,并生成对应的.c文件作为gcc的一部分一同编译到gcc的执行文件中。这部分的细节在后序篇幅中再做介绍。
五、总结
GENERIC、GIMPLE和RTL三者构成了gcc中间语言的全部,它们以GIMPLE为核心,由GENERIC承上,由RTL启下,在源文件和目标指令之间的鸿沟之上构建了一个三层的过渡。接下来,gcc的工作就是对中间语言进行平台无关优化。有关gcc优化的框架将在下一篇介绍。