新闻中心

EEPW首页 > 探索 GCC 前端的内部结构(三)

探索 GCC 前端的内部结构(三)

——
作者:时间:2007-04-17来源:嵌入开发网收藏
对用户源文件进行语法分析

这个 treelang 注册的这些回调函数在 GCC 主框架那里被调用的顺序,我们暂时还不想深入。拣有意思的先看看吧。首先关注的是 treelang_parse_file 这个函数。在 langhooks.h 里面关于这个回调函数所作的注释说明,是要它对用户的整个源文件进行语法分析。因为这个函数的返回值是 void 所以我们预期它是通过设置某一个全局变量来完成任务的;但是也有另外一种可能,就是它会把所有要做的事情都给做完,这样它也就自然不需要返回值了。这两种可能我们现在还不能确定。让我们往下看吧。 

这个 treelang_parse_file 函数在 tree1.c 中定义,这是属于到 GCC 前端的接口。它直接就跑去调用 yyparse 这个 YACC 主函数了。这倒是简单,呵呵。可是要我们从 parse.y 文件中理出个头绪来,这个文件有超过 900 行的 YACC 代码,未免有点麻烦。最关键的是这中间数据的交流不大容易看清楚,不像回调函数指针这样显而易见。如果程序果真是通过设置一些全局变量来完成任务的话,我们的分析任务就有点棘手了。 

注释开始::::: 

在这里先说一下 tree 这个数据结构。这是 GCC 围绕着 C 和 C++ 语言的语法分析,用到的主要数据结构。所有其它语言的前端,也都需要在语法分析阶段结束以后,为 GCC 生成相应的 tree 结构的数据。然后 GCC 的后端就可以从 tree 生成独立于平台的 RTL 数据结构,并随后生成相应平台上的机器语言代码。所以作为 GCC 的前端,这里的主要工作就是从一个文本文件,也就是源代码,生成这个 tree 结构的数据,喂给的后端。我们看到,前端是依赖于编程语言的;后端是依赖于机器平台的;中间的 tree 和 RTL 则独立于编程语言和机器平台。但是话虽如此说,这个 tree 和 RTL 数据结构也还是主要以 C 和 C++ 语言为考虑问题的中心。这是不可避免的事情。 

:::::注释结束 

好啦,没办法啦。我们这就开始从 treelang 目录下的 parse.y 一行一行的往下瞅吧。这个 Treelang 程序语言的语法很简单,我们看到哪儿,说到哪儿。 

注释开始::::: 

在看 GCC 的源代码的时候经常会遇到 GTY 这个东西。这是 GCC 内部的内存管理机制所需要的,在 C 语言代码上添加的一些类型信息,这些类型信息在 GCC 内部做垃圾收集的时候会用到。这个细节我们这里先忽略过去,以后讲到相关内容的时候再做说明。 

:::::注释结束 

在 parse.y 中的一些主要的产生式上所匹配的 C 函数,它们所做的工作大体上都是首先根据语法分析的结果,把自己定义的结构 struct prod_token_parm_item 里面的数据先给设置好;然后根据情况调用在 treetree.c 中定义的相关函数,生成 tree 结构的数据;这之后再把返回来的 tree 结构数据记录在 struct prod_token_parm_item 里面,并把整个结构的数据放到 symbol_table 这个单向链表上。这样看来,似乎这个 symbol_table 就是我们前面所要寻找的全局变量了。是不是在语法分析任务完成以后,就获得了这个全局变量;然后依赖于这个全局变量,后续任务才得以获得输入数据,继续往下执行呢? 

我们来仔细看一看 tree1.c 中这个 symbol_table 变量的定义如下。 



static GTY(()) struct prod_token_parm_item *symbol_table = NULL;

 



注意到这是被申明为 static 的变量。在 Samuel P. Harbison III 和 Guy L. Steele, Jr 所合著的 C: A Reference Manual 的英文版的第五版第八十三页上,关于 static 变量有如下说明:"On data declarations, it always signifies a defining declaration that is not exported to the linker."换句话说,这个 static 的 symbol_table 变量,在 tree1.o 之外是看不见的。这不可能是我们所要寻找的全局变量。 

可是,另一方面,除了这个变量有点像是那么一回事之外,其它的就再也没有什么有趣的变量了。这是怎么一回事呢?我们先不管它,往下看了再说吧。 

那么这个 parse.y 文件大体如是啦。其它的一些具体的细节问题,牵涉到 Treelang 程序语言的具体定义,暂且不是我们的兴趣所在。粗粗的看一遍下来,这个语法分析的过程,从 GCC 的主体结构上,经由 lang_hooks 进入 treelang 部分的 yyparse 函数,这个函数按照语法定义,把编译器用户输入的 Treelang 语言的源程序分解成若干类型的小块,加以分析,生成自己定义的 struct prod_token_parm_item 结构的数据,再把这些数据一个一个串到 symbol_table 这个链表上面;这样就算完成任务了。线索从 lang_hooks 中定义的这个回调函数撤出,再度回到 GCC 的主体框架。 

对了,上面还忘了说,在把用户输入的 Treelang 语言的源程序进行分解以后,在分析的过程中,按照各种类型的小块,还生成了相应的 tree 结构的数据,一起记录在各自的 struct prod_token_parm_item 结构里面,这样就一并把这个 tree 结构的数据也都放在了 symbol_table 这个链表里了。 

