


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