基于插件的编译原理课程实验设计

时间:2022-07-14 06:18:23

基于插件的编译原理课程实验设计

摘要:根据编译技术的最新进展及目前广泛使用的各种编译器框架,提出基于插件的编译原理课程实验设计的思想与方法,解除后端实践依赖于前端分析结果的限制,使学生能够利用现有的编译器框架直接进行后端语义分析、代码优化和代码生成等方面的实践;同时,文章介绍了基于插件的编译原理课程实验设计的必要性、可能性,并以Phoenix编译器框架为例说明了该方案的可行性。

关键词:编译原理;课程实验;插件;Phoenix

编译程序各个逻辑功能之间具有较强的依赖性,如后端的语义分析和中间代码生成、代码优化、目标代码生成等都依赖于前端的正确分析与处理。如果没有前端的输出,就无法进行后续的加工和处理。考虑到编译原理程序本身组织结构的特点、教学学时限制和学生实践动手能力等因素,目前课程实践环节普遍向编译器前端靠拢[1-2]。即使设置了与后端相关的实验,学生也往往无法完成。因此目前比较缺乏针对后端处理的、较为独立的小规模课程实验供学生练习,这势必影响学生对编译器整体性的掌握及对编译器各部分有机关联和接口的学习理解。

随着计算机体系结构的不断发展,编译技术也在不断进步和变化。为了快速对各种研究思想进行验证,并缩短编译相关研究成果与实际实现之间的转换时间,各种供研究人员使用的编译器框架平台应运而生。例如,微软公司推出的Phoenix编译器框架[3]、开源的Open64和GCC等。这些已有的编译器框架能够简化编译程序的设计与实现;同时,为了支持编译器的定制及相关理论的快速验证,有些编译器框架(如Phoenix和GCC4.5)允许以插件的形式对部分处理阶段进行修改或者加强。基于插件的设计方法对于研究人员而言其价值是毋容置疑的,同时也为编译原理课程实践提供了便利。我们可以利用这些编译框架提供的前端分析与识别功能,以及后端的部分处理能

力,设计针对语义分析与中间代码生成、代码优化和目标代码生成相关的实验环节,其好处在于:

1) 可以缩短实验完成所需要的时间,降低实验的难度,从而为教学目标的完成奠定良好基础;

2) 能够培养学生的科研能力和创新意识,为使他们顺利走上科研道路打下坚实的基础;

3)基于插件的设计思想和技术也是目前许多大型软件的普遍设计与实现方法,如Firefox、Eclipse和IDA Pro等。学生通过基于插件的课程实验能够加强学生对大型复杂软件架构、设计思想和实现方法等各个方面的认识,提升软件工程管理和软件设计水平。

目前清华大学“编译原理专题训练”课程已经将开放源码软件GCC和Open64作为实验框架引入实践教学[4],GCC 4.5及以上版本已经实现了对插件设计的支持。笔者仅以微软的Phoenix为例详细说明基于插件的实验设计的可行性。

1Phoenix编译框架

Phoenix是由微软公司新推出的用于构造编译程序,各种程序分析、优化和测试工具的一个基础框架。Phoenix编译器框架主要功能包括:

1)Phoenix是一个编译器。该编译器有着与其他编译器相似的功能,能够将源代码编译为二进制代码。

2) 是一个编译器开发工具。由于Phoenix采用了统一的中间形式,编译器开发者只需将新语言的源程序转化为这种中间形式,然后就可利用Phoenix后端工具完成中间语言的转化、优化以及二进制代码的生成。

3)Phoenix作为一个框架,同时还是可插接的(Plug-in),Phoenix包含一些API,使用这些API能编写利用Phoenix特性的工具。

Phoenix体系结构具有高度的可伸缩性,使得开发者或研究人员能够在该体系结构上开发各种各样的编译器以及分析优化工具。在Phoenix中,遍(Pass)和阶段(Phase)是两个极重要的概念,也是支持插件式设计的主要结构。Phoenix支持多遍处理,因此后端由多个Pass构成,而且允许使用者插入自己的Pass,用于特定处理。一个函数的分析过程可以划分为若干阶段,每个阶段的处理对应一个Phase。使用者可以插入、删除一个Phase或重新排列原有的Phase,Pass和Phase的关系如图1所示。

2基于插件的课程实验设计

插件是指能够被Phoenix核心编译模块(C2.exe)调用的外部模块。假设设计了一个名为MyPlugin.dll的插件,当使用命令行选项-d2plugin:MyPlugin.dll启动C2时,C2就会在编译的过程装载并执行MyPlugin.dll中的代码。

如图2所示,源代码程序经过前端C1.exe的分析和处理之后,然后交给C2中的各个Pass和Phase进行处理。当C2运行时,Myplugin能够访问C2内部的所有数据结构,因此,通过MyPlugin能够改变C2的行为,如增加新的Phase,旁路已经存在的Phase,或者替换可选的Phase等。例如,Myplugin可以提供一个寄存器分配Phase替换掉C2中已有的部分,可以向被编译的每个函数中插入一些其他的代码,输出某个函数编译所形成的IR等。

