申优论文 北航本科编译原理大作业

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

How to Design a C++ Object-oriented
Compiler
罗杨37230118 Abstract
This system is a C++ object-oriented compiler using a extended C0 grammar as the input language. It can generate the assembly code according to the Intel 386 instructions set. You can use the Masm32v10 software to assemble and link the source code to get your 32-bit applications.
摘要
本系统实现了一个以扩充C0文法为输入语言的采用C++及面向对象思想设计的编译器,可以生成符合386指令集规范的汇编代码,用Masm32v10汇编可生成32位应用程序。

本系统考虑到不同版本的Masm的功能和使用方法的差异,以及用户手工键入汇编,连接命令多有不便,本系统自带了汇编器Ml.exe和连接器link.exe。

正文
由于是目标是Windows平台下32位应用程序,有些文法中的功能如输入和输出无法再像16位汇编时调用DOS中断解决,因此对于这些问题我使用Microsoft提供的类似高级语言的设计方法——调用DLL函数来解决。

其实32位汇编语言在函数调用方面已与高级语言相差无异,可以调用系统API,C RunTime甚至是用户DLL中的函数。

本系统是一遍扫描编译程序,采用面向对象的方法进行构建,各个主要部分均抽象成类。

主要的类有:分析器类Parser,符号表管理器类SymbolTableMgr,四元式管理器类QuadrupleMgr,代码生成器类CodeGenerator,错误处理器类ErrorHandler,全局数据流分析器类GDAOptimizer,全局寄存器分配器类GRDOptimizer,局部公共子表达式删除器类CSDOptimizer,以上属于具有明显功能划分和界限区别的―高级类‖,他们的对象在全局main
函数中分别构造和删除;系统中还有其它一些既具有数据结构意义又具有功能操作的―中级类‖,它们一般是―高级类‖或―中级类‖的成员,如基本块集合管理器BBSetMgr,由全局数据流分析器类GDAOptimizer构建,并为其它多个优化器类共享;还有一些相对意义上的―低级类‖,它们具有复杂的数据结构,却几乎没有功能操作,通常由―中级类‖或―高级类‖得数组成员进行管理,如项类Item,四元式类Quadruple,其中项类Item代表符号表中的每一项,四元式类Quadruple顾名思义代表一个四元式,并且四元式的三个操作数是指向项Item的指针。

由于这些―低级类‖对象通常代表基本数据,数量众多而又经常在各个高级对象之间传递,为了减少时间空间的开销,本系统对所有的类均采用指针构造,这样需要传递的就不是对象而只是一个指针了,有效地提高了程序的运行效率。

本系统的一个特点是词法分析和语法分析结合,其功能集成在词法与语法分析类Parser 里,虽然本系统整体式面向对象的,但由于词法分析和语法分析本身的特性,Parser类却具有明显的面向过程的特征:各个代表文法子程序的层次关系,中间不断嵌入的词法程序和语义程序(动作符号子程序),这样的体系几乎体现不出面向对象抽象封装的优势,因此我在这部分虽然代码写的很快,调试却花了比写还要长的时间,这的确说明了我前期的程序框架没有设计好,没有一个好的结构的程序,代码量不大时并不会体现出它的缺陷,但是随着规模的扩大,功能的复杂化,程序最后会达到难以维护的程度。

因此我觉得在词法与语法分析方面能构造出一个简便适用的面向对象的模型非常必要。

另外在语法分析方面,考虑到题目中的C0文法并无左递归问题,只有回溯问题。

根据课本,为了消除回溯可以用改写文法或是预读字符的方法。

当时课堂上老师着重讲了改写文法的方法,预读字符的方法只是介绍了一下。

但是我考虑到若改写成等价文法,使规则右部多个候选式首符号集不相交的话,虽然语法分析变得简单了,但改写后的规则中的符号含义变得不明确,本来是语义连贯的动作符号语义子程序,可能因为文法的改写被分到不同的语法递归子程序中,参数传递复杂而繁琐,而且模糊了语义编写时容易出错。

反而给语义分析带来更大的麻烦。

因此我采用预读字符的方法处理回溯问题,由于在预读字符时只是进行语法分析,并没有语义处理,因此不会出现课本上所说的―相关的语义分析工作在回溯时也要推倒重来‖的情况,不难验证,这两种方法在运行速度上并无本质差别。

我认为本系统一个比较突出的亮点是中间代码——四元式的操作数均为指向符号表某一项的指针,也就是说中间代码其实是四元式表加上符号表。

这样做不仅节省空间,而且由于符号表存储了几乎所有的数据结构信息,在代码生成或优化中都非常方便调用。

显然若这样的话,符号表在生成目标代码前是不能释放的,因此没有采用课本上介绍的栈式符号表。

显而易见,进行语义分析时进入和退出过程的动作与堆栈很像,因此本系统的符号表也采用了一个指针pCurrentTable来表示当前栈顶符号表。

因此需要一个对象来管理当前栈顶符号表pCurrentTable。

再者,考虑到不同模块(过程或函数定义)的在变量作用域上具有独立性(即模块外的语句不可访问模块内变量),因此本系统实现了多符号表架构。