接下来回到 GCC 的主体框架上的 toplev.c 文件。可是迷惑人的事情出现了,在函数 compile_file 对回调函数 treelang_parse_file 进行调用之后,无论是在 toplev.c 文件中,还是说在哪一个其它的回调函数里也好,似乎都并没有什么有趣的事情发生了。这让我们如何是好?看来我们只有回过头去仔细跟踪 treelang 目录下的 treetree.c 文件中的那些函数,看看它们在被 parse.y 中的产生式调用执行的时候,到底干了些什么。

语法分析的细节

根据从 parse.y 这个 YACC 文件中的产生式得来的线索,我们首先关注 treetree.c 文件中的 tree_code_create_variable 这个函数。从那个 YACC 产生式,我们估计这个函数是为一个变量申明而构造必要的 tree 数据结构。这个函数有 100 行不到的源代码。我们来仔细的看一看。这个函数使用了从 GCC 的框架结构里面来的关于 tree 数据结构的一些 API 接口。我们目前所最感兴趣的,就是这个函数在利用这些接口函数构造一个和所对应的 YACC 产生式相当的 tree 结构数据以外,还干了些什么。我们之所以关心这个"以外",是因为目前我们最想了解的,是这个从 Treelang 语言的源程序开始,到一连串的 tree 结构数据,然后是怎么变成 RTL 结构的数据的。只有在有了这样一个概观以后,我们对 GCC 前端的编写方法才能算有了一个初步的大概的了解。 

根据这样的思路,我们很快就看清楚,在这个 tree_code_create_variable 函数中,在设置好若干个局部的 tree 结构的数据以后,引人注目的在一个 if 语句的分支中调用了 rest_of_decl_compilation 这个函数。而且在这个函数被调用返回以后,似乎不再有重要的事情发生了。这个函数来自于 GCC 框架结构上的 toplev.c 文件。这样的话,根据我们前面的分析,这个函数里面应该会隐藏有我们的主要问题的答案。也就是说,在 YACC 文件 parse.y 把用户提供的 Treelang 语言的源文件肢解以后,在 treetree.c 中的相应的函数,为之生成了相应的 tree 结构数据,而在现在我们所关注的这个 rest_of_decl_compilation 函数(以及在这个 if 语句的另一个分支中出现的一系列相应的函数)中,应该会完成从 tree 结构的数据到 RTL 数据的翻译。 

从另一个角度补充一点,程序的执行线索是如何从 GCC 主框架进入 parse.y 中的呢?这一段我们前面分析过了,现在再来提醒一下。这是从 GCC 的框架结构,进入到 treelang 这个 GCC 的语言前端模块注册的 lang_hooks 结构的数据,找到相应的回调函数,最终找到 parse.y 这个 YACC 程序的入口 yyparse 函数的。在 yyparse 之后,我们看到程序的主线索进入了 treelang 目录下的 treetree.c 文件中的函数。最后,我们重新又追踪到 GCC 主体部分的 toplev.c 文件中的函数。现在我们的整个图景的大轮廓就快要完全弄清楚了。

GCC 前端的全景图

终于,我们在 rest_of_decl_compilation 函数中,看到了一系列的和 RTL 相关的函数调用。稍微仔细的看了一遍之后,我们有把握得出这个结论了。我们在本文的开头部分,曾经猜想 GCC 的主体部分在要求 GCC 这个 Treelang 语言前端从用户提供的 Treelang 语言的源程序文本,经过语法分析,得出相应的 tree 结构数据以后,会把这个数据通过函数返回值传回给 GCC 的主体程序,或者设置一个全局变量,这样就算完成任务了。但是事实上,经过我们上面的分析,发现不是这么一回事。 

相反的,在 Treelang 这个语言前端得到需要的 tree 结构的数据以后,继续往下的运行,这完全是 Treelang 前端必须自己负责的任务。这个 GCC 前端必须自己调用 GCC 主体部分提供的,用来从 tree 结构数据生成 RTL 结构数据的函数接口,以完成从 tree 结构数据到 RTL 结构数据的翻译过程。这样,这个 GCC 的语言前端的任务才算完成。换句话说,GCC 的这个语言前端承担的角色是非常的主动的。很明显,这样的设计提供给我们极大的灵活性。关于这一点,我们以后会逐渐看到。 

小结

本文限于篇幅,只大略讲述了 GCC 前端的框架结构,给出了一个粗略的全景图。在以后的几篇文章中,我们将进一步探索 GCC 的主体部分为 GCC 前端所提供的 API 函数和数据结构。并利用这些知识,探索一下为 GCC 编写一个 Scheme 语言前端的可能性。在这一系列文章结束的时候,希望能使得读者朋友们对 GCC 以及程序语言的本质有一个更加深刻的了解。也希望 GCC 的前端的作者人数,就能和 Linux 内核模块的作者人数一样多。我们的座右铭是:每一个人都是程序员;每一个人都能加载自己编写的内核模块;每一个人都能使用自己实现的编程语言!(不要害怕,这只是一句玩笑话。呵呵。) 

在技术内容以外,本文也探索了开放源码运动所需要的技术文档的一种写作模式。开放源码运动为我们带来了大量的自由软件的源程序。对于用户来说,需要文档讲述如何使用这些自由软件;对于程序员来说,则需要文档讲述如何才能理解并真正的掌握这些自由软件的源程序。这第二种文档的写作,不是一件容易的事情。作者本人在经常阅读解释自由软件的源程序的内部运作机理的文档的过程中,总是觉得这件事情应该可以有办法做的更好。本文就是作者的一个尝试。希望读者朋友们给我来信,不仅仅讨论 GCC 的技术问题,也欢迎对作者的写作方式提出批评与指教!
 

-------------------------------------------------------------------------------- 
 


评论


相关推荐

技术专区

关闭