`
weimou66
  • 浏览: 1244749 次
文章分类
社区版块
存档分类
最新评论

GCC-3.4.6源代码学习笔记

 
阅读更多

大约4年前,我加入了GDNT - 北电网络在中国的合资企业,参与3G UMTS无线接入网的研发工作。与GCC有了第一次亲密的接触(之前使用的是MS的VC)。彼时,北电在其诸如,UMTS、CDMA、及自行开发的众多工具等项目中(此后,在4G项目,Wimax及Lte中),将GCC作为标准编译器来使用。每周我都需要进行数次的loadbuild,编译出load文件进行测试,以验证我对一些bug的修正代码。每次loadbuild,编译的文件数以千计,最后的执行文件可至百兆的尺寸。而GCC的运行令人惊异地稳定。这些年来,我只碰到过2次GCC的崩溃,其中一次还是因为我为了达到模板分离编译(separate compilation of template)的效果(GCC至今未支持这一特性。据我所知,目前只有EDG的前端能做到。而且在C++标准稳定下来之前【ISO-IEC - 14882-1998】,EDG的前端是实际的标准。在http://www.edg.com/可以找到有关EDG的情况),使用了奇怪的代码(含有错误的C++代码),而导致生成错误的中间树代码(很遗憾,GCC未能检出语法错误)。从而触发了GCC内部的断言。因此GCC虽然退出,但给出了详细的错误转储。还算退出得比较体面。

这个神奇的工具令人称奇!虽然以前我也学过编译原理,但是面对GCC,我觉得对它知之甚少。多亏GCC是开源的,使我得以窥探其神秘面纱后的容颜。这些年,虽然自觉对GCC已有相当的了解,但远还未到究竟。借此博客,我将和大家分享这些年来学习GCC的笔记(所关注的GCC为:3.4.6版,C++前端,运行平台:x86/Linux,目标机器:x86/Linux)。而这个笔记还在增长中,还远未结束。

参考文献

[1] Programming language pragmatics, 2nd edition

[2] gccint, version 3.4.6

[3] ISO-IEC-14882-2003

[4] The C Preprocessor April 2001, for GCC V3

[5] cppinternals

[6] Using the GNU Compiler Collection

[7] Inside The C++ Object Model, by Stanley B.Lippman

[8] GCC Complete Reference

[9] The design and evolution of C++, by Bjarne Stroustrup

[10] Linkers & Loaders, by John R. Levine

[11] Efficient Instruction Scheduling Using Finite State Automata, by Vasanth Bala, Norman Rubin

[12] Compilers: Principles, Techniques, and Tools, 2nd edition

其中,[2]和[5]保存在目录“YOUR-GCC-SOURCE-DIR/gcc/doc”下。[1],[7],[9],[10]和[12]给出了一些有用的背景知识。

准备工作

一些重要的GCC源码是由GCC自带工具生成的。在深入看代码前,我们首先需要编译GCC,生成这些源码。至于如何下载源代码(http://gcc.gnu.org/mirrors.html是官方的下载地址),编译前配置及编译,网上有丰富的资料,在此略过(注:如果已经装有GCC,使用g++ -###可查看GCC的配置命令)。

GCC的架构

GCC可简单地分为2部分:前端和后端。预处理器(如果存在)、词法分析器和语法分析器在前端实现,前端的作用是将源语言写的程序转换成与语言无关的中间形式。因此,理论上,引入对新语言的支持,只需要实现预处理器、词法分析器和语法分析器。但实际上,一般的我们还需要写一些代码以确立运行时环境。

在GCC 3.4.6,这个通用的中间语言是RTL(register transfer language)。RTL是一种简单的语言,很容易就能翻译成汇编代码。因此,直接将源语言转换为RTL,不太合适。事实上,前端首先会将代码转换成中间树并进行大量的处理,然后再进一步变换为RTL,并送入后端。

后端的作用则是生成汇编代码。作为广泛使用的编译器,GCC可以支持各种流行的平台。为了实现这个目的,GCC采用了机器描述文件,对目标机器的指令集、流水线结构、甚至架构本身带来的优化机会进行描述。对于目标机器(配置GCC时需指定),这些描述文件将被多个工具处理,生成相应的源代码,然后用于编译GCC。因此,引入新机器的主要工作,在于提供机器描述文件(一般而言还需要定义一些处理函数提供必要的处理逻辑)。

在以下地址,http://www.ibm.com/developerworks/cn/linux/l-gcc4/?ca=dwcn-newsletter-linux,可以找到关于GCC较新版本的信息。

前端

当我们调用 ”gcc –o xxx xxx.c” 时,事实上我们的请求会转交给一个shell,这个shell会解析这个调用命令,并根据它所能认识的命令行选项,在所运行的平台上进行相应的准备。然后根据源文件的后缀,调用相匹配的编译器,并将其不能识别的命令行选项一并传入。这里我们不关心这个shell,我们专注于真正的编译器。

1. 概览

前端读入源程序进行语法分析,然后将其转换为语言无关形式。理论上,每个前端可以使用独特的形式。不过在GCC中,为了尽可能地重用代码,不同前端生成的中间树都使用同一组树节点。显然,这组节点需要足够的多样性以满足不同语言的要求。这组树节点对于C/C++前端的意义非同寻常,我们首先需要对其着手。

1.1. 前端中树的表达形式

为了支持已经存在及以后将会加入的前端,GCC定义了数十种的树节点(不是所有节点都能作为叶子节点)。所有的节点,在其结构体定义中,以下面的结构作为第一个成员。

129 struct tree_common GTY(()) in tree.h

130 {

131 tree chain;

132 tree type;

133

134 ENUM_BITFIELD(tree_code) code : 8;

135

136 unsigned side_effects_flag : 1;

137 unsigned constant_flag : 1;

138 unsigned addressable_flag : 1;

139 unsigned volatile_flag : 1;

140 unsigned readonly_flag : 1;

141 unsigned unsigned_flag : 1;

142 unsigned asm_written_flag: 1;

143 unsigned unused_0 : 1;

144

145 unsigned used_flag : 1;

146 unsigned nothrow_flag : 1;

147 unsigned static_flag : 1;

148 unsigned public_flag : 1;

149 unsigned private_flag : 1;

150 unsigned protected_flag : 1;

151 unsigned deprecated_flag : 1;

152 unsigned unused_1 : 1;

153

154 unsigned lang_flag_0 : 1;

155 unsigned lang_flag_1 : 1;

156 unsigned lang_flag_2 : 1;

157 unsigned lang_flag_3 : 1;

158 unsigned lang_flag_4 : 1;

159 unsigned lang_flag_5 : 1;

160 unsigned lang_flag_6 : 1;

161 unsigned unused_2 : 1;

162 };

上面,在134行,在3.4.6版,ENUM_BITFIELD将被扩展为”__extension__ enum”, 131行的chain,如果需要,能将该节点链入树。

下面给出了,结构体中部分的标识域的含义,及访问它们的宏定义(红字部分)。

Ø TREE_TYPE ((NODE)->common.type)

用于所有表示表达式的节点,代表该表达式的数据类型。

² 在POINTER_TYPE节点中,代表该指针指向的类型。

² 在ARRAY_TYPE节点中,代表数组元素的类型。

² 在VECTOR_TYPE节点中,代表vector元素的类型(某些芯片的寄存器足够大,足以存放多个标量,这一组标量称为vector类型)。

Ø TREE_ADDRESSABLE((NODE)->common.addressable_flag)

² 在VAR_DECL节点中,非零值表示该节点代表的变量的地址,在程序的其他地方被使用,不能产生将该变量置入寄存器的代码。

² 在FUNCTION_DECL节点中,非零值表示该节点代表的函数的地址,在程序的其他地方被使用。该函数必须被编译出来即便它是内联函数(在被调用的地方,内联函数被展开为函数体,故不需要单独编译出来)。

² 在FIELD_DECL节点中,非零值表示该节点代表的成员的地址允许被程序员设定。彼时,该标识用于别名识别:参见函数record_component_aliases

² 在CONSTRUCTOR节点中,非零值表示该构造函数必须在内存中创建对象(而不是在寄存器中)。

² 在LABEL_DECL节点中,非零值表示一条目标为该lable的goto语句,出现在所有执行跳转到该label的goto语句,需要恢复栈的绑定域外。

² 在所有*_TYPE节点中,非零值表示该类型的所有对象必须是完全取址的。这意味着,例如:这些对象任何一部分都不能放入寄存器中。

² 在IDENTIFIER_NODE节点中,非零值表示某些同名的外部声明的地址已被引用。这关系到内联函数。

Ø TREE_STATIC ((NODE)->common.static_flag)

² 在VAR_DECL节点中,非零值表示该变量为静态类型。

² 在FUNCTION_DECL节点中,非零值表示该函数已被定义。

² In a CONSTRUCTOR, nonzero means allocate static storage.

Ø TREE_VIA_VIRTUAL ((NODE)->common.static_flag)

² 在TREE_LIST或TREE_VEC节点中,非零值表示所对应的类,其派生关系为虚继承(virtual)。

Ø TREE_CONSTANT_OVERFLOW ((NODE)->common.static_flag)

² 在INTEGER_CST,REAL_CST,COMPLEX_CST或 VECTOR_CST节点中,非零值表示在常量折叠中发生溢出。它与TREE_OVERFLOW的区别在于,如果常量表达式发生溢出,ANSI C要求给出诊断信息。

Ø TREE_SYMBOL_REFERENCED

(IDENTIFIER_NODE_CHECK (NODE)->common.static_flag)

² 在IDENTIFIER_NODE节点中,非零值表示该字串作为参数调用了函数assemble_name

Ø CLEANUP_EH_ONLY ((NODE)->common.static_flag)

² 在TARGET_EXPR,WITH_CLEANUP_EXPR,CLEANUP_STMT节点,或块(block)的清理(cleanup)链的节点中,非零值表示相关的清理只在异常抛出时执行,而在正常退出时不需要执行。

Ø TREE_OVERFLOW ((NODE)->common.public_flag)

² 在INTEGER_CST,REAL_CST,COMPLEX_CST或VECTOR_CST节点中,非零值表示在常量折叠中发生溢出,并且这部分的警告还没有发出。TREE_OVERFLOW同时意味着TREE_CONSTANT_OVERFLOW,但反之不成立。

Ø TREE_PUBLIC((NODE)->common.public_flag)

² 在VAR_DECL或FUNCTION_DECL节点中,非零值表示该名字可从模块(module)外访问。在IDENTIFIER_NODE节点中,非零值表示在域内(inner scope),已有该名字所代表的,能从模块(module)外访问的,外部链接性的声明(external declaration)。

Ø TREE_PRIVATE ((NODE)->common.private_flag)

² 在C++里,在类中使用。

Ø CALL_EXPR_HAS_RETURN_SLOT_ADDR ((NODE)->common.private_flag)

² 在CALL_EXPR节点中,非零值表示返回值的地址是参数链的一部分。

Ø TREE_PROTECTED ((NODE)->common.protected_flag)

² 在C++中用于类。在BLOCK节点中,这是个BLOCK_HANDLER_BLOCK节点。

Ø CALL_FROM_THUNK_P ((NODE)->common.protected_flag)

² 在CALL_EXPR节点中,非零值表示该函数调用是从thunk到thunk目标函数的跳转。

Ø TREE_SIDE_EFFECTS ((NODE)->common.side_effects_flag)

² 在所有的表达式中,非零值表示该表达式含有副作用(side effects)或者对其每次求值(reevaluation of the whole expression)将产生不同的值。如果其子表达式是一个函数调用、对一个volatile变量的引用或含有副作用,这个标识符将被设置。

² In a *_DECL, this is set only if the declaration said `volatile'.

