探索 GCC 前端的內(nèi)部結(jié)構(gòu)(三)
——
這個(gè) treelang 注冊(cè)的這些回調(diào)函數(shù)在 GCC 主框架那里被調(diào)用的順序,我們暫時(shí)還不想深入。揀有意思的先看看吧。首先關(guān)注的是 treelang_parse_file 這個(gè)函數(shù)。在 langhooks.h 里面關(guān)于這個(gè)回調(diào)函數(shù)所作的注釋說(shuō)明,是要它對(duì)用戶的整個(gè)源文件進(jìn)行語(yǔ)法分析。因?yàn)檫@個(gè)函數(shù)的返回值是 void 所以我們預(yù)期它是通過(guò)設(shè)置某一個(gè)全局變量來(lái)完成任務(wù)的;但是也有另外一種可能,就是它會(huì)把所有要做的事情都給做完,這樣它也就自然不需要返回值了。這兩種可能我們現(xiàn)在還不能確定。讓我們往下看吧。
這個(gè) treelang_parse_file 函數(shù)在 tree1.c 中定義,這是屬于到 GCC 前端的接口。它直接就跑去調(diào)用 yyparse 這個(gè) YACC 主函數(shù)了。這倒是簡(jiǎn)單,呵呵??墒且覀儚?nbsp;parse.y 文件中理出個(gè)頭緒來(lái),這個(gè)文件有超過(guò) 900 行的 YACC 代碼,未免有點(diǎn)麻煩。最關(guān)鍵的是這中間數(shù)據(jù)的交流不大容易看清楚,不像回調(diào)函數(shù)指針這樣顯而易見(jiàn)。如果程序果真是通過(guò)設(shè)置一些全局變量來(lái)完成任務(wù)的話,我們的分析任務(wù)就有點(diǎn)棘手了。
注釋開(kāi)始:::::
在這里先說(shuō)一下 tree 這個(gè)數(shù)據(jù)結(jié)構(gòu)。這是 GCC 圍繞著 C 和 C++ 語(yǔ)言的語(yǔ)法分析,用到的主要數(shù)據(jù)結(jié)構(gòu)。所有其它語(yǔ)言的編譯器前端,也都需要在語(yǔ)法分析階段結(jié)束以后,為 GCC 生成相應(yīng)的 tree 結(jié)構(gòu)的數(shù)據(jù)。然后 GCC 的后端就可以從 tree 生成獨(dú)立于平臺(tái)的 RTL 數(shù)據(jù)結(jié)構(gòu),并隨后生成相應(yīng)平臺(tái)上的機(jī)器語(yǔ)言代碼。所以作為 GCC 的編譯器前端,這里的主要工作就是從一個(gè)文本文件,也就是源代碼,生成這個(gè) tree 結(jié)構(gòu)的數(shù)據(jù),喂給編譯器的后端。我們看到,前端是依賴于編程語(yǔ)言的;后端是依賴于機(jī)器平臺(tái)的;中間的 tree 和 RTL 則獨(dú)立于編程語(yǔ)言和機(jī)器平臺(tái)。但是話雖如此說(shuō),這個(gè) tree 和 RTL 數(shù)據(jù)結(jié)構(gòu)也還是主要以 C 和 C++ 語(yǔ)言為考慮問(wèn)題的中心。這是不可避免的事情。
:::::注釋結(jié)束
好啦,沒(méi)辦法啦。我們這就開(kāi)始從 treelang 目錄下的 parse.y 一行一行的往下瞅吧。這個(gè) Treelang 程序語(yǔ)言的語(yǔ)法很簡(jiǎn)單,我們看到哪兒,說(shuō)到哪兒。
注釋開(kāi)始:::::
在看 GCC 的源代碼的時(shí)候經(jīng)常會(huì)遇到 GTY 這個(gè)東西。這是 GCC 內(nèi)部的內(nèi)存管理機(jī)制所需要的,在 C 語(yǔ)言代碼上添加的一些類(lèi)型信息,這些類(lèi)型信息在 GCC 內(nèi)部做垃圾收集的時(shí)候會(huì)用到。這個(gè)細(xì)節(jié)我們這里先忽略過(guò)去,以后講到相關(guān)內(nèi)容的時(shí)候再做說(shuō)明。
:::::注釋結(jié)束
在 parse.y 中的一些主要的產(chǎn)生式上所匹配的 C 函數(shù),它們所做的工作大體上都是首先根據(jù)語(yǔ)法分析的結(jié)果,把自己定義的結(jié)構(gòu) struct prod_token_parm_item 里面的數(shù)據(jù)先給設(shè)置好;然后根據(jù)情況調(diào)用在 treetree.c 中定義的相關(guān)函數(shù),生成 tree 結(jié)構(gòu)的數(shù)據(jù);這之后再把返回來(lái)的 tree 結(jié)構(gòu)數(shù)據(jù)記錄在 struct prod_token_parm_item 里面,并把整個(gè)結(jié)構(gòu)的數(shù)據(jù)放到 symbol_table 這個(gè)單向鏈表上。這樣看來(lái),似乎這個(gè) symbol_table 就是我們前面所要尋找的全局變量了。是不是在語(yǔ)法分析任務(wù)完成以后,就獲得了這個(gè)全局變量;然后依賴于這個(gè)全局變量,后續(xù)任務(wù)才得以獲得輸入數(shù)據(jù),繼續(xù)往下執(zhí)行呢?
我們來(lái)仔細(xì)看一看 tree1.c 中這個(gè) 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 的英文版的第五版第八十三頁(yè)上,關(guān)于 static 變量有如下說(shuō)明:"On data declarations, it always signifies a defining declaration that is not exported to the linker."換句話說(shuō),這個(gè) static 的 symbol_table 變量,在 tree1.o 之外是看不見(jiàn)的。這不可能是我們所要尋找的全局變量。
可是,另一方面,除了這個(gè)變量有點(diǎn)像是那么一回事之外,其它的就再也沒(méi)有什么有趣的變量了。這是怎么一回事呢?我們先不管它,往下看了再說(shuō)吧。
那么這個(gè) parse.y 文件大體如是啦。其它的一些具體的細(xì)節(jié)問(wèn)題,牽涉到 Treelang 程序語(yǔ)言的具體定義,暫且不是我們的興趣所在。粗粗的看一遍下來(lái),這個(gè)語(yǔ)法分析的過(guò)程,從 GCC 的主體結(jié)構(gòu)上,經(jīng)由 lang_hooks 進(jìn)入 treelang 部分的 yyparse 函數(shù),這個(gè)函數(shù)按照語(yǔ)法定義,把編譯器用戶輸入的 Treelang 語(yǔ)言的源程序分解成若干類(lèi)型的小塊,加以分析,生成自己定義的 struct prod_token_parm_item 結(jié)構(gòu)的數(shù)據(jù),再把這些數(shù)據(jù)一個(gè)一個(gè)串到 symbol_table 這個(gè)鏈表上面;這樣就算完成任務(wù)了。線索從 lang_hooks 中定義的這個(gè)回調(diào)函數(shù)撤出,再度回到 GCC 的主體框架。
對(duì)了,上面還忘了說(shuō),在把用戶輸入的 Treelang 語(yǔ)言的源程序進(jìn)行分解以后,在分析的過(guò)程中,按照各種類(lèi)型的小塊,還生成了相應(yīng)的 tree 結(jié)構(gòu)的數(shù)據(jù),一起記錄在各自的 struct prod_token_parm_item 結(jié)構(gòu)里面,這樣就一并把這個(gè) tree 結(jié)構(gòu)的數(shù)據(jù)也都放在了 symbol_table 這個(gè)鏈表里了。
接下來(lái)回到 GCC 的主體框架上的 toplev.c 文件??墒敲曰笕说氖虑槌霈F(xiàn)了,在函數(shù) compile_file 對(duì)回調(diào)函數(shù) treelang_parse_file 進(jìn)行調(diào)用之后,無(wú)論是在 toplev.c 文件中,還是說(shuō)在哪一個(gè)其它的回調(diào)函數(shù)里也好,似乎都并沒(méi)有什么有趣的事情發(fā)生了。這讓我們?nèi)绾问呛??看?lái)我們只有回過(guò)頭去仔細(xì)跟蹤 treelang 目錄下的 treetree.c 文件中的那些函數(shù),看看它們?cè)诒?nbsp;parse.y 中的產(chǎn)生式調(diào)用執(zhí)行的時(shí)候,到底干了些什么。
語(yǔ)法分析的細(xì)節(jié)
根據(jù)從 parse.y 這個(gè) YACC 文件中的產(chǎn)生式得來(lái)的線索,我們首先關(guān)注 treetree.c 文件中的 tree_code_create_variable 這個(gè)函數(shù)。從那個(gè) YACC 產(chǎn)生式,我們估計(jì)這個(gè)函數(shù)是為一個(gè)變量申明而構(gòu)造必要的 tree 數(shù)據(jù)結(jié)構(gòu)。這個(gè)函數(shù)有 100 行不到的源代碼。我們來(lái)仔細(xì)的看一看。這個(gè)函數(shù)使用了從 GCC 的框架結(jié)構(gòu)里面來(lái)的關(guān)于 tree 數(shù)據(jù)結(jié)構(gòu)的一些 API 接口。我們目前所最感興趣的,就是這個(gè)函數(shù)在利用這些接口函數(shù)構(gòu)造一個(gè)和所對(duì)應(yīng)的 YACC 產(chǎn)生式相當(dāng)?shù)?nbsp;tree 結(jié)構(gòu)數(shù)據(jù)以外,還干了些什么。我們之所以關(guān)心這個(gè)"以外",是因?yàn)槟壳拔覀冏钕肓私獾?,是這個(gè)從 Treelang 語(yǔ)言的源程序開(kāi)始,到一連串的 tree 結(jié)構(gòu)數(shù)據(jù),然后是怎么變成 RTL 結(jié)構(gòu)的數(shù)據(jù)的。只有在有了這樣一個(gè)概觀以后,我們對(duì) GCC 前端的編寫(xiě)方法才能算有了一個(gè)初步的大概的了解。
根據(jù)這樣的思路,我們很快就看清楚,在這個(gè) tree_code_create_variable 函數(shù)中,在設(shè)置好若干個(gè)局部的 tree 結(jié)構(gòu)的數(shù)據(jù)以后,引人注目的在一個(gè) if 語(yǔ)句的分支中調(diào)用了 rest_of_decl_compilation 這個(gè)函數(shù)。而且在這個(gè)函數(shù)被調(diào)用返回以后,似乎不再有重要的事情發(fā)生了。這個(gè)函數(shù)來(lái)自于 GCC 框架結(jié)構(gòu)上的 toplev.c 文件。這樣的話,根據(jù)我們前面的分析,這個(gè)函數(shù)里面應(yīng)該會(huì)隱藏有我們的主要問(wèn)題的答案。也就是說(shuō),在 YACC 文件 parse.y 把用戶提供的 Treelang 語(yǔ)言的源文件肢解以后,在 treetree.c 中的相應(yīng)的函數(shù),為之生成了相應(yīng)的 tree 結(jié)構(gòu)數(shù)據(jù),而在現(xiàn)在我們所關(guān)注的這個(gè) rest_of_decl_compilation 函數(shù)(以及在這個(gè) if 語(yǔ)句的另一個(gè)分支中出現(xiàn)的一系列相應(yīng)的函數(shù))中,應(yīng)該會(huì)完成從 tree 結(jié)構(gòu)的數(shù)據(jù)到 RTL 數(shù)據(jù)的翻譯。
從另一個(gè)角度補(bǔ)充一點(diǎn),程序的執(zhí)行線索是如何從 GCC 主框架進(jìn)入 parse.y 中的呢?這一段我們前面分析過(guò)了,現(xiàn)在再來(lái)提醒一下。這是從 GCC 的框架結(jié)構(gòu),進(jìn)入到 treelang 這個(gè) GCC 的語(yǔ)言前端模塊注冊(cè)的 lang_hooks 結(jié)構(gòu)的數(shù)據(jù),找到相應(yīng)的回調(diào)函數(shù),最終找到 parse.y 這個(gè) YACC 程序的入口 yyparse 函數(shù)的。在 yyparse 之后,我們看到程序的主線索進(jìn)入了 treelang 目錄下的 treetree.c 文件中的函數(shù)。最后,我們重新又追蹤到 GCC 主體部分的 toplev.c 文件中的函數(shù)?,F(xiàn)在我們的整個(gè)圖景的大輪廓就快要完全弄清楚了。
GCC 前端的全景圖
終于,我們?cè)?nbsp;rest_of_decl_compilation 函數(shù)中,看到了一系列的和 RTL 相關(guān)的函數(shù)調(diào)用。稍微仔細(xì)的看了一遍之后,我們有把握得出這個(gè)結(jié)論了。我們?cè)诒疚牡拈_(kāi)頭部分,曾經(jīng)猜想 GCC 的主體部分在要求 GCC 這個(gè) Treelang 語(yǔ)言前端從用戶提供的 Treelang 語(yǔ)言的源程序文本,經(jīng)過(guò)語(yǔ)法分析,得出相應(yīng)的 tree 結(jié)構(gòu)數(shù)據(jù)以后,會(huì)把這個(gè)數(shù)據(jù)通過(guò)函數(shù)返回值傳回給 GCC 的主體程序,或者設(shè)置一個(gè)全局變量,這樣就算完成任務(wù)了。但是事實(shí)上,經(jīng)過(guò)我們上面的分析,發(fā)現(xiàn)不是這么一回事。
相反的,在 Treelang 這個(gè)語(yǔ)言前端得到需要的 tree 結(jié)構(gòu)的數(shù)據(jù)以后,繼續(xù)往下的運(yùn)行,這完全是 Treelang 前端必須自己負(fù)責(zé)的任務(wù)。這個(gè) GCC 前端必須自己調(diào)用 GCC 主體部分提供的,用來(lái)從 tree 結(jié)構(gòu)數(shù)據(jù)生成 RTL 結(jié)構(gòu)數(shù)據(jù)的函數(shù)接口,以完成從 tree 結(jié)構(gòu)數(shù)據(jù)到 RTL 結(jié)構(gòu)數(shù)據(jù)的翻譯過(guò)程。這樣,這個(gè) GCC 的語(yǔ)言前端的任務(wù)才算完成。換句話說(shuō),GCC 的這個(gè)語(yǔ)言前端承擔(dān)的角色是非常的主動(dòng)的。很明顯,這樣的設(shè)計(jì)提供給我們極大的靈活性。關(guān)于這一點(diǎn),我們以后會(huì)逐漸看到。
小結(jié)
本文限于篇幅,只大略講述了 GCC 前端的框架結(jié)構(gòu),給出了一個(gè)粗略的全景圖。在以后的幾篇文章中,我們將進(jìn)一步探索 GCC 的主體部分為 GCC 前端所提供的 API 函數(shù)和數(shù)據(jù)結(jié)構(gòu)。并利用這些知識(shí),探索一下為 GCC 編寫(xiě)一個(gè) Scheme 語(yǔ)言前端的可能性。在這一系列文章結(jié)束的時(shí)候,希望能使得讀者朋友們對(duì) GCC 以及程序語(yǔ)言的本質(zhì)有一個(gè)更加深刻的了解。也希望 GCC 的前端的作者人數(shù),就能和 Linux 內(nèi)核模塊的作者人數(shù)一樣多。我們的座右銘是:每一個(gè)人都是程序員;每一個(gè)人都能加載自己編寫(xiě)的內(nèi)核模塊;每一個(gè)人都能使用自己實(shí)現(xiàn)的編程語(yǔ)言?。ú灰ε拢@只是一句玩笑話。呵呵。)
在技術(shù)內(nèi)容以外,本文也探索了開(kāi)放源碼運(yùn)動(dòng)所需要的技術(shù)文檔的一種寫(xiě)作模式。開(kāi)放源碼運(yùn)動(dòng)為我們帶來(lái)了大量的自由軟件的源程序。對(duì)于用戶來(lái)說(shuō),需要文檔講述如何使用這些自由軟件;對(duì)于程序員來(lái)說(shuō),則需要文檔講述如何才能理解并真正的掌握這些自由軟件的源程序。這第二種文檔的寫(xiě)作,不是一件容易的事情。作者本人在經(jīng)常閱讀解釋自由軟件的源程序的內(nèi)部運(yùn)作機(jī)理的文檔的過(guò)程中,總是覺(jué)得這件事情應(yīng)該可以有辦法做的更好。本文就是作者的一個(gè)嘗試。希望讀者朋友們給我來(lái)信,不僅僅討論 GCC 的技術(shù)問(wèn)題,也歡迎對(duì)作者的寫(xiě)作方式提出批評(píng)與指教!
--------------------------------------------------------------------------------
評(píng)論