GCC源码分析(三)——中间语言

原文链接: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按类别分为若干类:

  1. enum tree_code_class {  
  2.   tcc_exceptional, /* An exceptional code (fits no category).  */  
  3.   tcc_constant,    /* A constant.  */  
  4.   /* Order of tcc_type and tcc_declaration is important.  */  
  5.   tcc_type,        /* A type object code.  */  
  6.   tcc_declaration, /* A declaration (also serving as variable refs).  */  
  7.   tcc_reference,   /* A reference to storage.  */  
  8.   tcc_comparison,  /* A comparison expression.  */  
  9.   tcc_unary,       /* A unary arithmetic expression.  */  
  10.   tcc_binary,      /* A binary arithmetic expression.  */  
  11.   tcc_statement,   /* A statement expression, which have side effects 
  12.               but usually no interesting value.  */  
  13.   tcc_vl_exp,      /* A function call or other expression with a 
  14.               variable-length operand vector.  */  
  15.   tcc_expression   /* Any other expression.  */  
  16. };  

每个TREE除了有类别,还有自己的类型:

  1. enum tree_code {  
  2. #include "all-tree.def"  
  3. MAX_TREE_CODES  
  4. };  

这个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里,比如常用的基本块的遍历:

  1. /* For iterating over basic blocks.  */  
  2. #define FOR_BB_BETWEEN(BB, FROM, TO, DIR) \  
  3.   for (BB = FROM; BB != TO; BB = BB->DIR)  
  4.   
  5. #define FOR_EACH_BB_FN(BB, FN) \  
  6.   FOR_BB_BETWEEN (BB, (FN)->cfg->x_entry_block_ptr->next_bb, (FN)->cfg->x_exit_block_ptr, next_bb) // for循环遍历链表。  
  7.   
  8. #define FOR_EACH_BB(BB) FOR_EACH_BB_FN (BB, cfun) // cfun就是current_function_decl,是一个TREE  
basic block在控制流中以链表的形式存放,它们由edge组成逻辑意义上的图。gcc提供了对每个基本块相关的边进行遍历的宏:

  1. #define FOR_EACH_EDGE(EDGE,ITER,EDGE_VEC)   \ // 前两个参数的类型分别是edge和edge_iterator,是出参  
  2.   for ((ITER) = ei_start ((EDGE_VEC));      \ // 最后一个是入参,要么是bb->preds(入边集合),要么是bb->succs(出边集合)  
  3.        ei_cond ((ITER), &(EDGE));       \  
  4.        ei_next (&(ITER)))  
每个edge有flags标志位,用来判别边的类型,它决定了跳转的方式(条件、无条件等等)

四、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:

  1. /* This must also call cgraph_finalize_compilation_unit.  */  
  2. 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优化的框架将在下一篇介绍。


猜你喜欢

转载自blog.csdn.net/doniexun/article/details/38324577
今日推荐