编写一个Phoenix的插件非常简单,例如,我们想输出被编译的每个函数的名字,则需要编写一个插件FuncNames。为此,首先定义一个实现PlugIn接口的类MyPlugIn,作为插件FuncNames与C2.exe交互的接口。MyPlugIn必须实现PlugIn中的两个接口:RegisterObjects和BuildPhase。Phoenix编译框架在装载插件之后调用RegisterObjects并注册插件对命令行选项进行处理。本例中不支持任何命令行命令,所以该接口的实现为空。BuildPhase接口有一个PhaseConfiguration类型的参数,这个参数是C2.exe提供给插件的,插件中的代码通过这个参数能够访问C2中的Phase列表。本例中我们只需要创建一个新的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);

}

此外我们需要创建的一个新的Phase类,该类是从父类Phase继承而来,且其必须实现父类的两个方法New和Execute。New是在前述的BuildPhases方法中调用的,主要作用是构造和初始化MyPhase对象;而Execute是C2编译每个方法时都会调用的方法,实际完成函数名输出的方法,程序代码如下:

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(config, "FuncNames");

return phase;

}

void MyPhase::Execute(Phx::Unit ^ unit) {

if (!unit->IsFunctionUnit) return;

Phx::FunctionUnit ^ function = unit-> AsFunctionUnit;

Phx::Output::WriteLine("Function: {0}", function->NameString);

}

从上面这个例子可以看出,基于Phoenix编译框架构造一个插件是非常简单的,但是能够实现非常多的功能。Phoenix的SDK包中包含了很多插件实现的具体例子,笔者对这些实现的代码长度进行了统计,如表1所示。

从这些例子可以看出,使用Phoenix构建一个插件实现特定的功能是可能的,特别是一些简单的功能,例如,查找未初始化的局部变量、输出函数的名字等只需要几百行代码即可实现;此外Phoenix不需要关注前端分析,即可进行控制流分析和数据流分析等操作。正是由于Phoenix具有上述特征,为快速进行编译器后端相关的实践提供了可能。

3实验设计简例

基于Phoenix提供的核心构件和API,并参考Phoenix提供的插件例子,我们设计了如下几个实验:

1) 函数复杂度计算。定义评估函数复杂度的方法,并基于Phoenix实现一个插件程序对输入源程序中的所有函数的复杂度进行计算,并根据计算的结果对对函数进行排序输出。

2) 查找无用赋值。基于Phoenix实现一个插件程序查找输入源程序中的无用赋值语句,并输出其所在的位置。

3) 常量合并。基于Phoenix实现一个插件程序能够实现简单的常量合并,并输出其中常量合并的位置。

4) 静态类型检查。基于Phoenix实现一个插件程序,该插件对代码进行静态分析,输出需要进行强制类型转换的语句的位置。

由于Phoenix提供了丰富的数据结构和接口,并且插件能够访问和使用几乎所有的内部结构,因此能够实现各种各样的功能,笔者仅是给出几个简单的例子,希望起到抛砖引玉的作用。

4结语

针对编译课程教学实验设计的相关问题,笔者提出基于插件的实验设计方法,不仅能够扩展编译程序构建相关实践的广度和深度,并能够缩短学生完成实验的时间。基于插件的设计与实现非常有利于培养学生的科研能力和创新意识,同时提高学生对大型软件设计与分析的能力。

文章详细介绍了Phoenix编译器框架的结构,通过分析插件的设计方法和插件程序的代码长度,说明了基于该框架设计实验的可能性,最后给出了几个可能的实验设计,希望能够对实践课程的设置起到借鉴作用。

参考文献:

[1] 曹琼. 浅谈编译原理实验课程教学[J]. 计算机教育,2007(18):45-46.

[2] 朱朝霞,周云才. 编译技术语法分析实践教学探讨[J]. 北京教育学院学报:自然科学版,2008(3):11-14.

[3] Microsoft Corporation. Phoenix Microsoft Connection[EB/OL]. [2011-04-10]. https:///Phoenix.

[4] 董渊,王生原,张素琴.“编译原理专题训练”课程介绍[J]. 计算机教育,2009(21):16-18.

Experiments Design Method for Compiler Practices Based on Plug-in Technologies

JI Weixing, CHEN Ying, WANG Guizhen, LI Kan

(School of Computer Science and Technology, Beijing Institute of Technology, Beijing 100081, China)

Abstract: This paper proposes a new approach to design backend-oriented experiments based on the new compiler framework Phoenix, which offers plug-in toolkit enabling student to build code analysis and binary generation modules easily and quickly. We analyze the necessity and possibility of designing experiments based on Phoenix’s plug-in architecture in this paper, and some examples are given for reference.

Key words: Compiler Principle; practice teaching; plug-in; Phoenix

上一篇:编译原理课程的创新教学方式 下一篇:自动化测试工具课程教学方案