前面提到,本系统的―高级类‖之一是符号表管理器类SymbolTableMgr,就是用SymbolTableMgr来管理符号表SymbolTable。

存储方式考虑到符号表之间的严格的层次关系并没有采用符号表数组这样的线性存储方式,而是采用的类似于链式存储的形式。

在这里指针发挥了很大的作用。

按照前面所说,符号表SymbolTable以数组形式拥有项Item,这里项Item设置了一个pChildTable指针,表示子符号表,这个Item实际上就代表函数(过程)定义里的函数(过程)头,它拥有这个函数(过程)所对应的符号表。

然后符号表管理器类SymbolTableMgr 模拟了堆栈的操作——压栈和出栈,即对当前栈顶符号表pCurrentTable进行变更。

为了方便出栈,为每个符号表SymbolTable设置了父项Item,同时又为每个Item设置了父符号表SymbolTable,这样本系统实现的符号表体系实际上类似一个双向链表。

对于单个的符号表,课本上提到,其实编译系统分析过程中很多时间花费在查表建表上,并且提出了多种符号表组织形式,如无序符号表,有序符号表,散列符号表等等。

无序符号表插入操作简单,但查表费时;有序符号表,查表可以用很快的折半查找法,但插入时需要同时移动很多元素,很费时;散列符号表又过于复杂,包括HASH函数的选取等。

因此我借鉴本学期―数据库系统概论‖里介绍到的一种方法来解决插入和查找的时间复杂度问题,这就是索引。

为每一个符号表建立一个Int类型的索引数组,数组内容即为原表项Item数组下标。

这样在插入时,数据插入原表项Item数组末端,更新索引。

查找时直接查找索引即可。

因此插入和查找的时间复杂度都很低。

在中间代码方面,由于考虑到为使涉及四则运算的代码更易实现,选定了符号表项Item 作为四元式的操作数,但是也存在一些问题,有些操作数如符号串,标签,局部常量并不是符号表的内容,并且有着自己特殊的操作,如标签的命名等等。

由于不能存储在符号表中,因此把它们按类分别建立项Item的数组,由四元式管理器QuadrupleMgr拥有。

并且这些Item 的kind属性各不相同,这样就可以实现不同的语义动作,如全局变量显式生成声明语句,实参分配压栈后的地址,局部变量分配运行栈中的地址,全局常量采用类似C++中宏定义的使用方式,局部常量直接常量替换等等。

最后虽然各个操作数的意义大不相同,但至少逻辑格式是统一的,并且通过它的type可以很好地区分不同类型的操作数。

基于以上的优点,这部分的代码完成速度很快,而且质量也有保障。

文法中的条件转移语句中有6种关系操作,即>,<,==,>=,<=,!=,但它们的运算方式其实与普通的四则运算还是很像的,都有两个源操作数和一个目标操作数。

其实在C 语言语义下文法按照这样设计的,根据两个源操作数大小关系决定目标操作数为零还是非零。

因此我的中间代码所使用的指令就有类似的操作,即根据两个源操作数大小关系决定目标操作数为零还是为一。

这样就关系运算就没有必要和跳转指令绑在一起了,也算是一种对文法的扩展吧,当然只是语义上的,语法分析部分如果不符文法还是会报错的。

这里要提到的是由于X86指令集并没有判断大小并赋值的语句,因此上面提到的这些中间代码的指令生成汇编代码时实际上还是采用的判断跳转指令。

虽然本质上并没有避开跳转指令,但是这就实现了中间代码对汇编代码的一层抽象。

还有浮点数处理也是本系统着重实现的功能之一。

虽然文法中的三种数据结构char,int,float实际需要的大小并不一致,虽然int有16位和32位之分,但VC6生成的汇编的int类型是32位的,而float也是32位。

虽然char是8位大小的,为了方便处理考虑,本系统生成的汇编中char按照32位处理,由于文法并不存在数组这样的数据结构,可知这样的设定不会造成明显的空间浪费。

并且,事实上由于CPU内部执行机制的原因,统一大小的数据块通过寄存器和内存间的赋值往往比大小不一的数据块的赋值要快。

这就是为什么有时候VC6生成的汇编代码中的赋值经常取齐为字或双字的原因。

虽然char类型在运算时可以直接按照int类型来处理,毕竟存储格式本质是一样的。

而浮点数就不同了,IEEE定义的单精度浮点类型float的存储格式与int完全不同。

因此在涉及混合类型运算时必须进行格式转换。

可喜的是,386汇编指令集提供了浮点运算和转换的指令,由专门的CPU浮点数运算单元来处理。

因此,针对汇编指令集的情况,构建了两条中间代码指令来完成float转int和int 转float的运算。

因此在遇到混合类型的四则运算时,char和int之间是不需要处理的,若遇到float类型操作数,则它的临操作数若不是float类型也需要格式转换成float。

即对表达式内的表示范围较窄的类型进行隐式提升。

若赋值式的左右操作数类型不一致时,右操作数会强制格式转换成左操作数类型赋值。

浮点数需要在生成的汇编代码里能够运算和类型转换,这主要数针对浮点数变量而言的。

