GCC源码分析(2):从C代码到GIMPLE

* 从C代码到GIMPLE
    上一部分我们分析了gcc中的“树”,这些树是源程序在编译器内部的表示形式。本部分
试图来分析gcc是如何将源代码变成这种内部表示的。本文以C语言的翻译过程为例。
    首先,我需要找到cc1的控制流的入口点。在gcc目录下grep一下main,发现好多,但是
,根据文件名来看,main.c中的那个最值得怀疑。这个文件的第一行注释为
%{
/* main.c: defines main() for cc1, cc1plus, etc.
{%
可见,这个文件的确定义了cc1的入口控制流,从字面上看,它也定义了其它编译器的入口
控制流。目前推测,大部分编译器都使用同一份主代码生成,这份代码与不同的编译器前端
链接,生成不同的编译器二进制文件。问题是,这种二进制文件包含编译器的多少部分?其
实,每个编译器比较特殊的只有相关于源语言的部分,只要翻译到中间表示,后面的分析和
优化都是一样的了。所以,道理上讲,不会在每个编译器中都包含所有的分析和优化,以及
代码生成的部分,这些部分只要作为动态链接库,为多个编译器共享即可。
从后面的代码可以看到,这里的main函数什么也没有做,只是以相同的参数调用了
toplev_main。注释解释说,这样设计是为了支持每一个前端都可以自己定义main函数,如
果真的需要的话。
下面,我们继续看toplev_main。和它有关的两个文件是toplev.[hc]。先看看.h文件。该文
件基本上就是声明了toplev.c文件中的函数。另外,还定义了一些宏。现在,轮到toplev.c
文件了。toplev.c文件是cc1和c++的最高层的控制流。它会分析命令行参数,打开文件,调
用编译器的各种遍,并且可以记录每一遍使用的时间。错误消息和malloc的低级接口也是在
这里处理的。
在toplev.c文件中,定义了大量的用于控制编译的全局变量:
%{
/* 非0表示在分析的过程中输出调试信息(-dy) */
static int set_yydebug;
/* true表示本次编译不需要后端 */
static bool no_backend;
/* 打印各种选项的值时,每一行的宽度 */
#define MAX_LINE 75
/* 调用的程序的名字,不包括路径名,例如cc1 */
const char *progname;
/* toplev_main的参数向量的副本 */
static const char **save_argv;
/* 顶层源程序(输入给cpp的,例如hello.c)的文件名,一般来自于实际输入(hello.i)
 * 开始处的#-命令
 * 如果没有这个命令的话,就是输入文件的名字(hello.i) */
const char *main_input_filename;
/* 真正的源文件中的当前位置,其中有两个成员,分别记录文件名和行号 */
location_t input_location;
struct line_maps *line_table;
/* 保存文件信息的栈。虽然输入只有一个文件.i,但是,这个文件是经过预处理器处理过
 * 的,其中事实上包含着多个文件,这些文件在#-命令中说明,他们之间是include关系
 * 所以用栈来保存,该栈用一个单向链表实现,每个链表元素是一个location_t */
struct file_stack *input_file_stack;
/* input_file_stack每次改变,都增加 */
int input_file_stack_tick;
/* 记录在每一个tick时的input_file_stack */
typedef struct file_stack *fs_p;
DEF_VEC_P(fs_p);
DEF_VEC_ALLOC_P(fs_p,heap);
static VEC(fs_p,heap) *input_file_stack_history;
/* input_file_stack
 * 是否被回复成之前出现过的一个状态,Note:注释中说,这意味着之后不会再发生
 * pushing,但是,仍然可能push呀? */
static bool input_file_stack_restored;
/* dump输出文件的基文件名 */
const char *dump_base_name;
/* auxiliary输出文件的基文件名 */
const char *aux_base_name;
/* target_flags的掩码,表示命令行参数对这个标志的设置 */
int target_flags_explicit;
/* 调试用到的函数的钩子,依赖于命令行参数 */
const struct gcc_debug_hooks *debug_hooks;
/* 上面的东西的默认值 */
static const struct gcc_debug_hooks *default_debug_hooks;
/* 指定调试输出的类型 */
int rtl_dump_and_exit;
int flag_print_asm_name;
enum graph_dump_types graph_dump_format;
/* -o指定的输出文件名 */
const char *asm_file_name;
/* 非0表示做优化-O.
   不同的值代表不同的优化级别,例如-O2对应2。但是,只有典型的优化才被这个值控制
 * 其他的优化需要使用他们自己的标志指定 */
int optimize = 0;
/* 非0表示为目标代码的大小做优化-Os.
 * 只有0和非0两种情况。
 * 非0的时候,optimize的值是2,并且,其中一些可能导致代码膨胀的优化被禁止了*/
int optimize_size = 0;
/* FUNCTION_DECL节点,表示当前正在编译的函数,0表示目前编译在函数之间 */
tree current_function_decl;
/* 设置为当前函数的FUNC_BEGIN标号,NULL,如果没有当前函数。 */
const char * current_function_func_begin_label;
/* 用于临时禁止某些警告信息。当代码是某个系统头文件的代码时,非0*/
int in_system_header = 0;
/* 非0表示收集详细的关于编译的统计信息 */
int flag_detailed_statistics = 0;
/* 随机的字符串,可以被用户覆盖定义 */
static const char *flag_random_seed;
/* 用于记录编译时间的局部时间戳。如果系统不能提供时间,则为0。如果用户指定了一个
 * random seed,那么它为-1u*/
unsigned local_tick;
/* -f 标志 */
/* 非0表示'char'类型是有符号的 */
int flag_signed_char;
/* 非0表示仅仅给枚举类型分配它需要的字节数。
 * 值为2表示它还没有被初始化*/
int flag_short_enums;
/* 非0表示struct和union应该在内存中返回
 * 这个标志会产生较慢的代码,只有必要的时候才设置 */
#ifndef DEFAULT_PCC_STRUCT_RETURN
#define DEFAULT_PCC_STRUCT_RETURN 1
#endif
int flag_pcc_struct_return = DEFAULT_PCC_STRUCT_RETURN;
/* 表示如何处理复数 */
int flag_complex_method = 1;
/* 非0表示-fno-inline,我们本质上就不要inline */
int flag_really_no_inline = 2;
/* 非0表示我们需要把所有的声明保存在一个.X文件中 */
int flag_gen_aux_info = 0;
/* 指定上面那个文件的名字 */
const char *aux_info_file_name;
/* 非0表示我们在为一个共享库编译代码,否则是为一个可执行文件编译 */
int flag_shlib;
/* 为GNU还是NeXT Objective-C运行时环境生成代码 */
#ifdef NEXT_OBJC_RUNTIME
int flag_next_runtime = 1;
#else
int flag_next_runtime = 0;
#endif
/* 设置默认的tls模型 */
enum tls_model flag_tls_default = TLS_MODEL_GLOBAL_DYNAMIC;
/* 非0表示把某些警告变成错误 */
int flag_pedantic_errors = 0;
/* -dA 选项导致在输出的汇编文件中,以注释的方式输出调试信息 */
int flag_debug_asm = 0;
/* -dP 选项导致在汇编文件中,以注释的方式输出rtl */
int flag_dump_rtl_in_asm = 0;
/* 非空的时候表示,栈指针不能超过这个地址。也就是说,如果栈是向下增长的,栈指针
 * 必须始终大于等于它,否则,栈指针应该始终小于等于它。当前rtx可能是REG或
 * SYMBOL_REF,需要后端支持 */
rtx stack_limit_rtx;
/* 非0表示我们应该跟踪变量。当flag_var_tracking ==
 * AUTODETECT_VALUE它会被process_options ()自动设置  */
int flag_var_tracking = AUTODETECT_VALUE;
/* 为真表示用户为当前函数增加了'section'属性 */
bool user_defined_section_attribute = false;
/* -falign-*选项的值: 表示标号怎么对齐。
 * 0 :`use default', 1 :`don't align'. */
int align_loops_log;
int align_loops_max_skip;
int align_jumps_log;
int align_jumps_max_skip;
int align_labels_log;
int align_labels_max_skip;
int align_functions_log;
/*该结构体用于表示选项,而且是语言无关的*/
typedef struct
{
  const char *const string;
  int *const variable;
  const int on_value;
}
lang_independent_options;
/* 非0表示表达式必须从左到右计算 */
int flag_evaluation_order = 0;
/* 用户标号的前缀。Note: 什么时候使用? */
const char *user_label_prefix;
/* 本部分定义了GCC运行时的一些参数,例如这些参数可以控制某些优化进行的程度
 * 可以指定当一个函数中的指令数大于某个值时,不能进行inline,这些参数使用
 * --param 命令行参数指定*/
static const param_info lang_independent_params[] = {
#define DEFPARAM(ENUM, OPTION, HELP, DEFAULT, MIN, MAX) /
  { OPTION, DEFAULT, false, MIN, MAX, HELP },
#include "params.def"
#undef DEFPARAM
  { NULL, 0, false, 0, 0, NULL }
};
/* 输出文件 */
FILE *asm_out_file;
FILE *aux_info_file;
FILE *dump_file = NULL;
const char *dump_file_name;
/* 编译过程中的当前工作目录。*/
static const char *src_pwd;
}%
好多的全局变量呀。而且,大部分还不是文件内部的。
好吧,下面我们就来看看在main.c中被调用的toplev_main。这个函数正式cc1,cc1plus等
编译器的入口点。如果在编译的过程中出现错误,其返回FATAL_EXIT_CODE,否则返回
SUCCESS_EXIT_CODE。这个函数在一次编译过程中只能被调用一次,多次调用可能导致错误
,这个函数的控制流异常简单。
%{
int
toplev_main (unsigned int argc, const char **argv)
{
  save_argv = argv;

  /* 初始化GCC的运行环境和诊断信息的管理 */
  general_init (argv[0]);

  /* 根据命令行初始化选项并做简单的处理,基本上就是将未指定的选项设为默认值 */
  decode_options (argc, argv);

  /*初始化local_tick变量,用于计算时间*/
  init_local_tick ();

  /* 如果exit_after_options为真,处理完选项后退出,并不进行编译 */
  if (!exit_after_options)
    do_compile ();   /* 真正干活的函数 */

  /* 如果出错,返回错误值。Note:errorcount 和 sorrycount有什么区别?*/
  if (errorcount || sorrycount)
    return (FATAL_EXIT_CODE);

  return (SUCCESS_EXIT_CODE);
}
}%
由于本章的目的不是详细地分析每一个GCC的过程,而是看看C代码是怎么被变成GIMPLE的,
所以,越过初始化的部分,我们直接看干活的函数do_compile。这个函数会初始化编译器,
并编译当前的输入文件。这里的初始化应该是对toplev_main中的初始化的补充。
%{
static void
do_compile (void)
{
  /* 初始化计时器。所有计时器的类型和用途定义在timeval.def文件中 */
  if (time_report || !quiet_flag  || flag_detailed_statistics)
    timevar_init ();
  timevar_start (TV_TOTAL);
  /* 处理已经分析过的选项在这个函数里面出现了lang_hooks.post_options调用。这个
   * 函数的确在处理选项,只是它不再分析命令行,分析的过程在toplev_main中已经做
   * 了。这里是进一步的处理和一些一致性检查。 */
  process_options ();

  /* 如果已经发生了错误,不必继续做 */
  if (!errorcount)
    {
      /* 该函数必须运行,对于使用非默认浮点格式的目标,计算浮点的预定于宏 */
      init_adjust_machine_modes ();

      /* 如果需要后端的话,初始化它。该函数只在这里调用一次 */
      if (!no_backend)
    backend_init ();

      /* 语言相关的初始化,成功时返回非0 */
      if (lang_dependent_init (main_input_filename))
    compile_file ();  /*真正用于编译一个文件的函数*/
      /*清理函数,完成关闭打开的文件之类的任务*/
      finalize ();
    }

  /* 停止计时,打印时间信息 */
  timevar_stop (TV_TOTAL);
  timevar_print (stderr);
}
}%
下面,我们来看函数compile_file。该函数完成一个翻译单元(通常是一个.i文件)的编译。
输出汇编语言的文件以及一些调试用的文件。
%{
static void
compile_file (void)
{
  /* 初始化其他遍,init_cgraph只是设置了cgraph的dump文件 */
  init_cgraph ();
  /* 初始化final遍的数据,final遍会将RTL转换成汇编并输出它*/
  init_final (main_input_filename);
  /* 初始化关于coverage文件的东西,Note: 这是什么文件?*/
  coverage_init (aux_base_name);
  /* 分析的计时,这个说明分析过程开始了*/
  timevar_push (TV_PARSE);

  /* 调用分析器,它分析整个文件,并为每个函数调用rest_of_compilation*/
  lang_hooks.parse_file (set_yydebug);

  /* 在丢失'}'的情况下,使我们回到全局的作用域 */
  lang_hooks.clear_binding_stack ();

  /* 编译基本结束,还剩下一些将符号表中剩下的部分输出的工作Note:具体是什么? */
  timevar_pop (TV_PARSE);

  /* 如果该标志非0,不要输出汇编文件,返回即可*/
  if (flag_syntax_only)
    return;
  /* 在编译结束的时候,将所有的全局定义(包括函数)输出到汇编文件中
   * 在unit-at-a-time的情况下,所有的分析优化都在这里做*/
  lang_hooks.decls.final_write_globals ();

  if (errorcount || sorrycount)
    return;
  /*输出所有在队列中等待输出的变量定义到汇编文件*/
  varpool_assemble_pending_decls ();
  finish_aliases_2 ();

  /* 处理coverage文件的清理工作,关闭图文件等 */
  coverage_finish ();

  /* 同上 */
  if (flag_mudflap)
    mudflap_finish_file ();

  /* 同上 */
  if (!targetm.have_tls)
    emutls_finish ();

  /* 输出共享常量池的内容*/
  output_shared_constant_pool ();
  /* 输出所有object_block的定义*/
  output_object_blocks ();

  /* 输出weak symbol声明 */
  weak_finish ();

  /* 处理调试符号 */
  timevar_push (TV_SYMOUT);

#if defined DWARF2_DEBUGGING_INFO || defined DWARF2_UNWIND_INFO
  if (dwarf2out_do_frame ())
    dwarf2out_frame_finish ();
#endif

  (*debug_hooks->finish) (main_input_filename);
  timevar_pop (TV_SYMOUT);

  /* 必要时在文件的结束输出一些其他信息 */
  dw2_output_indirect_constants ();

  /* 处理剩下的汇编语言的外部的指示符(如果有的话) */
  process_pending_assemble_externals ();

  /* 增加一个.ident指示符,表示生成这份代码的gcc的版本 */
#ifdef IDENT_ASM_OP
  if (!flag_no_ident)
    {
      const char *pkg_version = "(GNU) ";

      if (strcmp ("(GCC) ", pkgversion_string))
    pkg_version = pkgversion_string;
      fprintf (asm_out_file, "%s/"GCC: %s%s/"/n",
           IDENT_ASM_OP, pkg_version, version_string);
    }
#endif

  /* 这个必须放在最后,有些目标要求在汇编文件的最后有一些特殊的指示符,所以,
   * GCC 不能在这之后再向汇编文件输出任何东西 */
  targetm.asm_out.file_end ();
}
}%
从这个函数的前几行就可以看出来,lang_hooks中的parse_file函数完成了大部分的编译工
作。另外,在其他的地方也多次出现这个lang_hooks变量,所有的出现都在调用其中的函数
。我们可以猜测,这个结构体就是GCC主体和编译器前端的接口。编译器的前端,通过定义
这个结构体中的函数来向GCC注册自己,GCC的主体代码通过这个接口中的函数来处理和语言
相关的东西。另外,parse_file还调用了GCC主体代码中的其它部分,来完成分析和优化。
这个部分很可能是rest_of_compileation那个函数。
好的,目标明确,下面,我们就看看这个langhook接口。在gcc目录下发现三个文件,这三
个文件的名字都包含langhooks,分别为langhools.[ch],langhooks-def.h。
首先,看一下langhooks.h文件。在这个文件中,看到了struct lang_hooks结构的定义。
根据它的注释,我们可以知道,前面的猜测是正确的,和语言相关的所有的回调函数“钩
子函数”都是在这个结构中定义的。每一个前端都应该定义这样类型的一个全局变量,该变
量的名字是lang_hooks。在文件langhooks-def.h和langhooks.c中,给出了这个变量的默认
定义。除了这个变量,每一个语言的前端还应该提供一个函数“add_builtin_function”。
至于每一个钩子应该给一个完成什么功能的函数,在langhooks.h文件中的注释中进行了详
细的说明。
仔细看一下langhooks-def.h,我们发现,在该文件中,对应于每一个lang_hooks的每一个
最底层的成员,都有一个宏与之对应,这些宏目前初始化定义为一个默认的函数(
在langhooks.c中定义)。例如,name成员的默认定义是:
%{
#define LANG_HOOKS_NAME         "GNU unknown"
}%
最后,该文件利用这些宏定义了一个LANG_HOOKS_INITIALIZER宏,它是一个结构体struct
lang_hooks的初始化器,初始化的值是上面那些宏的值。这里,又用到了一个小的编程技巧
。如果某个前端不用默认的钩子函数,只需要自己重新定义那个宏就好了。比如,C语言要
这样
%{
#undef LANG_HOOKS_NAME
#define LANG_HOOKS_NAME "GNU C"
#undef LANG_HOOKS_INIT
}%
之后,就可以使用LANG_HOOKS_INITIALIZER来初始化自己的lang_hooks了,没有被重新定义
的,仍然使用默认的函数。

