基于lcc编译工具的实现

时间:2022-06-06 03:08:53

基于lcc编译工具的实现

摘要:编译工具是进行软件系统开发和测试的强大工具。现有的编译工具复杂、庞大并且为数不多,诸如Vtune之类的程序剖面分析工具更是昂贵,但是对程序进行静态分析和动态跟踪评估等工作在很多方面十分有用,因此,以现有编译器为主体进行编译器扩展来现相应工具是非常有必要的。LCC是一款广为使用的ANSI C编译器,由于自身的简单和使用高效特点,由它出发通过修改、定制,构造其他特殊目的的编译器相关工具就变得相对简单。

关键词:编译器 编译工具 LCC 编译后端

Implementation of compilation-tools based on lcc

Abstract Compilation tools are powerfull tools for development and testing of software system. Compilation-tools existed are complex, large and infrequent and program profiling tools as VTune are costly. However, static analysing and dynamic tracing and evaluating for program is useful sometimes, so that, implementation these tools by extending a compiler existed is necessary. Lcc is a widely used compiler for ANSI C. construction compilation-tools by modifying, and configurating it is relatively simple because of its simple and effectiveness.

Keywords compiler, compilation- tools, lcc, compiler back-end

1.简介

编译器是编译工具的一种,是进行软件开发不可或缺的工具。编译器强大而复杂,其他编译相关工具也是如此。纵观各种编译相关的工具,如GCC编译器、lint语法检查器,以及许多程序剖面分析器等等,一直在强大、庞大、复杂和昂贵之间游走。对于许多特定目的的工作而言,一款轻便、高效和廉价的编译工具是十分具有吸引力的,因此,以一个简单高效的编译器为主体,针对不同的用途进行定制,有时就变得十分有必要。本文以此为目的,介绍了一种以LCC为主体进行配置和实现编译器相关工具的具体方法。

LCC是一款免费、开放源代码并被广泛使用的ANSI C编译器。它的作者是美国AT&T实验室的Christopher W. Fraser和美国普林斯顿大学教授David R. Hanson,当前版本为4.2。LCC的分析代码由手工编写而成,编译速度非常快;LCC没有单独的优化遍,但对于大多数应用来说,LCC产生的代码已经足够快了。[1],与广为使用的GCC形成鲜明对比的是,LCC的源代码简单、紧凑,十分有利于进行修改。以可变目标为目的的设计,移植或对各种不同用途后端的实现比较简单。我们的工作就针对该编译器后端进行自定义配置的方法展开,它与LCC编译器的移植工作没有区别。

2.数据表示

类型和符号的数据结构是编译器的核心数据表示。对于编译后端而言,中间代码有关的数据结构也是非常重要的。LCC使用DAG(有向无环图)对中间代码进行描述,它使用二叉树的链表形式进行组织。所有的中间代码通过代码表进行管理,这种代码表与优化器中用于控制流分析的基本块有所区别,但可以看作是一种扩展基本块[1],通过它可以方便进行基本块的划分,从而插入单独的优化遍,使LCC成为优化编译器。

编译器后端的实现者需要熟悉至少四种LCC的核心数据结构,分别是类型、符号、DAG节点和后端接口描述,本文不对其细节进行赘述,可参考LCC编译器源代码。

对于这四种数据结构中,LCC从概念上将其划分为三部分,一部分为编译前端私有数据,根据LCC后端的访问约定,LCC后端并不访问这个部分的数据;第二部分为前后端共享的数据;第三部分为后端私有数据,前端对此一无所知。其中,前两个部分的数据从形式上看没有区别,而第三部分,作为后端的私有数据结构由后端自己实现,由各结构中的x域成员进行维护,它们的组织形式如下:

LCC为生成可执行目标代码的代码生成器提供了一种自动代码生成的方法,使用这种方法,后端的实现者只需要提供一份规范描述的目标机器描述文本,由一个叫做iburg的程序据其自动生成相关代码,而上图所示的x域成员类型由相应的后端文件提供,其数据由自动生成的代码进行访问,相关类型和数据的声明在config.h文件中声明。对于手工生成的代码,这些x域成,由实现者自行实现和访问,可以将config.h文件替换为自己的实现。

LCC的中间代码表示使用的是DAG(有向无环图),其中多入口的节点就是公共子表达式节点。使用这样的表示方法起到了删除公共子表达式的目的,为后端生成高质量的目标代码提供了有力的保障。

LCC中间代码所提供的指令是通过仔细筛选的,能够匹配大多数机器的硬件指令[2]:

实际使用中的中间代码操作符由上表所示的操作码和具体表示数据类型的后缀组合使用。

3.后端的移植和实现