若存在浮点数常量的话,此常量必须在编译时就得到正确的值。

因此本系统在数据转换器类DTS中的ToFloat函数实现了把string类型的实数转换为其对应的string类型的32位浮点数格式,如string―1.234‖转化为string―3F9DF3B6h‖。

同时ToFloat函数的输入的实数过大,超出了float的表示范围,还能够向错误处理器类ErrorHandler提交错误。

另外,为了使程序有一定的可读性,项Item的kind属性,type属性以及四元式Quadruple的操作符opr属
性取值都采用宏定义给出一个有意义的名字。

也就是说这些属性的取值实际上为int,为了在控制台输出这些属性对应的string,在DTS类还实现了Convert方法。

由于DTS类本身更类似一个函数库,而不是一个类库,因此采用静态static方式提供方法,这样既方便了其他类直接调用,又具有类易于管理的特点。

最后是优化部分,在生成中间代码时,由于考虑到汇编语言四则运算指令的种种限制,如两操作数不能同时为变量,不能同时为寄存器间接寻址等因素,因此生成的四则运算相关的中间代码的目的操作数全都为新分配的临时变量(在运行栈中),然后再将此临时变量赋值给原高级语言中要赋值的变量。

这样做虽然比较安全,但势必会产生很多冗余代码。

因此必须进行一定的优化。

本系统实现了局部公共子表达式删除优化(对应类CSDOptimizer),全局寄存器分配优化(对应类GRDOptimizer)等。

其中全局寄存器分配优化采用了图着色算法,并使用全局数据流分析(对应类GDAOptimizer)的活跃变量分析来构建冲突图。

由于局部公共子表达式删除优化CSDOptimizer需要在基本块内进行,全局寄存器分配优化GRDOptimizer也需要函数块(过程块)的信息以及各基本块的冲突图信息。

因此全局数据流分析GDAOptimizer需要最先执行。

GDAOptimize负责构造BBSetMgr基本块集管理器类的一个数组,顾名思义,BBSetMgr代表一个基本块集,即一个函数(过程)。

同时BBSetMgr 基本块集管理器类又负责构造BasicBlock基本块类的一个数组,BasicBlock代表一个基本块,BasicBlock的划分及相应in和out集的生成又其父类BBSetMgr负责。

这样,全局数据流分析GDAOptimizer的工作就完成了。

下面是全局寄存器分配优化GRDOptimizer,这一步的优化时跨越基本块,在函数(过程)范围内的,因此需要上一步GDAOptimizer生成的BBSetMgr数组,又这个数组构建了冲突图表类ConflictTable的数组,一个冲突图表类ConflictTable是一个二维表,行和列相同,都为项Item,当表格中值为1表示行与列有冲突。

生成冲突图后实现图着色算法,为局部变量和参数变量分配全局寄存器,优化后的结果会直接反映到四元式管理器类QuadrupleMgr中的四元式数组。

最后是局部公共子表达式删除优化CSDOptimizer,它是在基本块下完成的,因此需要GDAOptimizer生成的BBSetMgr数组,此公共子表达式删除是针对中间代码中的四则运算,赋值和取反操作进行,遇到其他如函数(过程)调用和关系运算不会进行此优化,因为函数(过程)调用和关系运算都是通过跳转来实现的,这样也不会属于一个基本块。

CSDOptimizer 每完成一个基本块的优化,都会把结果反映到四元式管理器类QuadrupleMgr中的四元式数组中。

错误处理方面,语法和语义分析中的错误会提交给错误处理器类ErrorHandler,ErrorHandler负责显示出现的错误的行号,列号和对应错误信息。

一行出现错误后语法分析将移近到下一行开始进行,因此可以说实现了一定的错误局部化处理。

代码编写过程中为了方便测试,很多类均拥有Display成员方法,用来在控制台打印显示此类所存储或子类所存储的数据信息。

最后,这学期的编译课程设计让我收获很大。

我不仅在这此设计上融合了包括高级语言程序设计,面向对象思想,数据库系统概论等课程的知识,更锻炼了实际的代码开发能力,像一般的大学生跟那些已经步入社会的软件工程师比起来也许理论知识很强,但往往动手能力不足。

对于我来说,也是头一次一个人编写6000行以上的一个系统。

发现实际应用中会涉及到而理论上没有提到的问题还有很多,真的是一次历练。

我觉得这门课程的开设十分有意义。

北航的编译课程安排的也很合理,老师都是以循循善诱的方式来教学,而且讲授内容灵活,并不死板的跟随课本的章节,并照顾到了课程设计的进度。

这样边学边练的效率很高,一个月前的代码直到现在还记忆犹新。

最后,希望北航的编译课程越来越好!
参考资料
1.《高级编译器设计与实现》(美)Steven S. Muchnick著赵克佳,沈志宇译机械工
与出版社
2.《编译原理及编译程序构造》金茂忠,高仲仪编北京航空航天大学出版社
3.《计算机组成与设计–硬件/软件接口》(美)Pattersom,Hennessy著郑纬民等译机
械工业出版社。

相关文档
最新文档