好,弄清楚了lang_hooks这个接口,下面要继续的就是找到C语言对应的这个接口中,
parse_file成员对应的是什么函数了。
首先,在找到了对应于C语言前端的lang_hooks是在文件c-lang.c中定义的。
%{
/* Each front end provides its own lang hook initializer.  */
const struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
}%
这个文件就是用来定义C语言的前端的钩子的。
在文件的一开始,定义了这个前端接受的C语言的变种是C90,C94或C99标准。然后,定义了
这个钩子结构的名字GNU C以及一个钩子函数。我们要找的parse_file并不在这个文件里面
。在这个文件中还定义了C的前端用到的树节点的一些信息(type, length, name),这些数
组中既包括语言无关的,也包括语言相关的。
我们看一下这个文件include进来的文件,很容易看出来,“c-objc-common.h”文件最为可
疑。果然,这个文件中重新定义了大量的宏。这些函数的定义为c语言和objc语言共享的,
而在c-lang.c文件中定义的那两个宏是c语言自己的。我们看到,LANG_HOOKS_PARSE_FILE定
义为函数c_common_parse_file,这个函数定义在c-opts.c文件中。
=====================

经过跟踪,终于把C语言的预处理部分弄明白了。首先,在上面所说的“多个文件”,一般情
况下只是一个文件。这个文件是一个.c文件,还没有经过预处理。完成预处理部分的代码是
在上层目录的libcpp目录下。同时,libcpp也提供了一个词法分析器,在gcc目录下,只要
调用这个词法分析器就行了,预处理的过程对于编译器而言是透明的。下面,以文件
c-parse.c为核心,分析C语言的分析器。这个文件可是有8000多行呀...
%{
/* 该结构用于保存保留字/关键字表 */
struct resword
{
  const char *word;
  ENUM_BITFIELD(rid) rid : 16; /* 这个域的所有可能值定义在c-common.h中*/
  unsigned int disable   : 16;
};

/* if (reswords[i].disable & mask) 为真,则该关键字不起作用。有这个东西,主要是
 * 因为该文件中的parse用于分析好几种c的方言*/
#define D_C89    0x01    /* not in C89 */
#define D_EXT    0x02    /* GCC extension */
#define D_EXT89    0x04    /* GCC extension incorporated in C99 */
#define D_OBJC    0x08    /* Objective C only */

/* 这个数组中保存了所有可能的关键字 */
static const struct resword reswords[] =
}%
之后,有一个初始化分析器的函数。这个函数是c_parse_init,其主要作用就是初始化数组
ridpointers,里面存放了所有关键字对应的树节点(identifer节点)。这个初始化被
c-init-decl-processing调用,后者被c_objc_common_init调用。
c的词法分析器需要cpplib中的词法分析器,c-lex.c以及c的语法分析器配合完成词法分析
。对于c的而言,语法分析器的结构中存储了关于词法分析器的信息,而没有为词法分析器
单独定义一个结构。
%{
/* 由于cpp_ttype中没有定义关键字类型,为了表示这种类型,我们定义它*/
#define CPP_KEYWORD ((enum cpp_ttype) (N_TTYPES + 1))
}%
下面的代码把标志符进一步分类了
%{
/* 所有的标志符都是CPP_NAME类型的token */
typedef enum c_id_kind {
  /* 普通的标志符 */
  C_ID_ID,
  /* typedef定义的类型名 */
  C_ID_TYPENAME,
  /* Objective-C中的类名 */
  C_ID_CLASSNAME,
  /* 不是标志符 */
  C_ID_NONE
} c_id_kind;
}%
关于token的定义,用于保存一个token,这些token经过字符常量的合并以及预处理
%{
typedef struct c_token GTY (())
{
  /* token的类型,这个类型是cpp定义的类型 */
  ENUM_BITFIELD (cpp_ttype) type : 8;
  /* 如果这个token是CPP_NAME,该域表示其类型,否则,它的值为C_ID_NONE */
  ENUM_BITFIELD (c_id_kind) id_kind : 8;
  /* 如果该token是一个关键字,该域表示哪一个关键字,否则为RID_MAX.  */
  ENUM_BITFIELD (rid) keyword : 8;
  /* 如果是一个CPP_PRAGMA, 该域表示了PRAGMA的类型,否则为PRAGMA_NONE.  */
  ENUM_BITFIELD (pragma_kind) pragma_kind : 7;
  /* 为真表示这个token来自某个系统头文件 */
  BOOL_BITFIELD in_system_header : 1;
  /* 和这个token相关的值,如果有的话 */
  tree value;
  /* 这个token的位置 */
  location_t location;
} c_token;
}%
parser的结构保存了关于分析状态和上下文的信息,还有可能的两个lookahead,对于c语言
不需要更多的lookahead。
%{
typedef struct c_parser GTY(())
{
  /* look-ahead tokens.  */
  c_token tokens[2];
  /* 当前有多少个lookahead符号 (0, 1 or 2).  */
  short tokens_avail;
  /* 真表示正在从一个语法错误中恢复。
     c_parser_error会设置这个标志,当消耗了足够多的符号后,应该清除这个位 */
  BOOL_BITFIELD error : 1;
  /* 为真表示我们正在处理一个pragma, 不应该自动消耗CPP_PRAGMA_EOL.  */
  BOOL_BITFIELD in_pragma : 1;
  /* 真表示我们正在处理if块的最外层的块 */
  BOOL_BITFIELD in_if_block : 1;
  /* True if we want to lex an untranslated string.Note:这是什么?  */
  BOOL_BITFIELD lex_untranslated_string : 1;
  /* Objective-C 使用的信息 */
  BOOL_BITFIELD objc_pq_context : 1;
  BOOL_BITFIELD objc_need_raw_identifier : 1;
} c_parser;
}%
该文件中定义了一个c_parser类型的静态指针变量the_parser,这个就是真正使用的分析器
了。
下面这个函数会分析一个完整的c的源文件。
%{
void
c_parse_file (void)
{
  /* 使用局部内存 */
  c_parser tparser;

  memset (&tparser, 0, sizeof tparser);
  the_parser = &tparser;

  /* 如果是#pragma GCC pch_preprocess,那么会加载一个pch文件 */
  if (c_parser_peek_token (&tparser)->pragma_kind == PRAGMA_GCC_PCH_PREPROCESS)
    c_parser_pragma_pch_preprocess (&tparser);

  the_parser = GGC_NEW (c_parser);
  *the_parser = tparser;

  c_parser_translation_unit (the_parser);
  the_parser = NULL;
}

/* 返回指向parser中下一个token的指针,必要的话读入它 */
static inline c_token *
c_parser_peek_token (c_parser *parser)
{
  if (parser->tokens_avail == 0)
    {
      c_lex_one_token (parser, &parser->tokens[0]);
      parser->tokens_avail = 1;
    }
  return &parser->tokens[0];
}
}%
在这个函数中,仅仅处理第一个token是pragma并且需要加载PCH文件的情况,它会调用函数
加载这个文件。其他的翻译过程都在c_parser_translation_unit函数中。同时,我们也解
释了一下c_parser_peek_token函数,它并不消耗任何token。
c_parser_translation_unit函数分析一个翻译单元,其实就是一个文件。
首先看看transalation_unit的语法。
%{
   translation-unit:
     external-declarations

   external-declarations:
     external-declaration
     external-declarations external-declaration

   GNU extensions:

   translation-unit:
     empty
}%
可以看出,一个翻译单元其实就是一些外部声明构成的序列。用于分析它的函数是:
%{
/*如果下一个token有指定的类型,返回真*/
static inline bool
c_parser_next_token_is (c_parser *parser, enum cpp_ttype type)
{
  return c_parser_peek_token (parser)->type == type;
}

static void
c_parser_translation_unit (c_parser *parser)
{
  /* CPP_EOF代表文件结束*/
  if (c_parser_next_token_is (parser, CPP_EOF))
    {
      if (pedantic)
        pedwarn ("%HISO C forbids an empty source file",
                 &c_parser_peek_token (parser)->location);
    }
  else
    {
      void *obstack_position = obstack_alloc (&parser_obstack, 0);
      /*这个循环完成分析工作,每次分析一个外部声明,直到文件结束*/
      do
        {
          ggc_collect ();
          c_parser_external_declaration (parser);
          obstack_free (&parser_obstack, obstack_position);
        }
      while (c_parser_next_token_is_not (parser, CPP_EOF));
    }
}
}%
其中,obstack是一个用宏实现的栈,用于存储管理,而parser_obstack是用于保存和分析
相关的对象的栈。c_parser_external_declaration函数用于分析一个外部声明。现在可以
推测,这个分析器是递归下降分析器。呵呵,其实当发现这个分析器是手写的时候,就可以
这样推测了,只是当时还觉得像RM这样的家伙没准真的发疯写一个LR的出来。下面是外部声
明的语法
%{
   external-declaration:
     function-definition
     declaration
}%
可见,外部声明只有两种情况,一种是函数定义,另外一种是变量声明。
当然,这里面没有包含其他的扩展情况。
%{
static void
c_parser_external_declaration (c_parser *parser)
{
  int ext;
  /* 根据下一个符号的类型来区别对待*/
  switch (c_parser_peek_token (parser)->type)
    {
    case CPP_KEYWORD:
      /*关键字的话,再根据不同的关键字区别对待*/
      switch (c_parser_peek_token (parser)->keyword)
        {
        case RID_EXTENSION:
          /* c的_extension_关键字,首先取消某些警告,消耗掉这个关键字,其后应该
           * 是一个正常的外部声明,分析完后,回复原来的警告标志*/
          ext = disable_extension_diagnostics ();
          c_parser_consume_token (parser);
          c_parser_external_declaration (parser);
          restore_extension_diagnostics (ext);
          break;
        case RID_ASM:
          /* 分析asm定义, 分析的结果是一个树,这个树被加入到callgraph中*/
          c_parser_asm_definition (parser);
          break;
          /*下面几个,都是对object-c的处理*/
        case RID_AT_INTERFACE:
        case RID_AT_IMPLEMENTATION:
          gcc_assert (c_dialect_objc ());
          c_parser_objc_class_definition (parser);
          break;
        case RID_AT_CLASS:
          gcc_assert (c_dialect_objc ());
          c_parser_objc_class_declaration (parser);
          break;
        case RID_AT_ALIAS:
          gcc_assert (c_dialect_objc ());
          c_parser_objc_alias_declaration (parser);
          break;
        case RID_AT_PROTOCOL:
          gcc_assert (c_dialect_objc ());
          c_parser_objc_protocol_definition (parser);
          break;
        case RID_AT_END:
          gcc_assert (c_dialect_objc ());
          c_parser_consume_token (parser);
          objc_finish_implementation ();
          break;
        default:
          goto decl_or_fndef;
        }
      break;
    case CPP_SEMICOLON:
      if (pedantic)
        pedwarn ("%HISO C does not allow extra %<;%> outside of a function",
                 &c_parser_peek_token (parser)->location);
      c_parser_consume_token (parser);
      break;
    case CPP_PRAGMA:
      c_parser_pragma (parser, pragma_external);
      break;
    case CPP_PLUS:
    case CPP_MINUS:
      if (c_dialect_objc ())
        {
          c_parser_objc_method_definition (parser);
          break;
        }
      /* 上面处理的事特殊情况,所有一般的情况,包括声明和函数定义,都在这里处理
       * 在进一步分析之前,我们并不能判断是变量声明还是函数定义 */
      /* 只有分析了第一个declarator之后才能分别出来是什么声明*/
    default:
    decl_or_fndef:
      /*这个是主要干活的函数*/
      c_parser_declaration_or_fndef (parser, true, true, false, true);
      break;
    }
}
}%
先看一下声明和函数的语法
%{
   declaration:
     declaration-specifiers init-declarator-list[opt] ;

   function-definition:
     declaration-specifiers[opt] declarator declaration-list[opt]
       compound-statement

   declaration-list:
     declaration
     declaration-list declaration

   init-declarator-list:
     init-declarator
     init-declarator-list , init-declarator

   init-declarator:
     declarator simple-asm-expr[opt] attributes[opt]
     declarator simple-asm-expr[opt] attributes[opt] = initializer
}%
其中,simple-asm-expr,attributes都是gnu的扩展。除此之外,就是我们最熟悉的c的语法
了。
这个函数比较长。
%{
static void
c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok, bool empty_ok,
                               bool nested, bool start_attr_ok)
{
  /* 这个结构体代表了C语言中的一个声明序列,定义在c-tree.h中,它可以表示一个声明
   * 的类型以及所有的修饰符 */
  struct c_declspecs *specs;
  tree prefix_attrs;
  tree all_prefix_attrs;
  bool diagnosed_no_specs = false;
  location_t here = c_parser_peek_token (parser)->location;

  /* 返回一个c_declspecs,对应于一个空的声明列表*/
  specs = build_null_declspecs ();
  /* 这个函数用于分析一个声明序列,它尽量读更多的属于声明序列的东西,并将所有的
   * 信息记录在specs中*/
  c_parser_declspecs (parser, specs, true, true, start_attr_ok);
  /* 出错处理,暂时不用理会 */
  if (parser->error)
    {
      c_parser_skip_to_end_of_block_or_statement (parser);
      return;
    }
  if (nested && !specs->declspecs_seen_p)
    {
      c_parser_error (parser, "expected declaration specifiers");
      c_parser_skip_to_end_of_block_or_statement (parser);
      return;
    }
  /* 该函数计算specs中的type字段,将signed或是long等关键字于int等类型结合起来*/
  finish_declspecs (specs);
  /* 处理空的声明 */
  if (c_parser_next_token_is (parser, CPP_SEMICOLON))
    {
      if (empty_ok)
        shadow_tag (specs);
      else
        {
          shadow_tag_warned (specs, 1);
          pedwarn ("%Hempty declaration", &here);
        }
      c_parser_consume_token (parser);
      return;
    }
  /* 如果前面的结构体、联合体、枚举有错误,在此打印错
   * 误消息。这个必须放在空声明处理的后面 */
  pending_xref_error ();
  prefix_attrs = specs->attrs;
  all_prefix_attrs = prefix_attrs;
  specs->attrs = NULL_TREE;
  /* 这个循环处理剩下的所有的东西,即被声明的东西。*/
  while (true)
    {
      /*用于记录一个被声明的实体,可以是标识符、函数等*/
      struct c_declarator *declarator;
      bool dummy = false;
      tree fnbody;
      /* 分析一个或多个变量声明,或者分析一个函数定义 */
      declarator = c_parser_declarator (parser, specs->type_seen_p,
                                        C_DTR_NORMAL, &dummy);
      if (declarator == NULL)
        {
          c_parser_skip_to_end_of_block_or_statement (parser);
          return;
        }
      if (c_parser_next_token_is (parser, CPP_EQ)
          || c_parser_next_token_is (parser, CPP_COMMA)
          || c_parser_next_token_is (parser, CPP_SEMICOLON)
          || c_parser_next_token_is_keyword (parser, RID_ASM)
          || c_parser_next_token_is_keyword (parser, RID_ATTRIBUTE))
        {
    /* 这是一个数据定义,而不是函数定义 */
          tree asm_name = NULL_TREE;
          tree postfix_attrs = NULL_TREE;
          if (!diagnosed_no_specs && !specs->declspecs_seen_p)
            {
              diagnosed_no_specs = true;
              pedwarn ("%Hdata definition has no type or storage class",
                       &here);
            }
          fndef_ok = false;
      /* 声明后有一个asm属性,分析一下它 */
          if (c_parser_next_token_is_keyword (parser, RID_ASM))
            asm_name = c_parser_simple_asm_expr (parser);
      /* 声明后有attribute属性说明,分析 */
          if (c_parser_next_token_is_keyword (parser, RID_ATTRIBUTE))
            postfix_attrs = c_parser_attributes (parser);
          if (c_parser_next_token_is (parser, CPP_EQ))
            {
      /* 表示有初始化表达式 */
              tree d;
              struct c_expr init;
              c_parser_consume_token (parser);
              /* 分析初始化器时,被定义的变量是有效的 */
          /* 该函数分析一个declarator,当类型信息和变量名都已经分析完的时候
           * 调用(此时,如果有初始化器,还没有分析),这个函数中会创建_DECL
           * 树节点,填入其类型,并把它加入当前上下文的声明列表中,返回值也
           * 是这个节点。*/
              d = start_decl (declarator, specs, true,
                              chainon (postfix_attrs, all_prefix_attrs));
              if (!d)
                d = error_mark_node;
          /* 分析初始化器 */
              start_init (d, asm_name, global_bindings_p ());
              init = c_parser_initializer (parser);
              finish_init ();
              if (d != error_mark_node)
                {
                  maybe_warn_string_init (TREE_TYPE (d), init);
          /* 最终完成一个声明节点*/
                  finish_decl (d, init.value, asm_name);
                }
            }
          else
            {
        /*没有初始化器*/
              tree d = start_decl (declarator, specs, false,
                                   chainon (postfix_attrs,
                                            all_prefix_attrs));
              if (d)
                finish_decl (d, NULL_TREE, asm_name);
            }
          if (c_parser_next_token_is (parser, CPP_COMMA))
            {
          /*逗号,还有下一个声明,但是,在此先把一个属性分析了*/
              c_parser_consume_token (parser);
              if (c_parser_next_token_is_keyword (parser, RID_ATTRIBUTE))
                all_prefix_attrs = chainon (c_parser_attributes (parser),
                                            prefix_attrs);
              else
                all_prefix_attrs = prefix_attrs;
              continue;
            }
          else if (c_parser_next_token_is (parser, CPP_SEMICOLON))
            {
          /*一个声明结束了*/
              c_parser_consume_token (parser);
              return;
            }
          else
            {
          /*出错*/
              c_parser_error (parser, "expected %<,%> or %<;%>");
              c_parser_skip_to_end_of_block_or_statement (parser);
              return;
            }
        }
      else if (!fndef_ok)
        {
      /* 不接受函数定义 */
          c_parser_error (parser, "expected %<=%>, %<,%>, %<;%>, "
                          "% or %<__attribute__%>");
          c_parser_skip_to_end_of_block_or_statement (parser);
          return;
        }
      /* 函数定义 */
      if (nested)
        {
      /* 嵌套函数定义,标准c中是不允许的 */
          if (pedantic)
            pedwarn ("%HISO C forbids nested functions", &here);
          push_function_context ();
        }
      /* start_function函数为一个函数定义创建FUNCTION_DECL节点,
       * 该函数为其对应的函数体创建一个绑定上下文,同时会设置
       * current_function_decl全局变量*/
      if (!start_function (specs, declarator, all_prefix_attrs))
        {
          /* This can appear in many cases looking nothing like a
             function definition, so we don't give a more specific
             error suggesting there was one.  */
          c_parser_error (parser, "expected %<=%>, %<,%>, %<;%>, % "
                          "or %<__attribute__%>");
          if (nested)
            pop_function_context ();
          break;
        }
      /* 处理老式的参数声明 */
      while (c_parser_next_token_is_not (parser, CPP_EOF)
             && c_parser_next_token_is_not (parser, CPP_OPEN_BRACE))
        c_parser_declaration_or_fndef (parser, false, false, true, false);
      /* 设置位置 */
      DECL_SOURCE_LOCATION (current_function_decl)
        = c_parser_peek_token (parser)->location;
      /* 将当前函数的参数声明存储到函数声明中去 */
      store_parm_decls ();
      /* 分析一个复合语句, fnbody 是一个BIND表达式 */
      fnbody = c_parser_compound_statement (parser);
      if (nested)
        {
          tree decl = current_function_decl;
          add_stmt (fnbody);
          finish_function ();
          pop_function_context ();
          add_stmt (build_stmt (DECL_EXPR, decl));
        }
      else
        {
      /* 将函数加入到当前的语句列表中 */
          add_stmt (fnbody);
      /* 完成一个函数的编译,它会把该函数编译成汇编语言输出,释放函数定义所
       * 占用的空间,在分析完函数体后调用*/
          finish_function ();
        }
      break;
    }
}
}%
事实上,在调用finish_function函数之前,所有的分析都是生成语言相关的GENERIC树的。
剩下的所有的事情,包括所有的优化,都在finish_function中完成。根据这个函数的注释
当它结束的时候,已经把当前函数的编译成了汇编代码,并释放了所有用于编译当前函数
的存储空间。问题是,如果真是这样,那么过程间的分析和优化是如何进行的?
总之,我们先来看看这个函数把。
%{
void
finish_function (void)
{
  tree fndecl = current_function_decl;

  /* 弹出这两个stack的最后一个元素 */
  label_context_stack_se = label_context_stack_se->next;
  label_context_stack_vm = label_context_stack_vm->next;

  /* 整型提升,这里,在满足条件的情况下将所有的整形参数和返回值的位数提升到
   * interger_type_node,这个需要目标机器的信息*/
  if (TREE_CODE (fndecl) == FUNCTION_DECL
      && targetm.calls.promote_prototypes (TREE_TYPE (fndecl)))
    {
      tree args = DECL_ARGUMENTS (fndecl);
      for (; args; args = TREE_CHAIN (args))
    {
      tree type = TREE_TYPE (args);
      if (INTEGRAL_TYPE_P (type)
          && TYPE_PRECISION (type) < TYPE_PRECISION (integer_type_node))
        DECL_ARG_TYPE (args) = integer_type_node;
    }
    }

  /* 一些关联工作,将返回值、函数体关联到当前函数 */
  if (DECL_INITIAL (fndecl) && DECL_INITIAL (fndecl) != error_mark_node)
    BLOCK_SUPERCONTEXT (DECL_INITIAL (fndecl)) = fndecl;
  if (DECL_RESULT (fndecl) && DECL_RESULT (fndecl) != error_mark_node)
    DECL_CONTEXT (DECL_RESULT (fndecl)) = fndecl;

  /* 处理main函数 */
  if (MAIN_NAME_P (DECL_NAME (fndecl)) && flag_hosted)
    {
      if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (fndecl)))
      != integer_type_node)
    {
      /* If warn_main is 1 (-Wmain) or 2 (-Wall), we have already warned.
         If warn_main is -1 (-Wno-main) we don't want to be warned.  */
      if (!warn_main)
        pedwarn ("return type of %q+D is not %", fndecl);
    }
      else
    {
      if (flag_isoc99)
        {
          tree stmt = c_finish_return (integer_zero_node);
#ifdef USE_MAPPED_LOCATION
          /* Hack.  We don't want the middle-end to warn that this return
         is unreachable, so we mark its location as special.  Using
         UNKNOWN_LOCATION has the problem that it gets clobbered in
         annotate_one_with_locus.  A cleaner solution might be to
         ensure ! should_carry_locus_p (stmt), but that needs a flag.
          */
          SET_EXPR_LOCATION (stmt, BUILTINS_LOCATION);
#else
          /* Hack.  We don't want the middle-end to warn that this
         return is unreachable, so put the statement on the
         special line 0.  */
          annotate_with_file_line (stmt, input_filename, 0);
#endif
        }
    }
    }

  /* 对函数的语句树作一些处理 */
  DECL_SAVED_TREE (fndecl) = pop_stmt_list (DECL_SAVED_TREE (fndecl));

  /* 完成当前的绑定的处理,将它们加入到函数的语句树中。
  finish_fname_decls ();

  /* 没有返回语句,输出诊断信息 */
  if (warn_return_type
      && TREE_CODE (TREE_TYPE (TREE_TYPE (fndecl))) != VOID_TYPE
      && !current_function_returns_value && !current_function_returns_null
      /* Don't complain if we are no-return.  */
      && !current_function_returns_abnormally
      /* Don't warn for main().  */
      && !MAIN_NAME_P (DECL_NAME (fndecl))
      /* Or if they didn't actually specify a return type.  */
      && !C_FUNCTION_IMPLICIT_INT (fndecl)
      /* Normally, with -Wreturn-type, flow will complain.  Unless we're an
     inline function, as we might never be compiled separately.  */
      && DECL_INLINE (fndecl))
    {
      warning (OPT_Wreturn_type,
           "no return statement in function returning non-void");
      TREE_NO_WARNING (fndecl) = 1;
    }

  /* 保存函数的结束位置,cfun结构中保存了当前函数的所有信息 */
  cfun->function_end_locus = input_location;

  /* 确定elf的可见性信息,参考elf文件格式 */
  c_determine_visibility (fndecl);

  /* inline 关键字的处理 */
  if (DECL_EXTERNAL (fndecl)
      && DECL_DECLARED_INLINE_P (fndecl))
    DECL_DISREGARD_INLINE_LIMITS (fndecl) = 1;

  /* 这里,将函数的表示形式变为GENERIC.Note:难道不是已经是这种形式的吗? */

  if (DECL_INITIAL (fndecl) && DECL_INITIAL (fndecl) != error_mark_node
      && !undef_nested_function)
    {
      if (!decl_function_context (fndecl))
    {
      /* 非嵌套函数 */
      /* 这个函数的名字似乎是转换成GENERIC, 其实,和我们预想的一样,
       * c的前端构造的树已经是GENERIC了,在这个函数中会将这些树转换
       * 成gimple*/
      c_genericize (fndecl);
      c_gimple_diagnostics_recursively (fndecl);

      /* 这个部分用于处理Objc在finalize函数之后还可能插入新的函数
       * 这部分代码不应该这样用。cgraph_add_new_function是为
       * middle-end 准备的函数,而不是前端*/
      if (cgraph_global_info_ready)
        {
          cgraph_add_new_function (fndecl, false);
          return;
        }

      cgraph_finalize_function (fndecl, false);
    }
      else
    {
      /* 嵌套函数 */
      (void) cgraph_node (fndecl);
    }
    }

  if (!decl_function_context (fndecl))
    undef_nested_function = false;

  /* We're leaving the context of this function, so zap cfun.
     It's still in DECL_STRUCT_FUNCTION, and we'll restore it in
     tree_rest_of_compilation.  */
  set_cfun (NULL);
  current_function_decl = NULL;
}
}%
到此,我们看到了怎么样从c的代码翻译到GIMPLE中间表示。
所有剩下的工作,都是在函数cgraph_finalize_function完成的(在非unit-at-a-time的情况下,所有的分析和优化都在这里做)。

你可能感兴趣的:(编译器)