Ø TREE_THIS_VOLATILE ((NODE)->common.volatile_flag)

² 非零值表示该表达式在C的含义下(in the C sense)是volatile:它的地址应该是类型`volatile WHATEVER *'。换而言之,所声明的项(item)是volatile修饰的(volatile qualified)。该标识符用于*_DECL和*_REF节点中。

² 在*_TYPE节点中,非零值表示其对应的类型为volatile修饰的(volatile-qualified)。不过如果节点代表一个类型,应该使用TYPE_VOLATILE而不是这个宏。因为,以后这2个宏会访问不同的位(现在是相同的,见下一条)。如果这个位被设上,TREE_SIDE_EFFECTS也应该同时被设上。

Ø TYPE_VOLATILE (TYPE_CHECK (NODE)->common.volatile_flag)

² 非零值表示该类型作为整体是volatile的。

Ø TREE_READONLY ((NODE)->common.readonly_flag)

² 在VAR_DECL,PARM_DECL或者FIELD_DECL,或者所有*_REF类型节点中,非零值表示它不能作为左值(the lhs of an assignment)。

² 在*_TYPE节点中,非零值表示其对应的类型为常量(const-qualified)类型(但是如果节点表示类型则应该使用宏TYPE_READONLY)。

Ø TYPE_READONLY (TYPE_CHECK (NODE)->common.readonly_flag)

² 非零值表示该节点代表的类型为常量类型。

Ø TREE_CONSTANT ((NODE)->common.constant_flag)

² 非零值表示表达式的值为常量。在所有的*_CST节点中设置。也可能出现在数学表达式中,ADDR_EXPR或者CONSTRUCTOR节点中,如果它们的值为常量。

Ø TREE_UNSIGNED ((NODE)->common.unsigned_flag)

² 在INTEGER_TYPE或者ENUMERAL_TYPE节点中,非零值表示其对应类型为无符号类型。在FIELD_DECL节点中,则表示为无符号域(unsigned bit field)。

Ø TREE_ASM_WRITTEN ((NODE)->common.asm_written_flag)

² 在VAR_DECL节点中表示汇编代码也被写入。

² 在FUNCTION_DECL节点中表示该函数已被编译。在内联函数中,这个位很有意义,因为内联函数可能不需要独立编译(作为函数)。

² 在RECORD_TYPE,UNION_TYPE,QUAL_UNION_TYPE或者ENUMERAL_TYPE节点中,表示有关类型的sdb调试信息已经写入。

² 在BLOCK节点中,非零值表示在这个分段(block)内,reorder_block节点已发现。

Ø TREE_USED ((NODE)->common.used_flag)

² 在*_DECL节点中表示,在其作用域中,相应的名字被引用(is used in its scope)。

² 在表达式节点中,表示如果其值未被使用,不发出警告。

² 在IDENTIFIER_NODE节点中,表示同名的外部声明已被引用(was used)。

Ø TREE_NOTHROW ((NODE)->common.nothrow_flag)

² 在FUNCTION_DECL节点中,表示该函数不会抛出异常。在CALL_EXPR节点中,则表示该次调用不会抛出异常。

Ø TYPE_ALIGN_OK (TYPE_CHECK (NODE)->common.nothrow_flag)

² 用在表示类型的节点中,非零值表示所有该类型的对象由语言或前端保证,被正确地对齐。因此,我们可以得知该类型的MEM节点(RTL节点)的对齐量不少于该类型的对齐量,尽管它可能看起来不是这样。在面向对象语言中,这种情况发生在一个要求更多对齐量量的变种类型的对象上(in object-oriented languages where a tag field may show this is an object of a more-aligned variant of the more generic type)。

Ø TREE_DEPRECATED ((NODE)->common.deprecated_flag)

² 在IDENTIFIER_NODE节点中,该名字的使用是被__attribute__((deprecated))所不推荐的。

列表 1:tree_common中的标识符

1.1.1. 树节点的定义

以下是中间树的节点的定义。

45 typedef union tree_node *tree; in coretypes.h

1772 union tree_node GTY ((ptr_alias (union lang_tree_node), intree.h

1773 desc ("tree_node_structure (&%h)")))

1774 {

1775 struct tree_common GTY ((tag ("TS_COMMON"))) common;

1776 struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;

1777 struct tree_real_cst GTY ((tag ("TS_REAL_CST"))) real_cst;

1778 struct tree_vector GTY ((tag ("TS_VECTOR"))) vector;

1779 struct tree_string GTY ((tag ("TS_STRING"))) string;

1780 struct tree_complex GTY ((tag ("TS_COMPLEX"))) complex;

1781 struct tree_identifier GTY ((tag ("TS_IDENTIFIER"))) identifier;

1782 struct tree_decl GTY ((tag ("TS_DECL"))) decl;

1783 struct tree_type GTY ((tag ("TS_TYPE"))) type;

1784 struct tree_list GTY ((tag ("TS_LIST"))) list;

1785 struct tree_vec GTY ((tag ("TS_VEC"))) vec;

1786 struct tree_exp GTY ((tag ("TS_EXP"))) exp;

1787 struct tree_block GTY ((tag ("TS_BLOCK"))) block;

1788 };

显然,这个节点需要定义为union。注意第1772行的ptr_alias,它告诉GTY (GCC的废料回收系统(garbage collection service),我们暂时不理会它),指向tree_node的指针实际上指向lang_tree_node,这个节点由前端通过tree_node追加额外的特定于语言的成员来定义(因此,它可被视为tree_node)。在C++前端中,lang_tree_node有如下定义:

472 union lang_tree_node GTY((desc ("cp_tree_node_structure (&%h)"), incp-tree.h

473 chain_next ("(union lang_tree_node *)TREE_CHAIN (&%h.generic)")))

474 {

475 union tree_node GTY ((tag ("TS_CP_GENERIC"),

476 desc ("tree_node_structure (&%h)"))) generic;

477 struct template_parm_index_s GTY ((tag ("TS_CP_TPI"))) tpi;

478 struct ptrmem_cst GTY ((tag ("TS_CP_PTRMEM"))) ptrmem;

479 struct tree_overload GTY ((tag ("TS_CP_OVERLOAD"))) overload;

480 struct tree_baselink GTY ((tag ("TS_CP_BASELINK"))) baselink;

481 struct tree_wrapper GTY ((tag ("TS_CP_WRAPPER"))) wrapper;

482 struct tree_default_arg GTY ((tag ("TS_CP_DEFAULT_ARG"))) default_arg;

483 struct lang_identifier GTY ((tag ("TS_CP_IDENTIFIER"))) identifier;

484 };


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics