摘要:根據(jù)編譯技術(shù)的最新進(jìn)展及目前廣泛使用的各種編譯器框架,提出基于插件的編譯原理課程實(shí)驗(yàn)設(shè)計(jì)的思想與方法,解除后端實(shí)踐依賴于前端分析結(jié)果的限制,使學(xué)生能夠利用現(xiàn)有的編譯器框架直接進(jìn)行后端語(yǔ)義分析、代碼優(yōu)化和代碼生成等方面的實(shí)踐;同時(shí),文章介紹了基于插件的編譯原理課程實(shí)驗(yàn)設(shè)計(jì)的必要性、可能性,并以Phoenix編譯器框架為例說(shuō)明了該方案的可行性。
關(guān)鍵詞:編譯原理;課程實(shí)驗(yàn);插件;Phoenix
編譯程序各個(gè)邏輯功能之間具有較強(qiáng)的依賴性,如后端的語(yǔ)義分析和中間代碼生成、代碼優(yōu)化、目標(biāo)代碼生成等都依賴于前端的正確分析與處理。如果沒(méi)有前端的輸出,就無(wú)法進(jìn)行后續(xù)的加工和處理。考慮到編譯原理程序本身組織結(jié)構(gòu)的特點(diǎn)、教學(xué)學(xué)時(shí)限制和學(xué)生實(shí)踐動(dòng)手能力等因素,目前課程實(shí)踐環(huán)節(jié)普遍向編譯器前端靠攏[1-2]。即使設(shè)置了與后端相關(guān)的實(shí)驗(yàn),學(xué)生也往往無(wú)法完成。因此目前比較缺乏針對(duì)后端處理的、較為獨(dú)立的小規(guī)模課程實(shí)驗(yàn)供學(xué)生練習(xí),這勢(shì)必影響學(xué)生對(duì)編譯器整體性的掌握及對(duì)編譯器各部分有機(jī)關(guān)聯(lián)和接口的學(xué)習(xí)理解。
隨著計(jì)算機(jī)體系結(jié)構(gòu)的不斷發(fā)展,編譯技術(shù)也在不斷進(jìn)步和變化。為了快速對(duì)各種研究思想進(jìn)行驗(yàn)證,并縮短編譯相關(guān)研究成果與實(shí)際實(shí)現(xiàn)之間的轉(zhuǎn)換時(shí)間,各種供研究人員使用的編譯器框架平臺(tái)應(yīng)運(yùn)而生。例如,微軟公司推出的Phoenix編譯器框架[3]、開(kāi)源的Open64和GCC等。這些已有的編譯器框架能夠簡(jiǎn)化編譯程序的設(shè)計(jì)與實(shí)現(xiàn);同時(shí),為了支持編譯器的定制及相關(guān)理論的快速驗(yàn)證,有些編譯器框架(如Phoenix和GCC4.5)允許以插件的形式對(duì)部分處理階段進(jìn)行修改或者加強(qiáng)?;诓寮脑O(shè)計(jì)方法對(duì)于研究人員而言其價(jià)值是毋容置疑的,同時(shí)也為編譯原理課程實(shí)踐提供了便利。我們可以利用這些編譯框架提供的前端分析與識(shí)別功能,以及后端的部分處理能
力,設(shè)計(jì)針對(duì)語(yǔ)義分析與中間代碼生成、代碼優(yōu)化和目標(biāo)代碼生成相關(guān)的實(shí)驗(yàn)環(huán)節(jié),其好處在于:
1) 可以縮短實(shí)驗(yàn)完成所需要的時(shí)間,降低實(shí)驗(yàn)的難度,從而為教學(xué)目標(biāo)的完成奠定良好基礎(chǔ);
2) 能夠培養(yǎng)學(xué)生的科研能力和創(chuàng)新意識(shí),為使他們順利走上科研道路打下堅(jiān)實(shí)的基礎(chǔ);
3)基于插件的設(shè)計(jì)思想和技術(shù)也是目前許多大型軟件的普遍設(shè)計(jì)與實(shí)現(xiàn)方法,如Firefox、Eclipse和IDA Pro等。學(xué)生通過(guò)基于插件的課程實(shí)驗(yàn)?zāi)軌蚣訌?qiáng)學(xué)生對(duì)大型復(fù)雜軟件架構(gòu)、設(shè)計(jì)思想和實(shí)現(xiàn)方法等各個(gè)方面的認(rèn)識(shí),提升軟件工程管理和軟件設(shè)計(jì)水平。
目前清華大學(xué)“編譯原理專題訓(xùn)練”課程已經(jīng)將開(kāi)放源碼軟件GCC和Open64作為實(shí)驗(yàn)框架引入實(shí)踐教學(xué)[4],GCC 4.5及以上版本已經(jīng)實(shí)現(xiàn)了對(duì)插件設(shè)計(jì)的支持。筆者僅以微軟的Phoenix為例詳細(xì)說(shuō)明基于插件的實(shí)驗(yàn)設(shè)計(jì)的可行性。
1Phoenix編譯框架
Phoenix是由微軟公司新推出的用于構(gòu)造編譯程序,各種程序分析、優(yōu)化和測(cè)試工具的一個(gè)基礎(chǔ)框架。Phoenix編譯器框架主要功能包括:
1)Phoenix是一個(gè)編譯器。該編譯器有著與其他編譯器相似的功能,能夠?qū)⒃创a編譯為二進(jìn)制代碼。
2) 是一個(gè)編譯器開(kāi)發(fā)工具。由于Phoenix采用了統(tǒng)一的中間形式,編譯器開(kāi)發(fā)者只需將新語(yǔ)言的源程序轉(zhuǎn)化為這種中間形式,然后就可利用Phoenix后端工具完成中間語(yǔ)言的轉(zhuǎn)化、優(yōu)化以及二進(jìn)制代碼的生成。
3)Phoenix作為一個(gè)框架,同時(shí)還是可插接的(Plug-in),Phoenix包含一些API,使用這些API能編寫利用Phoenix特性的工具。
Phoenix體系結(jié)構(gòu)具有高度的可伸縮性,使得開(kāi)發(fā)者或研究人員能夠在該體系結(jié)構(gòu)上開(kāi)發(fā)各種各樣的編譯器以及分析優(yōu)化工具。在Phoenix中,遍(Pass)和階段(Phase)是兩個(gè)極重要的概念,也是支持插件式設(shè)計(jì)的主要結(jié)構(gòu)。Phoenix支持多遍處理,因此后端由多個(gè)Pass構(gòu)成,而且允許使用者插入自己的Pass,用于特定處理。一個(gè)函數(shù)的分析過(guò)程可以劃分為若干階段,每個(gè)階段的處理對(duì)應(yīng)一個(gè)Phase。使用者可以插入、刪除一個(gè)Phase或重新排列原有的Phase,Pass和Phase的關(guān)系如圖1所示。
2基于插件的課程實(shí)驗(yàn)設(shè)計(jì)
插件是指能夠被Phoenix核心編譯模塊(C2.exe)調(diào)用的外部模塊。假設(shè)設(shè)計(jì)了一個(gè)名為MyPlugin.dll的插件,當(dāng)使用命令行選項(xiàng)-d2plugin:MyPlugin.dll啟動(dòng)C2時(shí),C2就會(huì)在編譯的過(guò)程裝載并執(zhí)行MyPlugin.dll中的代碼。
如圖2所示,源代碼程序經(jīng)過(guò)前端C1.exe的分析和處理之后,然后交給C2中的各個(gè)Pass和Phase進(jìn)行處理。當(dāng)C2運(yùn)行時(shí),Myplugin能夠訪問(wèn)C2內(nèi)部的所有數(shù)據(jù)結(jié)構(gòu),因此,通過(guò)MyPlugin能夠改變C2的行為,如增加新的Phase,旁路已經(jīng)存在的Phase,或者替換可選的Phase等。例如,Myplugin可以提供一個(gè)寄存器分配Phase替換掉C2中已有的部分,可以向被編譯的每個(gè)函數(shù)中插入一些其他的代碼,輸出某個(gè)函數(shù)編譯所形成的IR等。
編寫一個(gè)Phoenix的插件非常簡(jiǎn)單,例如,我們想輸出被編譯的每個(gè)函數(shù)的名字,則需要編寫一個(gè)插件FuncNames。為此,首先定義一個(gè)實(shí)現(xiàn)PlugIn接口的類MyPlugIn,作為插件FuncNames與C2.exe交互的接口。MyPlugIn必須實(shí)現(xiàn)PlugIn中的兩個(gè)接口:RegisterObjects和BuildPhase。Phoenix編譯框架在裝載插件之后調(diào)用RegisterObjects并注冊(cè)插件對(duì)命令行選項(xiàng)進(jìn)行處理。本例中不支持任何命令行命令,所以該接口的實(shí)現(xiàn)為空。BuildPhase接口有一個(gè)PhaseConfiguration類型的參數(shù),這個(gè)參數(shù)是C2.exe提供給插件的,插件中的代碼通過(guò)這個(gè)參數(shù)能夠訪問(wèn)C2中的Phase列表。本例中我們只需要?jiǎng)?chuàng)建一個(gè)新的Phase實(shí)例并將其插入到列表中合適的位置就可以了,程序代碼如下:
class MyPlugIn : Phx::PlugIn{
...
virtual void RegisterObjects() override;
virtual void BuildPhases ( Phx::Phases:: PhaseConfiguration ^ config ) override;
...
};
void MyPlugIn::RegisterObjects() {}
void MyPlugIn::BuildPhases( Phx::Phases::PhaseConfiguration ^ config) {
Phx::Phases::Phase ^ encodingPhase;
Phx::Phases::Phase ^ funcNamesPhase;
encodingPhase = config->PhaseList->FindByName ("Encoding");
funcNamesPhase = MyPhase::New(config);
encodingPhase->InsertBefore(funcNamesPhase);
}
此外我們需要?jiǎng)?chuàng)建的一個(gè)新的Phase類,該類是從父類Phase繼承而來(lái),且其必須實(shí)現(xiàn)父類的兩個(gè)方法New和Execute。New是在前述的BuildPhases方法中調(diào)用的,主要作用是構(gòu)造和初始化MyPhase對(duì)象;而Execute是C2編譯每個(gè)方法時(shí)都會(huì)調(diào)用的方法,實(shí)際完成函數(shù)名輸出的方法,程序代碼如下:
class MyPhase : Phx::Phases::Phase{
...
static Phx::Phases::Phase ^New ( Phx:: Phases::PhaseConfiguration ^ config );
virtual voidExecute (Phx::Unit ^ unit ) override;
};
Phx::Phases::Phase ^ MyPhase::New( Phx:: Phases::PhaseConfiguration ^ config) {
Phase ^ phase = gcnew MyPhase();
phase->Initialize(con