LCC的后端是通过实现后端接口Interface结构声明的数据和函数来实现的。Interface接口包括了一些接口标记和函数指针,还包含了一个可自定义的扩展接口Xinterface,它的作用是为基于iburg紧缩规范自动生成的代码提供统一的接口。LCC的后端的任务就是实现并设置这些接口数据和函数。除开Xinterface接口,根据它们作用,下面分类介绍:

4.接口标记

后端接口包括数量适中的接口标记数据,它们描述了目标机器和后端实现的各种特性,为生成正确的代码和数据提供依据:

其中CALLB操作码是中间语言操作符CALL和后缀B的组合,它表示了对一个返回值类型为结构(struct)的函数的调用,由于一个类型为结构的变量常常不能存储在一个寄存器之内,因此它需要进行特殊处理;同理,ARGB表示传递一个结构类型的函数参数。

5.接口函数

后端接口包含一些重要的接口函数,编译器前端通过它们实现代码的生成和发送工作,这些函数接口描述如下:

6.手工实现

实际上手工实现LCC编译器后端是简繁参半。简单的是,后端的实现者可以自己定义Xinterface接口和Xnode、Xtype以及Xsymbol,而暴露给编译前端的接口就会变得十分简洁,只要实现Interface接口函数,就能使整个LCC编译器顺利的工作;麻烦的是,如此一来所有代码生成工作包括指令选择、寄存器分配等就需要全部自行设计和实行。

但是对于并不生成实际代码但完成其他重要任务的编译后端而言,这种做法则十分可行。这样,可以完全抛弃config.h中声明的结构和函数,仅提供一个简洁的接口即可。实际上,正确的设置接口标记,然后将Interface接口函数都设置为什么都不做的空函数,并定义Xinterface为空结构,实现了一个很好的语法检查器,它不实际生成并发送代码,只是进行语法语义检查,类似于UNIX平台下的lint。

具体而言手工生成LCC后端的工作如下:

声明相应的X类型

定义一个Interface接口实例

设置所有接口标记

实现所有接口函数

7.基于iburg紧缩规范的自动代码生成

对于使用基于iburg紧缩规范自动代码生成的后端实现方法而言,后端接口数据和函数分为两个部分:一部分是LCC为优化后端接口函数而业已实现的那部分接口函数;而另一部分是根据代码生成规范自动生成的接口数据和函数。在LCC的发展过程中,开发者将后端函数分为目标无关与目标相关部分,并实现了目标无关代码的部分代码。这样,iburg规范中需要的内容就减到了最少。这样做的目的是人工编写一个规模中等的代码生成器需要1000到1500行的C代码。如果尽可能地隔离与目标机器相关的特性,这个数字就会锐减一半。尽管这样做的代价增加了大约1000行与机器无关的代码,但是,只要有两个目标机器,就能从这种方法中获得益处。更重要的是,如果我们尽可能多的使用已有(即与机器无关)代码,开发一个新的代码生成器就变得更加容易了。[1]

iburg是代码生成器的生成器,它接受一份用类似于YACC语法规范的机器描述和自定义代码段,生成用于编译器后端的接口函数和接口数据,以配合编译器进行正确的工作。

使用iburg自动生成代码生成器需要使用LCC提供的config.h文件,它定义了相关的X开头的结构类型并声明了一些后端目标无关的工作函数。iburg生成相关的接口数据和接口函数。

iburg规范的语法如下,term和nonterm分别代表终结符和非终结符[4]:

grammar: ‘%{‘ 配置文本 ‘%}’ { dcl } %% { rule } [ %% C代码 ]

dcl: %startnonterm

%term { term = 整数 }

rule:

nonterm : tree template [ C表达式 ]

tree:

term [ ‘(‘ tree [ , tree ] ‘)’ ]

nonterm

template:

“ { 任意非引号字符 } “

iburg规范是按行组织的,由它自动生成的程序被叫做BURM。单词“%{”、“%}”和“%%”必须单独一行,每个dcl或rule必须出现在一行上,rule指定了代码选择进行的模式匹配规则,template指定了指令模版。配置文本是C语言代码,它被原封不动的复制到BURM的开头。如果第二个“%%”出现,那么它之后的正文也被原封不动的复制到BURM的末尾。%start声明了待分析树的根,%term声明了树节点的编码。

自动生成LCC后端的工作如下:

创建一份iburg规范文本

在规范文本中书写代码生成规则

在规范文本中书写代码选择以外的接口函数

定义接口实例并进行接口绑定

8.结论

LCC由于自身紧凑的设计和简单的代码以及比较清晰的接口设计使得修改和定制的工作比较简单,这样,对于很多具有实用价值的工具的实现不再因为编译器相关工具的复杂而变得遥不可及,它为有特殊用途的编译工具的实现提供了一个有价值的选择。

上一篇:平台的竞争力 下一篇:现代企业制度下内部审计