SlideShare a Scribd company logo
1 of 44
Download to read offline
毕业设计(论文)报告
题目 基于 Minisystem 的 GCC 编译器的移植
计算机科学与工程学院院(系) 计算机科学与技术 专 业
学 号 09006309
学生姓名 马小京
指导教师 王晓蔚
起讫日期 2010.3—2010.6
设计地点 计算机楼
2010 年 6 月 7 日
基于 Minisystem 的 GCC 编译器的移植
09006309 马小京
指导教师 王晓蔚
摘 要
根据 MiniSystem 系统的编译需要,研究并分析 GCC 的编译过程以及移植原理。通
过对 GCC 编译器后端的研究和 RTL 语言的理解,说明后端编译时编译器的处理过程;
给出移植过程中对机器定义中 INSN 的修改和新建的方法,以及机器定义中 RTL 的含
义和作用。以编译过程中典型的指令模板为例子,使用移植后的编译器对 C 程序片段
进行编译,说明了 GCC 移植的方法并列出编译产生的结果。同时,在移植过程中根据
指令集设置对编译器进行优化,讨论并且对优化前后的编译器进行了对比。
关键词: 编译器, GCC, 移植, RTL
i
GCC Porting on MiniSystem
09006309 Ma Xiaojing
Wang Xiaowei
Abstract
Based on the demand of program compile on MiniSystem, the project prepared for re-
search and analyse the compile process and porting principle of GCC. Through a study in the
back-end of GCC and to understand the RTL language, the thesis explains the operation pro-
cesses of the back-end compile. Then shows how to modify or create a INSN definition in
the machine-definition of the porting, also indicates the meaning and function of RTL in the
machine-definition. Takes several typical instruction templates as example, we compiled the C
language program by ported compiler and illustrate the result of user-defined compile through
GCC porting. Meanwhile, the compiler was optimized based on the set of instructions. Then
the thesis discussed and compared with the original and optimized cimpiler.
Keywords: Compiler, GCC, Porting, RTL
ii
本论文专用术语的注释表
GCC: GNU 编译器集合。
移植: 使得 GCC 可以编译在新定义处理器体系下运作。
RTL: 寄存器转换语言,在 GCC 编译中间起转换作用的描述语言。
前端:Front-end,GCC 编译器中跟机器无关而跟编程语言相关的前向处理部分。
后端:Back-end,GCC 编译器中跟语言无关而跟目标机器平台相关的后续处理部分。
汇编语言:一种与机器硬件紧密相关的底层程序语言,与机器语言存在部分对应关系。
机器语言:二进制程序代码,计算机能够直接识别并且执行,程序直接通过计算机硬件结构赋
予操作功能。同时,与硬件对应意味着无法再不同结构的计算机上执行。
指令集:计算机架构中与程序运行的相关内容。包含了本身的数据类型,指令,寄存器,地址
模型,存储结构,中断以及意外处理,I/O 处理等。一个指令集包括一系列的指令(机器语言)定
义,以及和处理器相关的运行命令。
机器描述:GCC 后端编译中对目标平台指令集进行描述的文件,编译过程中 GCC 会将中间代
码来匹配机器描述用来生成汇编程序。
iii
目 录
要 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · i
Abstract · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ii
本论文 用术语的注释表 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · iii
目录 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · iv
第一章 绪论 (前言) · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 1
1.1 课题研究背景 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 课题研究目的和意义 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 国内外研究状况 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 论文主要研究内容 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.5 论文组织结构 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
第二章 GCC 移植系统和平台介绍 · · · · · · · · · · · · · · · · · · · · · · · · 4
2.1 GCC 编译器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 MiniSystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3 ABI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.3.1 编译与移植环境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
第三章 GCC 编译与 GCC 移植 · · · · · · · · · · · · · · · · · · · · · · · · · · 7
3.1 GCC 的编译原理和过程 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.1.1 前端 Front-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1.2 中间接口/程序代码优化 . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1.3 后端 Back-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.1.4 RTL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2 GCC 的移植原理和方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.1 target.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.2 target.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2.3 target.md . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2.4 匹配原理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2.5 移植方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
第四章 GCC 移植的详细过程 · · · · · · · · · · · · · · · · · · · · · · · · · · 16
4.1 算数指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.1.1 ADD, ADDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.1.2 SUB,SUBI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
iv
4.1.3 验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.2 逻辑指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2.1 AND,OR,XOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2.2 NOR, NORI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2.3 NAND,NANDI,EQL,EQLI . . . . . . . . . . . . . . . . . . . . . . . 20
4.2.4 验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3 其他指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.3.1 SW,LW,LB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.3.2 寄存器的立即数存储 . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.3.3 MOV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.4 BNE,BEQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.5 JLI,JI,JR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.3.6 AL,AR,SL,SR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3.7 比较指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
第五章 编译器综合测试 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 25
5.1 实现命令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2 综合验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
第六章 GCC-MiniSys2 交叉编译器 · · · · · · · · · · · · · · · · · · · · · · · · 29
6.1 编译环境与编译器设置 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2 编译器的使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2.1 编译器命令行界面 . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2.2 编译程序 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.2.3 编译调试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.2.4 RTL 片段输出 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.2.5 GCC 编译器的优化 . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
6.2.6 汇编程序注释 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.2.7 程序成本统计 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.3 IDE 集成/高级编译器界面 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
第七章 项目总结 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 34
7.1 研究成果与说明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.2 总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.3 展望 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
参考文献 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 36
附录 A 研究与开发环境 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 37
A.1 Ubuntu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
A.2 GEDIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
A.3 Notepad++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
A.4 LATEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
A.4.1 seuthesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
v
第一章 绪论 (前言)
1.1 课题研究背景
对于一款成熟的计算机处理器而言,一个完善的、合适的编译器是其正常并且高效
作用的关键。而对于 MiniSystem 这款基于 FPGA 的软核处理器来说,只进行汇编程序
的开发是对处理器资源的浪费,汇编语言编程周期长,代码调试不方便,同时也是实现
复杂程序功能的一个重要瓶颈[1]
。这里就需要设计编译器对高级语言程序进行编译,进
而可以对处理器进行有效的利用。
编译器的开发有两种方法可以选用,第一种是根据硬件平台的设计完全编写一个
可以使用编译器,这种方法与平台结合完整,但是开发成本高,利用范围窄,并且
编译器质量得不到保证。另一种方法就是利用成熟的 GCC 编译器集合,进行移植工
作,使得可以在任何支持 GCC 的平台上对目标平台进行程序交叉编译,这种方法开
发成本低并且方便快捷,编译过程的质量和优化完全基于多年发展成功的 GCC 内部
功能。虽然使用移植的 GCC 编译器需要在指令集设计时参照 GCC 进行某些功能限
制,但是同时这也是最为现实的并且最易实现的。同时,GCC 提供了大部分流行的高
级编程语言前端支持,不需要任何新的设计就可以使用移植后的 GCC 编译成功例如
C,C++,Java,Fortran,Ada 等程序[2]
,而对如此多的程序语言支持在前一种方法下的工作量
是不可想象的。
同时 GCC 的移植也是一个十分复杂的过程。GCC 经过长期的开发发展,虽然程序
严谨精密,但是不可否定的是其自身结构与功能实现也相当繁琐复杂。尽管 GCC 移植
仅需要对后端与平台相关部分进行设计,前期的准备工作和相关内容的整理与研究也
是必不可少的。
1.2 课题研究目的和意义
为了给 MiniSys2 系统提供一个高效的高级语言程序编译器,对 GCC 编译器集合进
行功能和结构分析,从而研究并总结出 GCC 对目标平台的移植原理和方法。在提出的
移植方法基础上,设计符合 MiniSys2 系统结构的编译器并对 GCC 进行系统移植。移植
后的编译器就是 GCC-MiniSys2 交叉编译系统。
使用基于 GCC 的移植编译器可以提高 MiniSys2 系统代码的编写效率,并实现更为
复杂的程序功能,使得程序员可以在标准的计算机平台上编写并编译 C 语言等高级语
言程序,直接通过编译器生成可以在 MiniSys2 平台上运行的 ELF 机器程序。在程序的
1
第一章 绪论 (前言)
设计和编写过程中并不需要对硬件底层进行了解与干涉,交叉编译器会对寄存器和指
令作出分析并进行调用。
同时,低成本的 GCC 移植编译器兼容 GCC 编译器集合的所有功能。GCC-MiniSys2
交叉编译器支持多种前端语言,在 GCC 的前端 -中间优化基础上根据移植定义再对后
端进行优化。
1.3 国内外研究状况
GCC 的可移植理论由 GCC 开发社区基于其编译器结构提出,但是由于 GCC 已经
可以支持大部分流行的计算机平台,关于 GCC 移植的文献资料并不十分丰富。
国内的龙芯 2(GODSON 2) 处理器上使用的是 GCC 移植编译器,但是龙芯 2 执行
的是 MIPS LIKE 指令集,移植工作仅对少量的特殊指令进行支持即可,所以移植进程
并不完整和全面。除了龙芯系统就没有关于 GCC 的详细移植例子可循了,仅有部分文
献对移植的理论和方法进行简要的说明,对于 GCC-MiniSys2 项目并没有太多的参考意
义。
国外对于 GCC 的移植研究也并不多,但是有个别的研究非常全面因此对 GCC-
MiniSys2 移植有很重要的指导作用。其中有"Porting GCC for Dunces" 对 GCC 像 Dunces
平台的移植作出了详细的讲解,同时此篇文献也多次被众多中文相关论文引用。在
Hans-Peter Nilsson 的这篇文章中对 GCC 移植中很多相关的知识与信息作出了讲解,包
括很多 GCC 手册中也没有提及的内容,例如两种 INSN 模板每一项的含义及作用等。
还有 VLIW 架构,NIOS 架构的移植等,当然在 GCC 编译器的源代码中也包含了每
一种支持的目标平台机器描述代码。
1.4 论文主要研究内容
本文主要对 GCC 编译器的编译结构,编译原理以及移植原理作出研究,针对其可
移植性对每一条 MiniSys2 系统的指令进行分析。对于前端 -后端的编译步骤和后端移植
性进行分析,在论文中对 GCC 移植方法的研究基础上,对 MiniSys2 指令集进行移植并
对结果进行测试与分析,同时利用 GCC 的前端优化功能测试移植编译器的优化效果。
在编译器的移植过程中所要进行的工作有如下几点:
⋄ 对编译器功能的了解;
⋄ 理解 GCC 的结构与功能实现;
⋄ 对 GCC 移植方法的了解;
⋄ 理解 MiniSystem 的指令集及每一条指令所实现的功能;
⋄ 对于某一条指令的实验性修改;
⋄ 对于某一条指令的实验性添加;
⋄ 对全部指令进行可行性分析并进行修改或添加;
⋄ 验证编译器功能;
2
第一章 绪论 (前言)
⋄ 优化编译器功能;
1.5 论文组织结构
论文分为七章及若干小节,包含参考文献列表和附录等。第一章对项目的目的和
背景作出简介;第二章介绍了 GCC 编译器和移植目标平台;第三章和第四章详细的说
明了 GCC 编译与移植的原理,过程,方法以及对各种代表性指令的移植步骤;第五章
和第六章对编译器进行测试,同时说明了 GCC-MiniSys2 交叉编译器的各种命令,功能
等。第七章对项目进行总结。
3
第二章 GCC 移植系统和平台介绍
2.1 GCC 编译器
GCC 的全称是 GNU Compiler Collection[3]
, 是目前最为重要,应用最广的开放源代
码软件。作为一个编译器的集合,旨在支持多种不同的高级编程语言,同时可以根据不
同的目标平台分别编译出能够执行的程序。作为 GNU 计划/GNU 工具链中的重要组成
部分,GCC 现已成为了 Unix/Linux 平台系统中的标准编译器,并且可以通过 Cygwin 和
MinGW 等中间转换平台支持在 Windows 环境下的编译。GCC 由 1987 年时只支持 C 语
言的 GNU C Compiler 计划发展到现在,已经可以广泛支持 C, C++, Fortran, Objective-C,
Java 和 Ada 等多种语言;GCC 同时支持多种处理器结构平台,从标准 X86 处理器到多
种嵌入式平台处理器都可以进行程序编译或者交叉编译,例如 IA-32(X86), IA-64, ARM,
AVR 以及 MIPS 等[2]
。
图 2.1 GCC 编译器集合
GCC 的编译结构中包括一个语言相关的前端,所有语言共同的中介架构,以及一
个目标平台相关的后端。
GCC 在编译过程中通过前端对程序进行处理,得到一个抽象的语法树,再经过中
间和后端将语法树翻译成 RTL,最后得到汇编源程序,通过汇编器生成可以在机器直
接执行的二进制程序代码。整个编译过程相当复杂,但是一般的使用过程中只需简单的
控制命令即可对源程序进行编译或者交叉编译。正是因为 GCC 良好的后端可移植设计,
在编译时仅对 configure 进行少许设置,就可以生成在其他平台良好运行的机器程序。
最新的 GCC 版本是 4.5,发行与 2010 年 4 月 14 日,提供了链接时优化功能 (Link-
time Optimazation),更多的插件和对多种语言及目标平台的支持。
4
第二章 GCC 移植系统和平台介绍
2.2 MiniSystem
MiniSystem 是由东南大学计算机科学与工程学院,系统结构实验室设计并开发的
RISC SOC 32 位系统。系统设计使用 Altera 的 Cyclone FPGA 作为目标芯片承载 CPU 设
计结构与功能。系统的内部设计分为取指单元,译码单元,控制单元,执行单元和存储
单元。其中取指单元负责从指令存储器获取指令;译码单元主要负责从寄存器获取操
作数和将运算结果回写到寄存器;控制单元完成译码中解析指令和提供控制信号的功
能;执行单元负责算数,逻辑运算,并计算与存储器操作有关的地址;存储单元负责取
数或存数[4]
。
Minisys1 系列 SoC 使用 CPU 主要类别有单周期,多周期,3 级流水线,5 级流水
线,超标量,双核,浮点支持,硬件乘除支持,I2C 总线支持,SDRAM 总线支持等[4]
。
MiniSys1 有 32 个 32 位的通用寄存器,32 位数据线和 16 位地址线。系统包括多种外围
部件与控制器,例如定时器,PWM 控制器和 UART 控制器。处理器中的 32 个寄存器
除了个别有特殊的功能外,其余皆可以作为通用寄存器使用 (例如 0 号寄存器永远返回
值 0)。
MiniSys2 处理器是经过修改和优化的最新设计, MiniSys2 的指令集[5]
在 Mips32 指
令集[6]
的基础上修改了少数指令和功能,并且新增加了一些更加优化,效率更高的处
理器指令。指令集中分为算数运算指令,逻辑运算指令,数据传送指令,堆栈相关指
令,移位指令以及寄存器转移/转换指令,分支转移指令和位操作指令共八个分类近百
条指令。
MiniSys2 的指令分为 R 类型,I 类型和 J 类型,但都是 32 位等长,R,I,J 仅是区分
寄存器操作数,含立即数操作数以及转移地址操作。指令中有四种寻址方式,分别为立
即数寻址,相对寻址,寄存器寻址和寄存器相对寻址。
2.3 ABI
ABI(Application Binary Interface) 是计算机底层中程序和程序,程序和操作系统联系
的接口。ABI 定义了程序运行的必要条件:数据类型,数据大小,数据对齐方式,以及
参数传递等。一个完整的 ABI, 例如 Intel Binary Compatibility Standard(iBCS), 可以允许
编译好的程序在支持其 ABI 的不同操作系统上不需修改即可完整运行。
为了可以支持 MiniSys2 的操作系统,采用现代操作系统的通用 ABI 将可以更完整
的将程序和 MiniSys2 兼容。而用 GCC 来进行编译则可方便的提高程序在 ABI 接口上
的兼容性。
2.3.1 编译与移植环境
编译平台:-HOST
硬件平台:I386 Intel CPU
操作系统:Ubuntu 10.04 LTS (Linux 2.6.32-22-generic)
5
第二章 GCC 移植系统和平台介绍
系统编译环境:gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
目标编译环境:gcc version 2.6.3
目标平台:-TARGET
硬件平台:MiniSys2 (minisys2little-na-sstrix)
编译 GCC 的目标平台交叉编译器需要用到计算机架构模拟器:SimpleScalar,其中
包含了 binutils,simplescalar 和 gcc-2.6.3 三个程序包,而 gcc-2.6.3 正式需要进行移植工
作的 GCC 编译器源码集合。GCC 源代码目录下 language 是前端的代码支持,而 config
目录内则是对不同体系结构和操作系统支持的定义。在 GCC 的目标平台移植中主要改
动和分析的就是 config 文件夹下的体系结构定义。
6
第三章 GCC 编译与 GCC 移植
3.1 GCC 的编译原理和过程
编译器是一个翻译程序,它读入程序的源文件,经过一系列的编译过程翻译成可以
在指定平台上运行的顺序指令组,即可执行程序。总体上编译程序分为前端和后端两
部分,前端读入源程序,生成分析树,而一旦树生成,编译器后端就可以从其中读出信
息,进而转换成目标平台所能识别的汇编程序。
一般标准的编译器工作流程分为词法分析(Lexical Analyzer), 语法分析(Syntax
Analyzer)和中间代码产生(Intermediate-code generator)三部分。同样的,在 GCC 的
结构中,前端执行词法分析和语法分析的工作,后端进行中间代码的产生。
词法分析是编译器的开始部分,读入程序字符,分析并确定符号,数字和标点符号
等。
语法分析处理词法分析生成的符号流,并与语言规则确定关系,最后输出分析树结
构,传递给后端进行处理。
语法分析树会被后端翻译成一种称作寄存器传送语言 (Register Transfer Language,
RTL) 的伪汇编语言。GCC 分析 RTL 代码,同时执行将不必要的内容去掉等优化过程。
最终 RTL 代码被翻译成汇编语言,汇编语言可被后面的汇编器翻译成二进制的机
器语言代码。
整个过程如下图所示,输入源程序,输出汇编代码。
图 3.1 GCC 的编译过程
[7]
7
第三章 GCC 编译与 GCC 移植
3.1.1 前端 Front-end
语言相关的 GCC 前端生成供后端处理的分析树。针对不同的编程语言,语法树的
生成规则是不相同的。近几年 GCC 采用了新的 GENERIC 与 GIMPLE 语法树结构,使
得编译的前端产生一个与语言和机器平台都不相关的独立语法树结构,同时也称作适
应大多数现代编程语言的全局优化通用语言。
在编译处理中使用前端结构的优点有[8]
:
⋄ 可以直接支持已在 GCC 内部定义好的目标平台,移植新平台只需改动后端的描
述部分,正因为前端的独立性,编译器不用重写全部代码;
⋄ 可以有更多的优化途径,并且更易于分别开发;可以通过不同的前端来适应不同
的语言,不用重新编写整个编译器;
⋄ 相较于传统的编译器,对于错误的检查更为细致和精确。
⋄ GENERIC-GIMPLE 结构更易于程序代码的优化。
3.1.2 中间接口/程序代码优化
一般的传统编译器前端包含了程序优化功能,但是优化对于语言有相关性,而
GCC 的前端是相对语言独立的,所以 GCC 有一个特殊的中间接口用来对程序进行优
化。GCC 的优化包括消除死码,消除重复运算,消除全局数值重编码等。中间接口的
的实现得以将 GCC 的前端和后端相关联,并彻底的实现 GCC 的语言 -处理器架构独立
性。
3.1.3 后端 Back-end
GCC 后端的处理根据不同目标平台的机器描述而区分。机器描述中定义了例如字
长,字节序/位序,以及体系结构,指令系统等,编译器后端通过对定义的参考来帮助
生成 RTL 片段,优化 RTL 片段,以及将 RTL 片段转换成汇编程序。在这些过程中,机
器定义一直指导 RTL 进行工作,每一种目标平台对应单独的机器定义,因此也生成单
独的 RTL 片段。
正是因为 GCC 这种前后端 + 机器定义的结构,我们才可以快速的依照其相关理论
进行目标平台的快速移植。
3.1.4 RTL
RTL 即 Register Transfer Language,是一种用来描述计算机高级语言程序和汇编语
言程序中间的表示方法。RTL 表达式和 LISP 语言十分相似,在 GCC 编译的中间状态
时用来描述计算机寄存器行为,同时在机器描述文件中用来体现寄存器匹配模板[2]
。
(set:SI (reg:SI 1)
(plus:SI (reg:SI 2)(reg:SI 3)))
8
第三章 GCC 编译与 GCC 移植
这句 RTL 的意思就是,将寄存器 2 和 3 的值相加,存储在寄存器 1 中,这里的 SI
意味着是 Single Integer 模式。在实际的应用当中表达更为复杂,还要有限定和预测的
内容插入其中,另外还会加入属性定义,分类等附加/可选项。同时,寄存器的表达方
法根据目标系统的不同也并不一致。
RTL 的一般形式是 (Object:Mode OP1 OP2...OPn),其中 Object 为表示的对象,可
以为操作数,操作符等;Mode 为对象的数据形式,例如 SI(Single Integer),DI(Double
Interger) 等[9]
;OP 则是可选的操作数或操作对象。
这是几个常用的 RTL 表示方法:
⋄ 常数:(const int i),(const string str) 分别为整数和字符串。
⋄ 寄存器和存储器:(pc),(reg:m n),(mem:m addr adias) 分别为指令寄存器,Mode
为 m 的寄存器 n,地址是 addr 的内存单元。
⋄ 数学表达式:(plus:m x y),(minus:m x y),(mult:m x y) 分别代表着加法,减法和
乘法运算。(not:m x),(and:m x y),(ior:m x y),(xor:m x y) 分别表示逻辑运算中的非,
和,或和异或运算。(eq:m x y),(gt:m x y),(ge:m x y) 分别为比较操作中的等于,大于
和大于等于。(ltu:m x y),(leu:m x y) 表示无符号的小雨和小于等于比较。
⋄ 赋值与跳转:(set x y),(call function nargs) 和 (return) 分别代表了赋值,函数调用
和函数返回[10][3]
。
3.2 GCC 的移植原理和方法
在 GCC 的编译中,编译器根据不同的目标平台选用不同的机器描述文件,而将
GCC 移植到一个新的目标平台,就需要修改和编写和目标相关的定义部分,这其中包
括了 target.md,target.h 和 target.c.
3.2.1 target.h
target.h 是机器描述的宏文件,定义了机器 ABI 的基本属性[11]
:
⋄ 编译器环境:汇编程序语义,编译器的部件切换,头文件的位置以及系统库等。
⋄ 架构的基本属性:寄存器类型,数目,地址等。
⋄ ABI(Application Binary Interface):函数的调用方法。
⋄ 机器描述的基本支持:md 文件中可以用到的定义以及其转化,输出的结果。
3.2.2 target.c
机器描述的 c 文件中定义了汇编生成时用到的输出函数。包括寄存器地址的输出,
汇编开始段以及结尾段的输出。在某些指令的输出控制中存在多个定义位置,例如
MIPS 的 SW 指令就在程序段和结尾段分别定义,在移植时应该分别查找。在 c 文件中
的汇编输出使用标准的 printf 函数,指令的操作数位置和格式的修改时可以通过调换函
数顺序以及修改 printf 指令进行。
9
第三章 GCC 编译与 GCC 移植
3.2.3 target.md
机器描述的格式为与内部数据描述 RTX 有关的 RTL[11]
。指令的生成和匹配是由指
令模板定义的,最终生成的汇编代码格式也是由指令模板所规定的。一个机器描述文
件包括:指令及指令属性的定义模板,指令拆分及指令优化的定义。
典型的指令模板(Instruction defnition patterns)格式如下所示[12]
:
(define_pattern-type “(optional) pattern name”
[(set (target)
(the operand))
optionally more setting-expressions]
“optional paternal-condition”
“output template”
(optionally: [attribute settings]))
其中,
pattern name: 模板名称,一个模板的名称并不是必须的,但是一定是唯一的。有
一些特殊名称是被预留和预定义好的,叫做标准名称 (standard names)。例如,单整数
模式 (SI mode) 的寄存器加法模板名称为 addsi3,代表着 add-single-integer-3 操作数行
为。3 操作数行为的格式是被强制定义的,后两个操作数是源操作数,而第一个是目的
操作数。若是一个标准的行为模板没有模板名称,库函数将会被调用。
pattern type: 有两种模板类型:
define insn: 最主要的模板类型,用于指令的生成和匹配;
define expand: 标准模板 (standard-named) 最好使用其他的普通指令来替代库函数
调用,而这就是模板扩展 (pattern expansion)。模板扩展只有当操作和标准指令相关联时
才使用,当由简单指令归并为复杂指令是模板扩展不起作用。
模板类型为 define insn 和 define expand 的模板作用时调用 gen name() 构造函数,
当需要时从标准模板生成的构造函数被 GCC 内部调用。GCC 内部可以使用存在的指令
为简单的数学和逻辑运算归并丢失的运算指令。例如,若是机器没有双字节和指令,两
个单字节的和指令可以被用来代替这个操作。
the operand: 一个复杂操作的集合,对于加法操作 (plus:M(operand1) (operand2)) 里
面的 operand1 和 operand2 可以被任何其他的操作所代替。标准操作模板有固定的格式
和操作数位置。因此 define expand 并不是特指原始的模板,而只是表示结果的模板。
编译过程分析树的叶节点是操作数匹配表达式,其格式为 (match operand type:M
operand-number "predicate" "constraints"). 模板的操作和操作符的序号通常是从零开始的
固定增序数字,当然第一个目的操作数是 0. 匹配表达式可以有四种不同的类型:
◦ match operand: 最主要的匹配类型,预订符定义了操作中可以允许哪些操作数,
限定符更严格的指出了可以允许何种操作数的组合。
◦ match operator: 匹配一系列的操作,例如加,和,或等。可以像操作数类型一
样定义操作的预订符。
10
第三章 GCC 编译与 GCC 移植
◦ match dup: 就像 operand number 一样是作为操作数使用。只需指出操作数序号,
并不需要其他任何参数,例如 (match dup 1)。当然,在匹配过程中 match dup 只适用于
早操作中并不变更的操作数,例如 swap Rt Rd,其操作数本身并没有发生任何变化。
◦ match op dup: 像 match dup 定义一样的符号操作。
predicate:预订符 (predicate) 是一个 C 函数,返回一个为 1 或 0 的整数值。这是几
个预定义的函数[11][12]
:
函数 作用
register operand 寄存器操作数
address operand 地址操作数
immediate operand 立即数或立即数地址操作数
const int operand 常量整型
const double operand 浮点型常量
nonimmediate operand 非立即数操作数
memory operand 存储器 (内存) 操作数
general operand 通用操作数 (寄存器,常量或存储器)
indirect operand 简介寻址方式的地址操作数
comparison operator 比较运算操作
constraints: 限定符 (constraints) 是一组字符串,可以为空,在预订符 (predicate) 的
基础上再加以限制。操作没有限定符,而操作数一般都有。限定符描述操作数可能的组
合,对其加以限制。
target: 指令的输出目标,指令若是仅对标志进行置位,则在 target 上使用 cc0 来代
替。target 的格式和其他的操作数一样,但是不能为操作。
more setting-expressions: 可选项,这里的设置在平行 (parallel) 行为中被执行,所以
输出和输入不可以相互调用。
paternal-condition: 可选的状态设定。对于标准操作不需要进行操作数测试[3]
。
output template: 汇编代码输出的格式定义。汇编运算符,操作数位置,操作数形
式(ex.16 进制,10 进制),在 define insn 中直接设定输出,在 define expand 中则执行
此段的 C 语言程序,可以进行分支条件汇编代码输出。
attribute settings: 若本条指令不是默认定义或者已定义的指令,可以在此加入定
义。
3.2.4 匹配原理
GCC 通过使用生成的 RTL 代码片段对机器描述.md 文件进行匹配。对每一个 RTL
代码片段来说,都要从.md 的文件头开始扫描,逐个匹配直到找到一个 INSN DEFINE
定义的寄存器行为和 RTL 片段完全相同。若是扫描过后没有发现可匹配的 INSN 定义
则报错。
例如对于 C 语句"i++" 的编译过程:
11
第三章 GCC 编译与 GCC 移植
图 3.2 "i++" 的编译过程
通过对 GCC 编译器使用 RTL 输出命令"gcc -rd hello.c -o hello.s" 而生成中间 RTL 片
段,从中找出 i++ 相关的语句:
(insn 22 20 24 (set (reg:SI 73)
(plus:SI (reg:SI 74)
(const_int 1))) -1 (nil)
(nil))
在.md 文件中可以和"i++" 的 RTL 片段匹配的定义片段为:
(define_insn "addsi3"
[(set (match_operand:SI 0 "register_operand" "=d")
(plus:SI (match_operand:SI 1 "register_operand" "dJ")
(match_operand:SI 2 "arith_operand" "dI")))]
"TARGET_HARD_FLOAT"
"addt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
其中,addsi3 是定义的名称,名称与功能无关,仅作区分。这里的 add 是加,si 代
表的 single integer 模式,3 代表是三寄存器语句[11]
;
set 和 plus 组合描述的是运算行为,将后面两个寄存器/操作数的内容相加,存储在
第一个寄存器中;
而 match operand 后面所描述的是对操作数的匹配,例如 register operand 是寄存器
约束。
由于"i++" 生成的 RTL 片段和.md 里的 INSN 定义行为相符,GCC 后端判断可以进
行匹配,就可以按照输出中设定的"addt%0, %1, %2" 进行汇编程序输出。我们可以得
到"add $2, $3, 1" 类似的汇编语句。
12
第三章 GCC 编译与 GCC 移植
3.2.5 移植方法
GCC 移植中最重要的就是修改和编写 target.md 文件。由于 MiniSys2 系统的指令集
是基于 MIPS32 指令集设定的,这里可以直接在 MIPS32 指令集的基础上进行修改和定
义的新增。
首先需要设置新的 MiniSys2 目标平台,将 GCC VERSION/config 下的 ss 文件夹复
制并重命名为 minisys2,同时将文件夹内所有文件名中的 ss 修改为 MiniSys2. 另外还需
要在 GCC 目录下的 configure 文件中新增 Minisys2 配置块 (复制并修改 sslittle 块)。在
config.sub 里添加 MiniSys2 类型信息,配置交叉编译器成功后,就可以进行 GCC 的移
植和测试了。
鉴于 MIPS32 指令集里没有自增和自减命令,这里先增加一个 INC 命令,汇编行
为"INC Rd",执行工作则是 Rd = Rd + 1;由于 GCC 对.md 文件的匹配过程中是按照自
上而下的顺序进行的,为了让新增加的 INC 指令可以被检测到,应该放在文件前面的
部分。否则,在扫描过程中将首先匹配 add 指令,因为 add 指令可以通过 add $2, $3, 1
的方式进行自增运算。
(define_insn "incsi1"
[(set (match_operand:SI 0 "register_operand" "=d")
(plus:SI (match_operand:SI 1 "register_operand" "0")
(const_int 1)))]
""
"INCt%0"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
这个 INSN 模板检测的行为是:寄存器和常数 1 相加,结果存在寄存器中。
通过配置 GCC 目录下的 configure 文件,对 host target 等进行配置,然后编译,就
可以得到可以执行交叉编译的 CC1 程序。为了检测新加入的模板,可以写一个简单 C
程序,例如:
int main()
{
int i = 0;
i++;
return 0;
}
通过对旧 CC1 编译器和新增过 INC 指令的新 CC1 编译器分别生成的汇编程序对
比,可以发现旧编译器对自增运算执行的是 addu 指令,而新的编译器执行的是 INC 指
令。通过对汇编程序的查看和演算,说明 INC 指令移植成功。下面的汇编程序片段中,
addu 执行的是 $2 = $3 + 1,
13
第三章 GCC 编译与 GCC 移植
; 原 cc1 编译器生成的汇编程序片段
li $2,0x00000005 # 5
sw $2,16($fp)
lw $3,16($fp)
addu $2,$3,1
move $3,$2
; 加入 INC 的 cc1 编译器生成的汇编程序片段
li $2,0x00000005 # 5
sw $2,16($fp)
lw $3,16($fp)
INC $2
move $3,$2
接下来对 DEC 指令进行移植,DEC 的汇编格式是"DEC Rd",运算行为 Rd = Rd - 1,
根据模板的编写方法,直接将 INC 的 INSN 进行名称替换,并且将 PLUS 改为 MINUS
应该能正确识别 RTL 片段,但是这里却出现问题。
(define_insn "decsi1"
[(set (match_operand:SI 0 "register_operand" "=d")
(minus:SI (match_operand:SI 1 "register_operand" "0")
(const_int 1)))]
""
"DECt%0"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
尽管 INSN 模板完全修改自 INC,但是扫描过程中却始终不匹配。通过查看输出的
RTL 相关片段:
(insn 31 29 33 (set (reg:SI 76)
(plus:SI (reg:SI 77)
(const_int -1))) -1 (nil)
(nil))
以及 ALU 运算方法可以发现,对于减法的运算本质和加法相同,只是符号进行区
别。所以尽管 RTL 模板规定里有 minus 这一运算方法,这里 d 的 INSN 模板仍旧要使用
PLUS 运算进行匹配。正确的 DEC 模板如下,使用 plus 行为对常数 -1 进行运算,可以
正确得到 GCC 匹配。
同时对 GCC 的算数编译方式提出疑问,是否对于立即数/常数的算数运算不论正负
都采用加法模式匹配?后面将通过实验来确定。
(define_insn "decsi1"
[(set (match_operand:SI 0 "register_operand" "=d")
(plus:SI (match_operand:SI 1 "register_operand" "dJ")
14
第三章 GCC 编译与 GCC 移植
(const_int -1)))]
""
"DECt%0"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
按照 INC 和 DEC 的 INSN 模板形式,便可以新增或者修改机器描述,从而达到
GCC 移植的目的。当然因为 GCC 的复杂性和灵活性,移植后的编译器功能还需要进一
步的调试与优化修改。对于 MiniSys2 的移植,由于指令集和编译器同步开发,可以根
据 GCC 的限制和条件进一步的优化指令集设置,从而更深入的利用强大的 GCC 编译
功能,并将 MiniSys2 系统的功能效率发挥完善。
15
第四章 GCC 移植的详细过程
编译器的移植过程中,按照指令集中 [算数运算指令],[逻辑运算指令] 以及 [其他
指令] 的顺序逐一进行模板编写。同时,由于指令集设定中的指令并不全会被编译器识
别并输出,参照 GCC 的编译汇编过程,仅对可以编译并执行的指令进行移植。
4.1 算数指令
4.1.1 ADD, ADDI
ADD 汇编描述 ADD Rd, Rs, Rt; 执行运算 Rd = Rs + Rt;
ADDI 汇编描述 ADDI Rd, Rs, im16; 执行运算 Rd = Rs + (0x)im16;
通过对 C 语句"i = i + j" 和"i = i + 5" 的 GCC DUMP 的 RTL 片段为:
(insn 56 54 58 (set (reg:SI 85)
(plus:SI (reg:SI 83)
(reg:SI 84))) -1 (nil)
(nil))
(insn 40 38 42 (set (reg:SI 80)
(plus:SI (reg:SI 79)
(const_int 5))) -1 (nil)
(nil))
两条指令分别是寄存器/寄存器加法运算和寄存器/立即数加法运算。通过对 RTL 片
段和汇编格式的观察和分析,这两条指令功能相似,仅第三个参数不同,这种情况和
MIPS 指令集中的 XOR 与 XORI 相同。在 MIPS 中 XOR/XORI 是作为一条指令出现的,
复制并进行修改,新的 ADD/ADDI 机器描述如下:
(define_insn "addisi3"
[(set (match_operand:SI 0 "register_operand" "=d,d")
(plus:SI (match_operand:SI 1 "uns_arith_operand" "%d,d")
(match_operand:SI 2 "uns_arith_operand" "d,K")))]
""
"@
ADDt%0,%1,%2
ADDIt%0,%1,%x2"
[(set_attr "type" "arith")
16
第四章 GCC 移植的详细过程
(set_attr "mode" "SI")
(set_attr "length" "1")])
在这个 INSN 定义中同时识别两条指令,操作数匹配中使用逗号分割第一与第二个
指令限定。同样在汇编输出中,用 @ 表示第一行为第一个指令输出,第二行为第二个
指令输出。由于 ADD/ADDI 和 XOR/XORI 在指令手册中的形式和功能是相似的,所以
可以直接进行修改。
尽管可以分成两条单独的 ADD 和 ADDI 指令,但是由于每一条指令的模板是被顺
序匹配,则模板数量越多编译速度越慢。为了优化编译器的效率,两条指令还是合并为
一个模板。
4.1.2 SUB,SUBI
SUB 和 SUBI 与同类型的加法指令相似,对于"i = j - i" 和"i = i - 6",其 RTL 片段为:
(insn 65 63 67 (set (reg:SI 88)
(minus:SI (reg:SI 86)
(reg:SI 87))) -1 (nil)
(nil))
(insn 47 45 49 (set (reg:SI 82)
(plus:SI (reg:SI 81)
(const_int -6))) -1 (nil)
(nil))
同样是在运算指令中的 SUB/SUBI 指令就不能像 ADD/ADDI 一样进行移植,因为
通过分析 GCC 对寄存器/立即数的减法编译可以得知,在立即数减法操作中调用的仍旧
是加法操作,只不过是加一个负数,例如 Rd = Rs + (-Rt); 这一点和 DEC 操作十分类似。
所以对 SUBI 仍旧需要使用加法判断:
(define_insn "subisi3"
[(set (match_operand:SI 0 "register_operand" "=d")
(plus:SI (match_operand:SI 1 "reg_or_0_operand" "dJ")
(match_operand:SI 2 "arith_operand" "dI")))]
"GET_CODE (operands[2]) != CONST_INT || INTVAL (operands[2]) != -32768"
"SUBIt%0,%z1,%x2"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
在 SUBI 的 INSN 模板中,尽管使用的是 plus 匹配,但是仍旧会出现误操作导致将
正常的加法混淆,这里采用简单的办法避免出现错误:由于 GCC 中 RTL CODE 对机器
描述的匹配是由上自下顺序扫描,所以将这个 INSN 放在之前所编写的 ADD/ADDI 模
板后面,当有正常加法运算的时候将先被 ADD/ADDI 匹配,从而避免问题。
17
第四章 GCC 移植的详细过程
值得注意的是,由于 SUBI 调用的加法运算,所以生成的汇编代码中立即数将表示
为负数,由于系统中立即数采用 16 进制表示,所以在后面的汇编器设计和处理器运算
处理上要格外注意。
而 SUB 指令的模板使用的还是 minus 运算方式,所以仅需将加法运算模板的运算
方式更改即可:
(define_insn "subnewsi3"
[(set (match_operand:SI 0 "register_operand" "=d")
(minus:SI (match_operand:SI 1 "register_operand" "dJ")
(match_operand:SI 2 "register_operand" "dJ")))]
""
"SUBt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
另外在对 GCC 的汇编实验中发现,尽管多数指令集设计中算数运算的三个寄存器
可以不同,例如 MiniSys2 中的 ADD Rd, Rs, Rt,但是 GCC 的前端分析树生成以及后
端 RTL 片段的生成中目的寄存器(寄存器 0,Rd)和源寄存器 (寄存器 1, Rs) 是固定相
同的。即不管 C 语言中的加法指令是 i = i + j 或 i = j + k,后端转换中一定会生成如同
ADD $2, $2, $3 这样的命令。
在进行负数测试时会发现 GCC 匹配的另一组模板:negsi. 从机器描述文件中找到
negsi 所在的位置,将其中的汇编输出改为 SUB 即可满足指令集要求。同样的,减法运
算在多个模板内用到,包括对 $fp 的操作等,应该逐一的找到并将输出改为 SUB。
4.1.3 验证
这样算数运算指令之中的 6 个基本指令模板就完成了。编译 GCC 源代码,目录中
生成的 cc1 就是可以使用的编译器。验证过程中编写一段测试 C 程序,进行自增,自
减,寄存器加,立即数加,寄存器减,立即数减运算:
int main()
{
int i = 0;
int j = 1;
int m = 10;
i++;
j--;
i = j + 5;
j = i - 5;
i = i + j;
18
第四章 GCC 移植的详细过程
j = m - i;
return 0;
}
通过在 Linux 环境下使用生成的 cc1 进行编译,得到下面的汇编代码,汇编程序中
和算数运算没有关联的部分被省略:
main:
sw $0,16($fp)
li $2,0x00000001 # 1
sw $2,20($fp)
li $2,0x0000000a # 10
sw $2,24($fp)
lw $3,16($fp)
INC $2
move $3,$2
sw $3,16($fp)
lw $3,20($fp)
DEC $2
move $3,$2
sw $3,20($fp)
lw $2,20($fp)
ADDI $3,$2,0x0005
sw $3,16($fp)
lw $2,16($fp)
SUBI $3,$2,0xfffb
sw $3,20($fp)
lw $2,16($fp)
lw $3,20($fp)
ADD $2,$2,$3
sw $2,16($fp)
lw $2,24($fp)
lw $3,16($fp)
SUB $2,$2,$3
sw $2,20($fp)
move $2,$0
}
其中大写命令是更改或者新增的指令,其他的是 MIPS 的命令。可以看出 ADDI 和
SUBI 命令语句中的立即数是 16 进制表示,分别为 +5 和 -5.
另外,MiniSys2 系统指令集并没有设计乘法和除法运算,若是有此指令根据 MIPS
的指令模板进行简单修改即可。
19
第四章 GCC 移植的详细过程
4.2 逻辑指令
4.2.1 AND,OR,XOR
在 MIPS 指令集和 MiniSys2 指令集中 AND/ANDI, OR/ORI, XOR/XORI 完全一样,
只需简单更改汇编输出语句即可。需要修改的匹配模板是 andsi3, iorsi3, xorsi3. 由于立
即数逻辑运算需要汇编语句中的操作数表达为 16 进制,则要保证模板中输出部分的操
作数前有 x,表示 16 进制输出。例如"ANDIt%0, %1, %x2" 表示汇编的立即数和运算
输出语句。
在立即数逻辑运算中有一种特例。对于正数的立即数运算将正常匹配模板,而负数
的立即数逻辑运算不会直接匹配。使用 GCC 对测试 C 语句"i = i & -5" 进行编译,在编
译器的输出中,首先将 -5 存入了寄存器,再调用模板中的寄存器和运算进行处理。这
说明系统中对负数的逻辑运算将不会被用到。
4.2.2 NOR, NORI
尽管 NOR 指令在两个指令集中功能和运算相同,但是由于 NOR 在多个匹配 INSN
中使用,则需要在每个模板中都修改汇编输出。通过在机器描述中搜索 NOR 字符串,
有两个 INSN 在编译中会经常用到:norsi 和 one-cmplsi2. 前者进行标准的 NOR 指令逻
辑运算,后者执行单操作数非运算。通过修改输出汇编将 NOR 指令实现。
对于 NORI, 尽管修改成功却并不会在逻辑 (寄存器 | 立即数) 调用。这里通过编译
实验可以得知:NORI 逻辑会被 GCC 前端分解为 Rs 和立即数的和,以及其结果的非两
步。这个结果足以表明在 GCC 的编译中,NORI 将不会起任何作用,并且不会被任何
RTL 片段所匹配,如此 NORI 指令变没有存在的意义。
可以提出假设:对于其余的复杂指令,是否同 NORI 一样不会被访问到?
4.2.3 NAND,NANDI,EQL,EQLI
根据上述假设,需要对编译进行分析来确定这些指令会不会被用到。其中 AND 和
ANDI 执行的是 (操作数 1 & 操作数 2) 运算,而 EQL 和 EQLI 执行的是 ((Rs & Rt/im)|( Rs
& Rt/im)) 运算。将四个模板编写好后,通过设计如下 C 程序片段:
i = ~(i & j);
i = ~(j & 5);
i = ~((i & ~j) } (~i & j));
i = ~((j & ~5) } (~j & 5));
在编译器输出的 RTL 片段和汇编结果中并没有发现 NAND/NANDI,EQL/EQLI 指
令的出现,而是被分解成了 AND,NOR 等简单逻辑运算。很明显在 GCC 的前端分析
树生成过程中,将复杂逻辑运算全部简单化,分解到二元运算。
但是同样为复杂运算的 NOR 却出现在了汇编程序中,通过对 NOR 的匹配来源分析
得知,NOR 在编译中仅被非运算调用,即二元的自身非运算。编译过程中三元逻辑 (操
20
第四章 GCC 移植的详细过程
作数 1 | 操作数 2)同样被分解为或运算和结果的非运算,即使将 NORSI3 模板提前也
不会被访问。
可以得出结论:GCC 编译过程中将所有的复杂逻辑运算全部分解为二元逻辑运算。
既然如此复杂逻辑指令在指令集中就没有意义了,NORI,NAND,NANDI,EQL
和 EQLI 指令可以从机器描述中删除,在指令集中只有通过编写汇编程序才可以调用
到。
4.2.4 验证
逻辑指令实现了 AND/ANDI, OR/ORI, XOR/XORI, NOR, 通过编写 C 程序片段来进
行验证。
int i = 1;
int j = 5;
int m = 10;
i = i & 5;
i = ~j | m;
i = ~(i ^ j);
i = ~5;
在编译器输出的汇编程序中,相关联的部分有:
li $2,0x00000001 # 1
sw $2,16($fp)
li $2,0x00000005 # 5
sw $2,20($fp)
li $2,0x0000000a # 10
sw $2,24($fp)
lw $2,16($fp)
ANDI $3,$2,0x0005
sw $3,16($fp)
lw $3,20($fp)
NOR $2,$0,$3
lw $3,24($fp)
OR $2,$2,$3
sw $2,16($fp)
lw $2,16($fp)
lw $3,20($fp)
XOR $2,$2,$3
NOR $3,$0,$2
sw $3,16($fp)
li $2,-6 # 0xfffffffa
sw $2,16($fp)
21
第四章 GCC 移植的详细过程
4.3 其他指令
4.3.1 SW,LW,LB
Store Word 汇编格式 SW Rs,Rt,im16 执行功能 [Rt + im16] = Rs;
Load Word 汇编格式 LW Rd,Rs,im16 执行功能 Rd = Word[Rs + im16];
Load Byte 汇编格式 LB Rd,Rs,im15 执行功能 Rd = Byte[Rs + im16];
这三个存取指令在 MIPS 和 MiniSys2 指令集中的功能和名称是一样的,并不需要对
模板进行改变,所以仅需将指令所在的程序找到即可。SW,LW 以及 LB 所在的位置并
非是机器描述文件中,而是在定义 C 程序中,处于寄存器存取的判断函数部分和汇编
程序结尾定义段。
MIPS 指令和 MiniSys2 指令在 SW 与 LW 指令格式不同,MIPS 是将基址和偏移地
址放在一个操作数内用括号分开 (16($fp)),而 MiniSys2 风格则是将基址和偏移地址
放在不同的操作数内 ($fp,16),而这里需要对 MIPS 的指令格式定义进行修改来满足
MiniSys2 指令集的条件。
操作数地址定义在机器定义 C 文件的 print operand address (file, addr) 函数中。在
函数结尾处有两个操作,output addr const (file, offset) 和 fprintf (file, "(%s)", reg names
[REGNO (reg)]); 前者输出立即数偏移地址,后者输出寄存器基址。为了满足移植条件,
将两个函数的位置互相调换,并修改寄存器基址的格式,调整后的程序如下:
fprintf (file, "%s,", reg_names [REGNO (reg)]);
output_addr_const (file, offset);
在汇编程序结尾定义段还有两处 LW 需要修改:
if (store_p || !TARGET_ABICALLS
|| regno != (PIC_OFFSET_TABLE_REGNUM - GP_REG_FIRST)) {
fprintf (file, "t%st%s,%s,%ldn",
(store_p) ? "LW" : "LW",
reg_names[regno],
reg_names[REGNO(base_reg_rtx)],
gp_offset - base_offset);
}
这样寻址方式的表达就调整完成了。在后面的汇编器设计中只需按照指令定义从不
同的操作数取出地址即可。
4.3.2 寄存器的立即数存储
在 ARM 和 X86 的汇编语言中,都有 mov 指令来对通用寄存器进行立即数赋值,在
MIPS 指令集中也有 li 指令执行该操作。而在 MiniSys2 系统中却没有对寄存器赋立即数
值的指令,只是可以用其他例如算数运算指令来间接实现。为了提高系统的效率,最好
22
第四章 GCC 移植的详细过程
在 MiniSys2 指令集中加入立即数存通用寄存器的命令,可以沿用 MIPS 的 li 指令,或
者在 mov 指令内加入存储立即数的功能。
4.3.3 MOV
MOV 指令执行的命令是寄存器数据传送,在 MiniSys2 指令集中通过识别字来区分
全字节传送还是字节范围间的数据传送。在 GCC 的编译过程当中不提供寄存器字节片
段传送的功能,所以编译仅会用到 MOV 的第一条寄存器全字节的传送指令。
MiniSys2 的 MOV 指令和 MIPS 的 move 指令执行的功能相似,由于编译过程中不
会用到片段字节传送,仅可以对指令形式进行修改。在 GCC 中 move 指令识别三种寄
存器行为,一是寄存器向寄存器的数据传送,另一种是 0 号寄存器(接地)向通用寄存
器赋 0 值,还有一种是 fp 指针向 sp 指针数据的传送。在 C 程序的寄存器存取判断函数
和汇编结束处理函数部分可以找到三种识别语句,在 MOV 指令结尾需加上 0x0000 来
在 MiniSys2 系统中对指令传送范围进行识别。
4.3.4 BNE,BEQ
BNE Rd,Rs,im16;
BEQ Rd,Rs,im16;
执行条件跳转功能,指令判断第一,第二操作数是否相等并根据条件向立即数偏移
地址进行跳转。BNE 和 BEQ 在 MIPS 与 MiniSys2 指令集中功能完全一样,定义在机器
描述的 branch zero 模板。模板中通过对 RTL 代码片段的操作数行为来判断输出哪种条
件跳转指令:
switch (GET_CODE (operands[0]))
{ case EQ: return "%*BEQ%?t%z1,%.,%2";
case NE: return "%*BNE%?t%z1,%.,%2";
case GTU: return "%*BNE%?t%z1,%.,%2";
case LEU: return "%*BEQ%?t%z1,%.,%2";
case GEU: return "%*Jt%2";
case LTU: return "%*BNE%?t%.,%.,%2";
}
当执行 EQ(Equal) 动作时输出 BEQ 指令,当执行 NE(Not Equal) 时输出 BNE 指令。
当 RTL 代码需要时可以对反向动作进行匹配,这时在模板中另有定义,当执行 EQ 时
输出 BNE 指令,当执行 NE 时出处 BEQ 指令。
4.3.5 JLI,JI,JR
JLI 指令执行的是无条件跳转并 LINK 指令,将当前地址存入寄存器后向立即数地
址进行跳转,用于函数的调用和返回。一般将当前地址存入 31 号寄存器,当跳转返回
的时候从 31 号寄存器取出地址填入 PC。但是当返回时一般需要执行跳转源的下一条地
址,所以 JLI 指令应当存储跳转源的地址 +8;JLI 匹配位于 call internal1 模板。
23
第四章 GCC 移植的详细过程
JI 执行的是向操作数的立即数地址无条件跳转,与 MIPS 中的 J 指令相同,JR 执行
的是想寄存器中的地址无条件跳转,与 MIPS 中的 JR 指令相同。
4.3.6 AL,AR,SL,SR
AL,AR 是依照寄存器内容左移/右移,而 SL,SR 是依照立即数内容左移/右移。
在 MIPS 指令集中并没有与 AL,AR 这两条相近的指令,所以对于 AL 和 AR 需要新
增模板。观察 MIPS 中的左移/右移指令模板,第三个操作数可以识别为立即数或寄存
器。而为了让 AL 和 AR 指令从中分离,可以新增同样操作的指令模板,在第三操作数
仅对寄存器 (register operand) 进行识别即可:
(define_insn "ashlREGsi3"
[(set (match_operand:SI 0 "register_operand" "=d")
(ashift:SI (match_operand:SI 1 "register_operand" "d")
(match_operand:SI 2 "register_operand" "dI")))]
""
"ALt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "mode" "SI")
(set_attr "length" "1")])
AR 的指令模板相似,仅是执行的运算操作不同。同时需要将 AR 和 AL 的指令模
板放在之前左移/右移的模板前,这样先对 AR/AL 进行识别就可以将两种不同的操作数
分开。
对于 SL 和 SR 指令,将 MIPS 中的移位模板汇编输出进行重命名即可。
4.3.7 比较指令
MiniSys2 中并没有类似于 MIPS 中的 slt 这样的比较指令,可以用来和 BNE,BEQ 等
指令配合进行比较条件跳转。标准的比较条件跳转是 C 语言中的 if(i < j)then 语句,对
于 i < j 的判断使用 slt 指令并对 i>j 和 i<j 两种情况分别向寄存器置 0 或 1,紧接着用
BNE 或 BEQ 指令对条件进行判断并跳转。
缺少这样的比较指令使得 MiniSys2 指令系统不具备编译判断大小语句的能力。应
当加入一条类似 slt 这样的指令,或者在跳转指令中加入对操作数比较然后跳转这样的
一体指令。
24
第五章 编译器综合测试
5.1 实现命令
编译器移植实现的命令如下:
ADD Rd, Rs, Rt [Rd = Rs + Rt]
SUB Rd, Rs, Rt [Rd = Rs - Rt]
ADDI Rd, Rs, im16 [Rd = Rs + im16]
SUBI Rd, Rs, im16 [Rd = Rs - im16]
INC Rd [Rd = Rd + 1]
DEC Rd [Rd = Rd - 1]
AND Rd, Rs, Rt [Rd = Rs & Rt]
ANDI Rd, Rs, im16 [Rd = Rs & (0x)im16]
OR Rd, Rs, Rt [Rd = Rs | Rt]
ORI Rd, Rs, im16 [Rd = Rs | (0x)im16]
XOR Rd, Rs, Rt [Rd = Rs ∧ Rt]
XORI Rd, Rs, im16 [Rd = Rs ∧ im16]
NOR Rd, Rs, Rt [Rd = ∼(Rs | Rt)]
SW Rs, Rt, im16 [(Rt + im16) = Rs]
LW Rd, Rs, im16 [Rd = (Rs + im16)]
LB Rd, Rs, im16 [Rd = (Rs + im16)]
LI Rd, im16 [Rd = im16]
MOV Rd, Rs, 0x0000 [Rd = Rs]
BNE Rd, Rs, im16 [PC = (Rd!=Rs) ? NPC + im16 : NPC]
BEQ Rd, Rs, im16 [PC = (Rd==Rs) ? NPC + im16 : NPC]
JLI Rd, im20 [LINK, PC = Rd]
JI im28 [PC = im28]
JR Rd [PC = Rd]
AL Rd, Rs, Rt [Rd = (Rs<<Rt)]
AR Rd, Rs, Rt [Rd = (Rs<<Rt)]
SL Rd, Rs, im16 [Rd = (Rs<<im16)]
SR Rd, Rs, im16 [Rd = (Rs<<im16)]
SLT Rd, Rs, Rt [Rd = (Rs < Rt) ? 1 : 0 ]
25
第五章 编译器综合测试
这些命令是编译产生汇编语言的基本命令。但是指令集只实现了单整数指令
(SIMODE),并没有对浮点数等提出命令定义。对于 MiniSys2 指令集,除了上述基本命
令其余的大都不会被编译器产生。编译器前端支持所有 GCC 的标准语言。
5.2 综合验证
这里编写 C++ 测试代码,测试赋值,数学运算,逻辑运算的功能,同时对常用的
循环,判断,符合运算等作出编译验证:
1 i n t i = 3;
2 i n t j = 2;
3 i n t m = 1;
4 i f ( i > j )
5 i ++;
6 e l s e
7 i = j − 7;
8 while ( i > 9 )
9 {
10 i −−;
11 j = i ˆ (˜ j & m) ;
12 i = j << 3;
13 }
14 i = j >> i ;
使用 cc1 编译器对 hello.cpp 文件进行普通编译,由于没有配置汇编器只生成汇编文
件 hello.s:
1 main :
2 SUB $sp , $sp ,40
3 SW $31 , $sp ,36
4 SW $fp , $sp ,32
5 MOV $fp , $sp ,0 x0000
6 JLI $31 , main
7 LI $2 ,0 x00000003 # 对寄存器赋值整数 3 并存入存储器
8 SW $2 , $fp ,16
9 LI $2 ,0 x00000002 # 对寄存器赋值整数 2 并存入存储器
10 SW $2 , $fp ,20
11 LI $2 ,0 x00000001 # 对寄存器赋值整数 1 并存入存储器
12 SW $2 , $fp ,24
13 LW $2 , $fp ,16 # 取出 i
26
第五章 编译器综合测试
14 LW $3 , $fp ,20 # 取出 j
15 SLT $2 , $3 , $2 # 比较两数大小并置数
16 BEQ $2 , $0 , $L2 # 若置数等于零则跳转L2
17 LW $3 , $fp ,16 # 取出 i
18 MOV $2 , $3 ,0 x0000
19 INC $2 # 自增
20 MOV $3 , $2 ,0 x0000
21 SW $3 , $fp ,16 # 将结果存入存储器
22 J I $L3 # 跳转至L3
23 $L2 :
24 LW $2 , $fp ,20 # 取出 j
25 SUBI $3 , $2 ,0 x f f f 9 # 执行j −7
26 SW $3 , $fp ,16 # 将结果存入存储器
27 $L3 :
28 . s e t n o r e o r d e r
29 NOP
30 . s e t r e o r d e r
31 $L4 :
32 LW $2 , $fp ,16
33 SLT $3 , $2 ,10 # 对寄存器和立即数进行比较并置数
34 BEQ $3 , $0 , $L6 # 若置数等于零则跳转L6
35 J I $L5 # 无条件跳转L5
36 $L6 :
37 LW $3 , $fp ,16 # 取出 i
38 MOV $2 , $3 ,0 x0000
39 DEC $2 # 自减
40 MOV $3 , $2 ,0 x0000
41 SW $3 , $fp ,16 # 存入存储器
42 LW $3 , $fp ,20 # 取出 j
43 NOR $2 , $0 , $3 # 取非
44 LW $3 , $fp ,24 # 取出m
45 AND $2 , $2 , $3 # 对二者进行逻辑和运算
46 LW $3 , $fp ,16 # 取出 i
47 XOR $2 , $3 , $2 # 与上述结果进行异或运算
48 SW $2 , $fp ,20 # 存入结果
49 LW $2 , $fp ,20 # 取出 j
50 SL $3 , $2 ,3 # 左移三位
51 SW $3 , $fp ,16 # 存入 i
27
第五章 编译器综合测试
52 J I $L4 # 无条件跳转L4
53 $L5 :
54 LW $2 , $fp ,20 # 取出 j
55 LW $3 , $fp ,16 # 取出 i
56 AR $2 , $2 , $3 # 右移(寄存器指定)位
57 SW $2 , $fp ,16 # 存入 i
58 LW $2 , $fp ,16
59 J I $L1 # 向结尾跳转
可以看出所有的基本指令都被移植成功,算数和逻辑指令执行准确,赋值和寄存器
存取指令正常。其中 LI 和 SLT 指令是新增加的,并不存在于初始的 MiniSys2 指令集,
同时两个指令也可以集成到其他的基本指令中。当然,还要在汇编器上做一些调整才
能正确的在 MiniSys2 计算机系统上运行成功。
当然由于没有开启 GCC 优化功能,所以汇编程序按照源程序直接转换,并没有对
前段的语法树或后端的 RTL 片段进行优化。若是开启 GCC 优化功能生成的程序将更加
简短高效。
28
第六章 GCC-MiniSys2 交叉编译器
6.1 编译环境与编译器设置
在不同平台上使用目标为 MiniSys2 的 GCC 编译器时需要先对 GCC 编译。使用
shell 进入 gcc 目录后对编译环境进行配置:
.configure -host=i386-intel-linux -target=minisys2little-na-sstrix
命令行中 configure 是配置命令,-host 后面是编译平台的环境,分别为 i386(体系结
构)-intel(附加信息)-linux(操作系统)。而 -target 就是目标平台的环境,同样定义为体系
机构 -附加信息 -操作系统。从 -host 平台向 -target 平台进行的就是交叉编译,得到的程
序可以在目标平台上运行。
环境配置好后对编译目录进行清理 (make clean),之后就可以直接编译了 (make)。
最后得到的 cc1 就是 -i386-linux 平台上执行的对 minisys2little-sstrix 平台的交叉编译器。
编译好的 GCC-MiniSys2 交叉编译器可以在任何 Linux 发行版操作系统上使用。为
了程序正常的运行建议在 Linux 内核为 2.6.32-22 以上的版本中使用。
6.2 编译器的使用
6.2.1 编译器命令行界面
GCC-MiniSys2 交叉编译的默认工作环境是 Linux 的 Shell 命令行界面。
简单编译结果如下, 默认对环节时间成本进行统计:
user@user−computer : ˜ / s i m p l e s c a l a r / gcc −2.6.3 $ . / cc1 h e l l o . cpp
main
time in parse : 0.010000
time in i n t e g r a t i o n : 0.000000
time in jump : 0.000000
time in cse : 0.000000
time in loop : 0.000000
time in cse2 : 0.000000
time in flow : 0.000000
time in combine : 0.000000
time in sched : 0.000000
29
第六章 GCC-MiniSys2 交叉编译器
time in l o c a l −a l l o c : 0.000000
time in global −a l l o c : 0.000000
time in sched2 : 0.000000
time in dbranch : 0.000000
time in shorten −branch : 0.000000
time in stack −reg : 0.000000
time in f i n a l : 0.000000
time in v a r c o n s t : 0.000000
time in symout : 0.000000
time in dump : 0.000000
6.2.2 编译程序
这是一个 MiniSys2 编译器的简单编译命令例子:
.cc1 hello.cpp
这句命令对 hello 的 c++ 程序源代码进行编译。默认输出文件名为 hello.s 的汇编程
序代码。通常对于 GCC 前端支持的编程语言都可以进行编译。
6.2.3 编译调试
.cc1 -g hello.cpp
-g 命令是 gcc 编译调试命令,编译时对汇编程序加入调试命令,例如:
.loc 1 4
LI $2,0x00000002 # 2
SW $2,$fp,20
.loc 1 6
LW $3,$fp,16
INC $2
MOV $3,$2,0x0000
SW $3,$fp,16
6.2.4 RTL 片段输出
.cc1 -dr hello.cpp
编译器将生成 hello.cpp.rtl,hello.s;前者为 RTL 代码 DUMP 输出,后者为汇编程
序。
30
第六章 GCC-MiniSys2 交叉编译器
6.2.5 GCC 编译器的优化
GCC 编译器支持四级优化,分别由 0 到 3 代表的从无优化到最大优化。
.cc1 hello.c
.cc1 -O1 hello.c
两句命令分别为无优化和等级 1 优化。
针对编译器的优化功能,这里对一段 C 程序进行编译,在无优化和等级 1 优化下对
编译的结果进行对比。
int main()
{
int i = 1;
int j = 2;
i++;
i = ~(i ^ j);
if( i < j )
{
i = i + (j & 7);
}
return i;
}
分别对程序进行两种优化,由于程序源代码不大,所以消耗时间忽略不计。当然如
果对很长的程序进行编译可能优化功能要消耗更长的时间。下面是对无优化和 O1 优化
程序成本的对比:
无优化 O1 优化
汇编程序大小 61 行 33 行
汇编字符统计 915 502
主函数统计 640 221
变量使用 8 0
寄存器使用 2 1
可以看出优化后汇编程序效率大大提高,由于汇编程序过于冗长就不放在此处,但
是根据源代码对比可以发现,GCC 无优化时的编译不对程序的进程进行预测,仅将立
即数数学运算进行合并;而 O1 优化同时对程序的进程进行预测并去除不会用到的分
支,由于作为测试的 C 程序执行的是常数的数学和逻辑运算,尽管存在寄存器数据和
立即数,编译器对程序的常数运算步骤进行合并,并预测判断和循环分支,直接给出运
31
第六章 GCC-MiniSys2 交叉编译器
行结果,所以优化后的汇编程序除去开头和结尾段仅有将结果直接输入寄存器和 PC 命
令两句。
6.2.6 汇编程序注释
.cc1 -dumpversion hello.cpp
这个命令将在生成的汇编程序内对每一句的匹配来源进行注释,例如对于立即数加
法运算的匹配,指出了使用哪一个匹配模板进行的输出:
ADDI $3,$2,0x0005 # 22 addisi3/2
同时生产文件 hello.cpp.rtl,hello.cpp.cse 以及 hello.s,分别为 RTL 程序和带注释的
汇编语言程序。
6.2.7 程序成本统计
.cc1 -dumpmachine hello.cpp
-dumpmachine 命令将产生很多文件,其中 hello.cpp.flow 和 hello.cpp.greg 文件内对
寄存器的使用次数和序号状态进行了统计。
6.3 IDE 集成/高级编译器界面
GCC-MiniSys2 交叉编译器可以被多种 IDE(Integrated development environment) 调用
并进行程序的编译。
图 6.1 QT4 结合 GCC-MiniSys2 交叉编译器
这里以 Linux 下的 QT4/QT creator 为例进行设置:
32
第六章 GCC-MiniSys2 交叉编译器
◦ 在工程选项里使用 (Remove Step) 清空所有的 Build Step 和 Build Step.
◦ 在 Build Step 里新加入一个 Custom Process Step.
◦ 编译器路径选择项目目录里的 cc1 交叉编译器.
◦ 附加命令里填写 main.cpp
图 6.2 QT4 的编译器集成设置
这样就可以使用 QT4 的 IDE 进行程序设计和编译了。
33
第七章 项目总结
7.1 研究成果与说明
⋄ 编译器移植源代码
◦ Minisys2.md 机器描述/匹配模板 130,200bytes 4466 行
◦ Minisys2.h 机器描述/宏文件 132,473bytes 3640 行
◦ Minisys2.c 机器描述/宏文件 141,728bytes 5370 行
⋄ 编译器可执行程序
◦ cc1
7.2 总结
在 GCC 编译器的移植工作开始时,对于项目能做到什么程度并没有确切的认识,
尽管有移植成功例子和文献可以作为参考,但是整体内容并不明晰,并且需要大量的
准备工作和背景知识。尤其是机器描述中模板的 RTL 定义,由于没有准确的或者正式
的说明文档来介绍匹配模板的编写方法,以及 RTL 的语义定义等,所以对指令的移植
可以说是在摸索和猜测中进行。对于模板中每一部分,每一个参数的含义都要经过多
次试验改动才能确定确切的功能和意义,进而才可以进行修改,添加。
当对于模板内容和含义有了初步的理解之后,指令的定义工作才稍微有一些头绪。
当然,更为复杂的工作还在后面,例如算数减法的定义,有通过加法运算进行减法的操
作,也有直接调用减法运算的操作,而最终的匹配定义则是靠多次的猜测试验才能确
定。又如逻辑运算指令集,当所有的逻辑匹配模板定义完成后才发现有一部分是不会
用到的,为了验证这种猜测对数十种逻辑运算组合进行了试验,最后才能得出不使用
多余指令的结论。
另外,对于相关知识的了解是十分重要的。对于 GCC 编译器,各种指令集,乃至
Linux 系统都要十分熟悉才不会在工作中出现不必要的问题,这也是为什么有必要在移
植前对 GCC 的说明文档进行系统的学习。
尽管移植工作十分复杂,但是相较于开发完整的编译器,GCC 移植还是比较节省
成本和工作量需求的。通过 GCC 移植可以短期内开发出一个支持多种前端语言的高效
程序编译器,继而通过交叉编译直接生成可以在嵌入式平台可以直接运行的程序,又
避免了汇编程序开发的周期长,效率低等问题。
GCC 编译器对 MiniSys2 系统的移植也可以证明其良好的可移植性。GCC 对于其他
34
第七章 项目总结
的处理器架构也是可以完整移植的,当然主流的处理器几乎都可以被 GCC 原生支持,
新的架构尤其是嵌入式系统架构也可以根据移植原理短期内开发出自己的高级语言编
译器。
同时虽然 GCC 的移植是根据指令集的设定来进行,但是更多的需要在指令集设计
初始就对将两者合二为一进行考虑。因为 GCC 尽管是前后端独立,但是其分析树和
RTL 代码片段确是固定的,这也就导致了指令并不是完全会被 GCC 用到,也就说明了
指令集的设定应该根据 GCC 的功能来进行,才能将编译器和指令完整结合,更深入的
提高系统的效率。
7.3 展望
本文说明了 GCC 移植的理论并给出了 GCC 移植的方法。通过对关键文件的修改和
定义实现了 GCC 编译器对 MiniSys2 架构指令系统的兼容,并且测试了移植后的编译器
对程序的编译能力与汇编代码生成的准确程度。但是由于时间与能力所限,仅对一般
程序可以用到的指令进行了单整数 (SI mode) 操作移植定义,在测试时也只对普通的加
减法运算和简单逻辑运算进行验证,并没有涉及复杂运算,64 位运算等。
若是可以进一步的就 GCC 的后端移植进行改进和优化,例如根据硬件平台修改汇
编输出和 RTL 片段生成方式,并基于各种程序的编译方案将可应用的复杂指令也进行
修改移植,则 GCC 编译器本身的能力将会完全的发挥,MiniSys2 平台的应用效率也会
充分的得到利用。
35
参考文献
1 吴克寿, 任小西, 李仁发, et al. GCC 到 Nios 系统的移植研究与实现 [J]. 湖南大学学报: 自然科学
版, 2007, 34(008):70--73.
2 Free Software Foundation. GCC, the GNU Compiler Collection[EB/OL]. 2010. http://gcc.gnu.
org/.
3 Stallman R. GNU compiler collection internals[J]. Free Software Foundation, 2002.
4 杨全胜, 李正兴, 成炼, 等. Minisys SoC 结构与设计 [DB/OL]. 2009.
5 Quansheng Y, Zhengxing L. Minisys 2 Instructions Set Quick Reference[EB/OL]. 2009.
6 MIPs Technologies. MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction
Set[EB/OL]. 2009.
7 蔡杰. GCC 编译系统结构分析与后端移植实践 [D]:[Master's Thesis].[S.l.]: 浙江大学, 2004.
8 林秉毅, 劉興傑, 陳立杰, 等. 追蹤 GCC 核心原始碼與移植相關之研究 [DB/OL]. 2005.
9 Griffith A. GCC: The Complete Reference[M].[S.l.]: McGraw-Hill/Osborne, 2002.
10 冯钢, 郑扣根. 基于 GCC 的交叉编译器研究与开发 [J]. 计算机工程与设计, 2004, 25(011):1880--
1883.
11 林丹. 解析 GCC 的移植机制 [C/OL]. 2002.
12 Nilsson H. Porting GCC for dunces[J]. Master Thesis, 2000, 5:43--54.
13 Parthey J. Porting the GCC-Backend to a VLIW-Architecture[J]. Chemnitz University of Technology,
2004.
14 郭学鹏, 赵克佳. 跨文件编译模式与基于 GCC 的实现 [J]. 计算机工程与科学, 2007, 29(004):111--
115.
36
附录 A 研究与开发环境
开发平台和软件的说明来自 wikipedia.org
A.1 Ubuntu
Ubuntu 是一个以桌面应用为主的 GNU/Linux 操作系统,Ubuntu 基于 Debian 发行版和 GNOME
桌面环境,与 Debian 的不同在于它每 6 个月会发布一个新版本。Ubuntu 的目标在于为一般用户提
供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。
GCC 的移植工作主要在 Ubuntu 下进行,利用其自身的 GCC 编译器对 GCC 的交叉编译版本进
行编译。
A.2 GEDIT
gedit 是一个 GNOME 桌面环境下兼容 UTF-8 的文本编辑器。它简单易用,有良好的语法高亮,
对中文支持很好,支持包括 gb2313、gbk 在内的多种字符编码。gedit 是一个自由软件。
gedit 包含语法高亮和标签编辑多个文件的功能。利用 GNOME VFS 库,它还可以编辑远程文
件。它支持完整的恢复和重做系统以及查找和替换。
它还支持包括多语言拼写检查和一个灵活的插件系统,可以动态地添加新特性。例如 snippets
和外部程序的整合。
在移植工作中使用 gedit 对 target.md,target.h 和 target.c 文件进行编辑。
A.3 Notepad++
Notepad++ 是一套自由软件的纯文本编辑器。该软件以 GPL 发布,有完整的中文化接口及支持
多国语言撰写的功能(采用万国码 UTF-8 技术)。它的功能比 Windows 中的 Notepad(记事簿)强
大,除了可以用来制作一般的纯文字说明文件,也十分适合当作撰写电脑程序的编辑器。
支持的程序设计语言如下:Java、C / C++、C#、HTML、PHP、XML、JavaScript、makefile、
ASCII 艺术、doxygen、ASP、VB / VBScript、Unix Shell Script、BAT (Batch file)、SQL、Objective-
C、CSS、PASCAL、Perl、Python、Lua、TCL、Assembler、Ruby、Lisp、Scheme、Diff、Smalltalk、
Postscript and VHDL。
由于 Notepad++ 支持 lisp 语言,在 windows 下编辑文档时使用 notepad++ 作为主要的编辑器。
可以对机器描述文件准确的解析并高亮和折叠。
37
附录 A 研究与开发环境
A.4 LATEX
LATEX 是一种基于 TeX 的排版系统,由美国计算机学家莱斯利 · 兰伯特(Leslie Lamport)在 20
世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由
TeX 所提供的强大功能。
在论文的撰写过程中实际是使用的 XeLaTex,便可以良好的支持多语种文本编辑和生成。
A.4.1 seuthesis
seuthesis 是东南大学学位论文的 LaTex 模板,目前版本号为 seuthesis 2.0.0。根据模板设置只需
对定义项进行填写并使用 Tex 语法编写文章,即可生成符合规范格式的学位论文。这个系统同样适
用于生成从简单的信件到完整书籍的所有其他种类的文档。
38

More Related Content

Viewers also liked

Pastorijwoning van Paola klaar en bewoond
Pastorijwoning van Paola klaar en bewoondPastorijwoning van Paola klaar en bewoond
Pastorijwoning van Paola klaar en bewoondThierry Debels
 
Cuestiones de interés: "los alumn@s preguntan, las familias responden".
Cuestiones de interés: "los alumn@s preguntan, las familias responden".Cuestiones de interés: "los alumn@s preguntan, las familias responden".
Cuestiones de interés: "los alumn@s preguntan, las familias responden".Francisco Cid Fornell
 
Charla Infantil 2.016
Charla Infantil 2.016Charla Infantil 2.016
Charla Infantil 2.016blogppalsie
 
Tomorrow Leaders
Tomorrow Leaders Tomorrow Leaders
Tomorrow Leaders Colin Fabig
 
Oval Colorful Rings from ShinyJewel
Oval Colorful Rings from ShinyJewelOval Colorful Rings from ShinyJewel
Oval Colorful Rings from ShinyJewelkent_chan
 

Viewers also liked (11)

Mp2
Mp2Mp2
Mp2
 
Pastorijwoning van Paola klaar en bewoond
Pastorijwoning van Paola klaar en bewoondPastorijwoning van Paola klaar en bewoond
Pastorijwoning van Paola klaar en bewoond
 
Lidia y marina
Lidia y marinaLidia y marina
Lidia y marina
 
Cuestiones de interés: "los alumn@s preguntan, las familias responden".
Cuestiones de interés: "los alumn@s preguntan, las familias responden".Cuestiones de interés: "los alumn@s preguntan, las familias responden".
Cuestiones de interés: "los alumn@s preguntan, las familias responden".
 
Anticonvulsivos antihta
Anticonvulsivos antihtaAnticonvulsivos antihta
Anticonvulsivos antihta
 
Charla Infantil 2.016
Charla Infantil 2.016Charla Infantil 2.016
Charla Infantil 2.016
 
Tomorrow Leaders
Tomorrow Leaders Tomorrow Leaders
Tomorrow Leaders
 
Débuter avec mdt
Débuter avec mdtDébuter avec mdt
Débuter avec mdt
 
Web 20
Web 20Web 20
Web 20
 
Jose celi
Jose celiJose celi
Jose celi
 
Oval Colorful Rings from ShinyJewel
Oval Colorful Rings from ShinyJewelOval Colorful Rings from ShinyJewel
Oval Colorful Rings from ShinyJewel
 

Similar to GCC_Porting_on_MiniSystem

Pl sql developer7.0用户指南
Pl sql developer7.0用户指南Pl sql developer7.0用户指南
Pl sql developer7.0用户指南irons_zhou
 
51 cto下载 2010-ccna实验手册
51 cto下载 2010-ccna实验手册51 cto下载 2010-ccna实验手册
51 cto下载 2010-ccna实验手册poker mr
 
Memcached
MemcachedMemcached
Memcachednowise
 
Memcached全面剖析
Memcached全面剖析Memcached全面剖析
Memcached全面剖析chen vivian
 
Memcached
MemcachedMemcached
Memcachedfeizone
 
深入浅出My sql数据库开发、优化与管理维护
深入浅出My sql数据库开发、优化与管理维护深入浅出My sql数据库开发、优化与管理维护
深入浅出My sql数据库开发、优化与管理维护colderboy17
 
深入浅出My sql数据库开发、优化与管理维护 (1)
深入浅出My sql数据库开发、优化与管理维护 (1)深入浅出My sql数据库开发、优化与管理维护 (1)
深入浅出My sql数据库开发、优化与管理维护 (1)colderboy17
 
J Boss+J Bpm+J Pdl用户开发手册 3.2.3
J Boss+J Bpm+J Pdl用户开发手册 3.2.3J Boss+J Bpm+J Pdl用户开发手册 3.2.3
J Boss+J Bpm+J Pdl用户开发手册 3.2.3yiditushe
 
高质量C++c 编程指南
高质量C++c 编程指南高质量C++c 编程指南
高质量C++c 编程指南flying886
 
Progit.zh
Progit.zhProgit.zh
Progit.zhhhguang
 
iml_chinese.pdf
iml_chinese.pdfiml_chinese.pdf
iml_chinese.pdfhjie2
 
Csdn Emag(Oracle)第三期
Csdn Emag(Oracle)第三期Csdn Emag(Oracle)第三期
Csdn Emag(Oracle)第三期yiditushe
 
Glibc memory management
Glibc memory managementGlibc memory management
Glibc memory managementdddsf3562
 
Twido hw guide modular & compact bases
Twido hw guide   modular & compact basesTwido hw guide   modular & compact bases
Twido hw guide modular & compact basesJohanna Mesa Torres
 

Similar to GCC_Porting_on_MiniSystem (20)

Pl sql developer7.0用户指南
Pl sql developer7.0用户指南Pl sql developer7.0用户指南
Pl sql developer7.0用户指南
 
51 cto下载 2010-ccna实验手册
51 cto下载 2010-ccna实验手册51 cto下载 2010-ccna实验手册
51 cto下载 2010-ccna实验手册
 
Memcached
MemcachedMemcached
Memcached
 
Memcached
MemcachedMemcached
Memcached
 
Memcached全面剖析
Memcached全面剖析Memcached全面剖析
Memcached全面剖析
 
Memcached
MemcachedMemcached
Memcached
 
Direct show
Direct showDirect show
Direct show
 
深入浅出My sql数据库开发、优化与管理维护
深入浅出My sql数据库开发、优化与管理维护深入浅出My sql数据库开发、优化与管理维护
深入浅出My sql数据库开发、优化与管理维护
 
深入浅出My sql数据库开发、优化与管理维护 (1)
深入浅出My sql数据库开发、优化与管理维护 (1)深入浅出My sql数据库开发、优化与管理维护 (1)
深入浅出My sql数据库开发、优化与管理维护 (1)
 
Fluxay
FluxayFluxay
Fluxay
 
Twido programming guide
Twido programming guideTwido programming guide
Twido programming guide
 
J Boss+J Bpm+J Pdl用户开发手册 3.2.3
J Boss+J Bpm+J Pdl用户开发手册 3.2.3J Boss+J Bpm+J Pdl用户开发手册 3.2.3
J Boss+J Bpm+J Pdl用户开发手册 3.2.3
 
高质量C++c 编程指南
高质量C++c 编程指南高质量C++c 编程指南
高质量C++c 编程指南
 
Progit.zh
Progit.zhProgit.zh
Progit.zh
 
iml_chinese.pdf
iml_chinese.pdfiml_chinese.pdf
iml_chinese.pdf
 
Progit cn
Progit cnProgit cn
Progit cn
 
Micro2440 Um 20090817
Micro2440 Um 20090817Micro2440 Um 20090817
Micro2440 Um 20090817
 
Csdn Emag(Oracle)第三期
Csdn Emag(Oracle)第三期Csdn Emag(Oracle)第三期
Csdn Emag(Oracle)第三期
 
Glibc memory management
Glibc memory managementGlibc memory management
Glibc memory management
 
Twido hw guide modular & compact bases
Twido hw guide   modular & compact basesTwido hw guide   modular & compact bases
Twido hw guide modular & compact bases
 

GCC_Porting_on_MiniSystem

  • 1. 毕业设计(论文)报告 题目 基于 Minisystem 的 GCC 编译器的移植 计算机科学与工程学院院(系) 计算机科学与技术 专 业 学 号 09006309 学生姓名 马小京 指导教师 王晓蔚 起讫日期 2010.3—2010.6 设计地点 计算机楼 2010 年 6 月 7 日
  • 2. 基于 Minisystem 的 GCC 编译器的移植 09006309 马小京 指导教师 王晓蔚 摘 要 根据 MiniSystem 系统的编译需要,研究并分析 GCC 的编译过程以及移植原理。通 过对 GCC 编译器后端的研究和 RTL 语言的理解,说明后端编译时编译器的处理过程; 给出移植过程中对机器定义中 INSN 的修改和新建的方法,以及机器定义中 RTL 的含 义和作用。以编译过程中典型的指令模板为例子,使用移植后的编译器对 C 程序片段 进行编译,说明了 GCC 移植的方法并列出编译产生的结果。同时,在移植过程中根据 指令集设置对编译器进行优化,讨论并且对优化前后的编译器进行了对比。 关键词: 编译器, GCC, 移植, RTL i
  • 3. GCC Porting on MiniSystem 09006309 Ma Xiaojing Wang Xiaowei Abstract Based on the demand of program compile on MiniSystem, the project prepared for re- search and analyse the compile process and porting principle of GCC. Through a study in the back-end of GCC and to understand the RTL language, the thesis explains the operation pro- cesses of the back-end compile. Then shows how to modify or create a INSN definition in the machine-definition of the porting, also indicates the meaning and function of RTL in the machine-definition. Takes several typical instruction templates as example, we compiled the C language program by ported compiler and illustrate the result of user-defined compile through GCC porting. Meanwhile, the compiler was optimized based on the set of instructions. Then the thesis discussed and compared with the original and optimized cimpiler. Keywords: Compiler, GCC, Porting, RTL ii
  • 4. 本论文专用术语的注释表 GCC: GNU 编译器集合。 移植: 使得 GCC 可以编译在新定义处理器体系下运作。 RTL: 寄存器转换语言,在 GCC 编译中间起转换作用的描述语言。 前端:Front-end,GCC 编译器中跟机器无关而跟编程语言相关的前向处理部分。 后端:Back-end,GCC 编译器中跟语言无关而跟目标机器平台相关的后续处理部分。 汇编语言:一种与机器硬件紧密相关的底层程序语言,与机器语言存在部分对应关系。 机器语言:二进制程序代码,计算机能够直接识别并且执行,程序直接通过计算机硬件结构赋 予操作功能。同时,与硬件对应意味着无法再不同结构的计算机上执行。 指令集:计算机架构中与程序运行的相关内容。包含了本身的数据类型,指令,寄存器,地址 模型,存储结构,中断以及意外处理,I/O 处理等。一个指令集包括一系列的指令(机器语言)定 义,以及和处理器相关的运行命令。 机器描述:GCC 后端编译中对目标平台指令集进行描述的文件,编译过程中 GCC 会将中间代 码来匹配机器描述用来生成汇编程序。 iii
  • 5. 目 录 要 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · i Abstract · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ii 本论文 用术语的注释表 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · iii 目录 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · iv 第一章 绪论 (前言) · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 1 1.1 课题研究背景 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 课题研究目的和意义 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 国内外研究状况 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 论文主要研究内容 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.5 论文组织结构 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 第二章 GCC 移植系统和平台介绍 · · · · · · · · · · · · · · · · · · · · · · · · 4 2.1 GCC 编译器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.2 MiniSystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.3 ABI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.3.1 编译与移植环境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 第三章 GCC 编译与 GCC 移植 · · · · · · · · · · · · · · · · · · · · · · · · · · 7 3.1 GCC 的编译原理和过程 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.1.1 前端 Front-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.1.2 中间接口/程序代码优化 . . . . . . . . . . . . . . . . . . . . . . . . 8 3.1.3 后端 Back-end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.1.4 RTL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.2 GCC 的移植原理和方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.2.1 target.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.2.2 target.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.2.3 target.md . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2.4 匹配原理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.2.5 移植方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 第四章 GCC 移植的详细过程 · · · · · · · · · · · · · · · · · · · · · · · · · · 16 4.1 算数指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.1.1 ADD, ADDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.1.2 SUB,SUBI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 iv
  • 6. 4.1.3 验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2 逻辑指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.1 AND,OR,XOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.2 NOR, NORI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.3 NAND,NANDI,EQL,EQLI . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.4 验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.3 其他指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.1 SW,LW,LB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.2 寄存器的立即数存储 . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.3 MOV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.3.4 BNE,BEQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.3.5 JLI,JI,JR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.3.6 AL,AR,SL,SR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.3.7 比较指令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 第五章 编译器综合测试 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 25 5.1 实现命令 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 5.2 综合验证 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 第六章 GCC-MiniSys2 交叉编译器 · · · · · · · · · · · · · · · · · · · · · · · · 29 6.1 编译环境与编译器设置 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 6.2 编译器的使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 6.2.1 编译器命令行界面 . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 6.2.2 编译程序 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 6.2.3 编译调试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 6.2.4 RTL 片段输出 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 6.2.5 GCC 编译器的优化 . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 6.2.6 汇编程序注释 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2.7 程序成本统计 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.3 IDE 集成/高级编译器界面 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 第七章 项目总结 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 34 7.1 研究成果与说明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 7.2 总结 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 7.3 展望 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 参考文献 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 36 附录 A 研究与开发环境 · · · · · · · · · · · · · · · · · · · · · · · · · · · · · 37 A.1 Ubuntu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 A.2 GEDIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 A.3 Notepad++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 A.4 LATEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 A.4.1 seuthesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 v
  • 7. 第一章 绪论 (前言) 1.1 课题研究背景 对于一款成熟的计算机处理器而言,一个完善的、合适的编译器是其正常并且高效 作用的关键。而对于 MiniSystem 这款基于 FPGA 的软核处理器来说,只进行汇编程序 的开发是对处理器资源的浪费,汇编语言编程周期长,代码调试不方便,同时也是实现 复杂程序功能的一个重要瓶颈[1] 。这里就需要设计编译器对高级语言程序进行编译,进 而可以对处理器进行有效的利用。 编译器的开发有两种方法可以选用,第一种是根据硬件平台的设计完全编写一个 可以使用编译器,这种方法与平台结合完整,但是开发成本高,利用范围窄,并且 编译器质量得不到保证。另一种方法就是利用成熟的 GCC 编译器集合,进行移植工 作,使得可以在任何支持 GCC 的平台上对目标平台进行程序交叉编译,这种方法开 发成本低并且方便快捷,编译过程的质量和优化完全基于多年发展成功的 GCC 内部 功能。虽然使用移植的 GCC 编译器需要在指令集设计时参照 GCC 进行某些功能限 制,但是同时这也是最为现实的并且最易实现的。同时,GCC 提供了大部分流行的高 级编程语言前端支持,不需要任何新的设计就可以使用移植后的 GCC 编译成功例如 C,C++,Java,Fortran,Ada 等程序[2] ,而对如此多的程序语言支持在前一种方法下的工作量 是不可想象的。 同时 GCC 的移植也是一个十分复杂的过程。GCC 经过长期的开发发展,虽然程序 严谨精密,但是不可否定的是其自身结构与功能实现也相当繁琐复杂。尽管 GCC 移植 仅需要对后端与平台相关部分进行设计,前期的准备工作和相关内容的整理与研究也 是必不可少的。 1.2 课题研究目的和意义 为了给 MiniSys2 系统提供一个高效的高级语言程序编译器,对 GCC 编译器集合进 行功能和结构分析,从而研究并总结出 GCC 对目标平台的移植原理和方法。在提出的 移植方法基础上,设计符合 MiniSys2 系统结构的编译器并对 GCC 进行系统移植。移植 后的编译器就是 GCC-MiniSys2 交叉编译系统。 使用基于 GCC 的移植编译器可以提高 MiniSys2 系统代码的编写效率,并实现更为 复杂的程序功能,使得程序员可以在标准的计算机平台上编写并编译 C 语言等高级语 言程序,直接通过编译器生成可以在 MiniSys2 平台上运行的 ELF 机器程序。在程序的 1
  • 8. 第一章 绪论 (前言) 设计和编写过程中并不需要对硬件底层进行了解与干涉,交叉编译器会对寄存器和指 令作出分析并进行调用。 同时,低成本的 GCC 移植编译器兼容 GCC 编译器集合的所有功能。GCC-MiniSys2 交叉编译器支持多种前端语言,在 GCC 的前端 -中间优化基础上根据移植定义再对后 端进行优化。 1.3 国内外研究状况 GCC 的可移植理论由 GCC 开发社区基于其编译器结构提出,但是由于 GCC 已经 可以支持大部分流行的计算机平台,关于 GCC 移植的文献资料并不十分丰富。 国内的龙芯 2(GODSON 2) 处理器上使用的是 GCC 移植编译器,但是龙芯 2 执行 的是 MIPS LIKE 指令集,移植工作仅对少量的特殊指令进行支持即可,所以移植进程 并不完整和全面。除了龙芯系统就没有关于 GCC 的详细移植例子可循了,仅有部分文 献对移植的理论和方法进行简要的说明,对于 GCC-MiniSys2 项目并没有太多的参考意 义。 国外对于 GCC 的移植研究也并不多,但是有个别的研究非常全面因此对 GCC- MiniSys2 移植有很重要的指导作用。其中有"Porting GCC for Dunces" 对 GCC 像 Dunces 平台的移植作出了详细的讲解,同时此篇文献也多次被众多中文相关论文引用。在 Hans-Peter Nilsson 的这篇文章中对 GCC 移植中很多相关的知识与信息作出了讲解,包 括很多 GCC 手册中也没有提及的内容,例如两种 INSN 模板每一项的含义及作用等。 还有 VLIW 架构,NIOS 架构的移植等,当然在 GCC 编译器的源代码中也包含了每 一种支持的目标平台机器描述代码。 1.4 论文主要研究内容 本文主要对 GCC 编译器的编译结构,编译原理以及移植原理作出研究,针对其可 移植性对每一条 MiniSys2 系统的指令进行分析。对于前端 -后端的编译步骤和后端移植 性进行分析,在论文中对 GCC 移植方法的研究基础上,对 MiniSys2 指令集进行移植并 对结果进行测试与分析,同时利用 GCC 的前端优化功能测试移植编译器的优化效果。 在编译器的移植过程中所要进行的工作有如下几点: ⋄ 对编译器功能的了解; ⋄ 理解 GCC 的结构与功能实现; ⋄ 对 GCC 移植方法的了解; ⋄ 理解 MiniSystem 的指令集及每一条指令所实现的功能; ⋄ 对于某一条指令的实验性修改; ⋄ 对于某一条指令的实验性添加; ⋄ 对全部指令进行可行性分析并进行修改或添加; ⋄ 验证编译器功能; 2
  • 9. 第一章 绪论 (前言) ⋄ 优化编译器功能; 1.5 论文组织结构 论文分为七章及若干小节,包含参考文献列表和附录等。第一章对项目的目的和 背景作出简介;第二章介绍了 GCC 编译器和移植目标平台;第三章和第四章详细的说 明了 GCC 编译与移植的原理,过程,方法以及对各种代表性指令的移植步骤;第五章 和第六章对编译器进行测试,同时说明了 GCC-MiniSys2 交叉编译器的各种命令,功能 等。第七章对项目进行总结。 3
  • 10. 第二章 GCC 移植系统和平台介绍 2.1 GCC 编译器 GCC 的全称是 GNU Compiler Collection[3] , 是目前最为重要,应用最广的开放源代 码软件。作为一个编译器的集合,旨在支持多种不同的高级编程语言,同时可以根据不 同的目标平台分别编译出能够执行的程序。作为 GNU 计划/GNU 工具链中的重要组成 部分,GCC 现已成为了 Unix/Linux 平台系统中的标准编译器,并且可以通过 Cygwin 和 MinGW 等中间转换平台支持在 Windows 环境下的编译。GCC 由 1987 年时只支持 C 语 言的 GNU C Compiler 计划发展到现在,已经可以广泛支持 C, C++, Fortran, Objective-C, Java 和 Ada 等多种语言;GCC 同时支持多种处理器结构平台,从标准 X86 处理器到多 种嵌入式平台处理器都可以进行程序编译或者交叉编译,例如 IA-32(X86), IA-64, ARM, AVR 以及 MIPS 等[2] 。 图 2.1 GCC 编译器集合 GCC 的编译结构中包括一个语言相关的前端,所有语言共同的中介架构,以及一 个目标平台相关的后端。 GCC 在编译过程中通过前端对程序进行处理,得到一个抽象的语法树,再经过中 间和后端将语法树翻译成 RTL,最后得到汇编源程序,通过汇编器生成可以在机器直 接执行的二进制程序代码。整个编译过程相当复杂,但是一般的使用过程中只需简单的 控制命令即可对源程序进行编译或者交叉编译。正是因为 GCC 良好的后端可移植设计, 在编译时仅对 configure 进行少许设置,就可以生成在其他平台良好运行的机器程序。 最新的 GCC 版本是 4.5,发行与 2010 年 4 月 14 日,提供了链接时优化功能 (Link- time Optimazation),更多的插件和对多种语言及目标平台的支持。 4
  • 11. 第二章 GCC 移植系统和平台介绍 2.2 MiniSystem MiniSystem 是由东南大学计算机科学与工程学院,系统结构实验室设计并开发的 RISC SOC 32 位系统。系统设计使用 Altera 的 Cyclone FPGA 作为目标芯片承载 CPU 设 计结构与功能。系统的内部设计分为取指单元,译码单元,控制单元,执行单元和存储 单元。其中取指单元负责从指令存储器获取指令;译码单元主要负责从寄存器获取操 作数和将运算结果回写到寄存器;控制单元完成译码中解析指令和提供控制信号的功 能;执行单元负责算数,逻辑运算,并计算与存储器操作有关的地址;存储单元负责取 数或存数[4] 。 Minisys1 系列 SoC 使用 CPU 主要类别有单周期,多周期,3 级流水线,5 级流水 线,超标量,双核,浮点支持,硬件乘除支持,I2C 总线支持,SDRAM 总线支持等[4] 。 MiniSys1 有 32 个 32 位的通用寄存器,32 位数据线和 16 位地址线。系统包括多种外围 部件与控制器,例如定时器,PWM 控制器和 UART 控制器。处理器中的 32 个寄存器 除了个别有特殊的功能外,其余皆可以作为通用寄存器使用 (例如 0 号寄存器永远返回 值 0)。 MiniSys2 处理器是经过修改和优化的最新设计, MiniSys2 的指令集[5] 在 Mips32 指 令集[6] 的基础上修改了少数指令和功能,并且新增加了一些更加优化,效率更高的处 理器指令。指令集中分为算数运算指令,逻辑运算指令,数据传送指令,堆栈相关指 令,移位指令以及寄存器转移/转换指令,分支转移指令和位操作指令共八个分类近百 条指令。 MiniSys2 的指令分为 R 类型,I 类型和 J 类型,但都是 32 位等长,R,I,J 仅是区分 寄存器操作数,含立即数操作数以及转移地址操作。指令中有四种寻址方式,分别为立 即数寻址,相对寻址,寄存器寻址和寄存器相对寻址。 2.3 ABI ABI(Application Binary Interface) 是计算机底层中程序和程序,程序和操作系统联系 的接口。ABI 定义了程序运行的必要条件:数据类型,数据大小,数据对齐方式,以及 参数传递等。一个完整的 ABI, 例如 Intel Binary Compatibility Standard(iBCS), 可以允许 编译好的程序在支持其 ABI 的不同操作系统上不需修改即可完整运行。 为了可以支持 MiniSys2 的操作系统,采用现代操作系统的通用 ABI 将可以更完整 的将程序和 MiniSys2 兼容。而用 GCC 来进行编译则可方便的提高程序在 ABI 接口上 的兼容性。 2.3.1 编译与移植环境 编译平台:-HOST 硬件平台:I386 Intel CPU 操作系统:Ubuntu 10.04 LTS (Linux 2.6.32-22-generic) 5
  • 12. 第二章 GCC 移植系统和平台介绍 系统编译环境:gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5) 目标编译环境:gcc version 2.6.3 目标平台:-TARGET 硬件平台:MiniSys2 (minisys2little-na-sstrix) 编译 GCC 的目标平台交叉编译器需要用到计算机架构模拟器:SimpleScalar,其中 包含了 binutils,simplescalar 和 gcc-2.6.3 三个程序包,而 gcc-2.6.3 正式需要进行移植工 作的 GCC 编译器源码集合。GCC 源代码目录下 language 是前端的代码支持,而 config 目录内则是对不同体系结构和操作系统支持的定义。在 GCC 的目标平台移植中主要改 动和分析的就是 config 文件夹下的体系结构定义。 6
  • 13. 第三章 GCC 编译与 GCC 移植 3.1 GCC 的编译原理和过程 编译器是一个翻译程序,它读入程序的源文件,经过一系列的编译过程翻译成可以 在指定平台上运行的顺序指令组,即可执行程序。总体上编译程序分为前端和后端两 部分,前端读入源程序,生成分析树,而一旦树生成,编译器后端就可以从其中读出信 息,进而转换成目标平台所能识别的汇编程序。 一般标准的编译器工作流程分为词法分析(Lexical Analyzer), 语法分析(Syntax Analyzer)和中间代码产生(Intermediate-code generator)三部分。同样的,在 GCC 的 结构中,前端执行词法分析和语法分析的工作,后端进行中间代码的产生。 词法分析是编译器的开始部分,读入程序字符,分析并确定符号,数字和标点符号 等。 语法分析处理词法分析生成的符号流,并与语言规则确定关系,最后输出分析树结 构,传递给后端进行处理。 语法分析树会被后端翻译成一种称作寄存器传送语言 (Register Transfer Language, RTL) 的伪汇编语言。GCC 分析 RTL 代码,同时执行将不必要的内容去掉等优化过程。 最终 RTL 代码被翻译成汇编语言,汇编语言可被后面的汇编器翻译成二进制的机 器语言代码。 整个过程如下图所示,输入源程序,输出汇编代码。 图 3.1 GCC 的编译过程 [7] 7
  • 14. 第三章 GCC 编译与 GCC 移植 3.1.1 前端 Front-end 语言相关的 GCC 前端生成供后端处理的分析树。针对不同的编程语言,语法树的 生成规则是不相同的。近几年 GCC 采用了新的 GENERIC 与 GIMPLE 语法树结构,使 得编译的前端产生一个与语言和机器平台都不相关的独立语法树结构,同时也称作适 应大多数现代编程语言的全局优化通用语言。 在编译处理中使用前端结构的优点有[8] : ⋄ 可以直接支持已在 GCC 内部定义好的目标平台,移植新平台只需改动后端的描 述部分,正因为前端的独立性,编译器不用重写全部代码; ⋄ 可以有更多的优化途径,并且更易于分别开发;可以通过不同的前端来适应不同 的语言,不用重新编写整个编译器; ⋄ 相较于传统的编译器,对于错误的检查更为细致和精确。 ⋄ GENERIC-GIMPLE 结构更易于程序代码的优化。 3.1.2 中间接口/程序代码优化 一般的传统编译器前端包含了程序优化功能,但是优化对于语言有相关性,而 GCC 的前端是相对语言独立的,所以 GCC 有一个特殊的中间接口用来对程序进行优 化。GCC 的优化包括消除死码,消除重复运算,消除全局数值重编码等。中间接口的 的实现得以将 GCC 的前端和后端相关联,并彻底的实现 GCC 的语言 -处理器架构独立 性。 3.1.3 后端 Back-end GCC 后端的处理根据不同目标平台的机器描述而区分。机器描述中定义了例如字 长,字节序/位序,以及体系结构,指令系统等,编译器后端通过对定义的参考来帮助 生成 RTL 片段,优化 RTL 片段,以及将 RTL 片段转换成汇编程序。在这些过程中,机 器定义一直指导 RTL 进行工作,每一种目标平台对应单独的机器定义,因此也生成单 独的 RTL 片段。 正是因为 GCC 这种前后端 + 机器定义的结构,我们才可以快速的依照其相关理论 进行目标平台的快速移植。 3.1.4 RTL RTL 即 Register Transfer Language,是一种用来描述计算机高级语言程序和汇编语 言程序中间的表示方法。RTL 表达式和 LISP 语言十分相似,在 GCC 编译的中间状态 时用来描述计算机寄存器行为,同时在机器描述文件中用来体现寄存器匹配模板[2] 。 (set:SI (reg:SI 1) (plus:SI (reg:SI 2)(reg:SI 3))) 8
  • 15. 第三章 GCC 编译与 GCC 移植 这句 RTL 的意思就是,将寄存器 2 和 3 的值相加,存储在寄存器 1 中,这里的 SI 意味着是 Single Integer 模式。在实际的应用当中表达更为复杂,还要有限定和预测的 内容插入其中,另外还会加入属性定义,分类等附加/可选项。同时,寄存器的表达方 法根据目标系统的不同也并不一致。 RTL 的一般形式是 (Object:Mode OP1 OP2...OPn),其中 Object 为表示的对象,可 以为操作数,操作符等;Mode 为对象的数据形式,例如 SI(Single Integer),DI(Double Interger) 等[9] ;OP 则是可选的操作数或操作对象。 这是几个常用的 RTL 表示方法: ⋄ 常数:(const int i),(const string str) 分别为整数和字符串。 ⋄ 寄存器和存储器:(pc),(reg:m n),(mem:m addr adias) 分别为指令寄存器,Mode 为 m 的寄存器 n,地址是 addr 的内存单元。 ⋄ 数学表达式:(plus:m x y),(minus:m x y),(mult:m x y) 分别代表着加法,减法和 乘法运算。(not:m x),(and:m x y),(ior:m x y),(xor:m x y) 分别表示逻辑运算中的非, 和,或和异或运算。(eq:m x y),(gt:m x y),(ge:m x y) 分别为比较操作中的等于,大于 和大于等于。(ltu:m x y),(leu:m x y) 表示无符号的小雨和小于等于比较。 ⋄ 赋值与跳转:(set x y),(call function nargs) 和 (return) 分别代表了赋值,函数调用 和函数返回[10][3] 。 3.2 GCC 的移植原理和方法 在 GCC 的编译中,编译器根据不同的目标平台选用不同的机器描述文件,而将 GCC 移植到一个新的目标平台,就需要修改和编写和目标相关的定义部分,这其中包 括了 target.md,target.h 和 target.c. 3.2.1 target.h target.h 是机器描述的宏文件,定义了机器 ABI 的基本属性[11] : ⋄ 编译器环境:汇编程序语义,编译器的部件切换,头文件的位置以及系统库等。 ⋄ 架构的基本属性:寄存器类型,数目,地址等。 ⋄ ABI(Application Binary Interface):函数的调用方法。 ⋄ 机器描述的基本支持:md 文件中可以用到的定义以及其转化,输出的结果。 3.2.2 target.c 机器描述的 c 文件中定义了汇编生成时用到的输出函数。包括寄存器地址的输出, 汇编开始段以及结尾段的输出。在某些指令的输出控制中存在多个定义位置,例如 MIPS 的 SW 指令就在程序段和结尾段分别定义,在移植时应该分别查找。在 c 文件中 的汇编输出使用标准的 printf 函数,指令的操作数位置和格式的修改时可以通过调换函 数顺序以及修改 printf 指令进行。 9
  • 16. 第三章 GCC 编译与 GCC 移植 3.2.3 target.md 机器描述的格式为与内部数据描述 RTX 有关的 RTL[11] 。指令的生成和匹配是由指 令模板定义的,最终生成的汇编代码格式也是由指令模板所规定的。一个机器描述文 件包括:指令及指令属性的定义模板,指令拆分及指令优化的定义。 典型的指令模板(Instruction defnition patterns)格式如下所示[12] : (define_pattern-type “(optional) pattern name” [(set (target) (the operand)) optionally more setting-expressions] “optional paternal-condition” “output template” (optionally: [attribute settings])) 其中, pattern name: 模板名称,一个模板的名称并不是必须的,但是一定是唯一的。有 一些特殊名称是被预留和预定义好的,叫做标准名称 (standard names)。例如,单整数 模式 (SI mode) 的寄存器加法模板名称为 addsi3,代表着 add-single-integer-3 操作数行 为。3 操作数行为的格式是被强制定义的,后两个操作数是源操作数,而第一个是目的 操作数。若是一个标准的行为模板没有模板名称,库函数将会被调用。 pattern type: 有两种模板类型: define insn: 最主要的模板类型,用于指令的生成和匹配; define expand: 标准模板 (standard-named) 最好使用其他的普通指令来替代库函数 调用,而这就是模板扩展 (pattern expansion)。模板扩展只有当操作和标准指令相关联时 才使用,当由简单指令归并为复杂指令是模板扩展不起作用。 模板类型为 define insn 和 define expand 的模板作用时调用 gen name() 构造函数, 当需要时从标准模板生成的构造函数被 GCC 内部调用。GCC 内部可以使用存在的指令 为简单的数学和逻辑运算归并丢失的运算指令。例如,若是机器没有双字节和指令,两 个单字节的和指令可以被用来代替这个操作。 the operand: 一个复杂操作的集合,对于加法操作 (plus:M(operand1) (operand2)) 里 面的 operand1 和 operand2 可以被任何其他的操作所代替。标准操作模板有固定的格式 和操作数位置。因此 define expand 并不是特指原始的模板,而只是表示结果的模板。 编译过程分析树的叶节点是操作数匹配表达式,其格式为 (match operand type:M operand-number "predicate" "constraints"). 模板的操作和操作符的序号通常是从零开始的 固定增序数字,当然第一个目的操作数是 0. 匹配表达式可以有四种不同的类型: ◦ match operand: 最主要的匹配类型,预订符定义了操作中可以允许哪些操作数, 限定符更严格的指出了可以允许何种操作数的组合。 ◦ match operator: 匹配一系列的操作,例如加,和,或等。可以像操作数类型一 样定义操作的预订符。 10
  • 17. 第三章 GCC 编译与 GCC 移植 ◦ match dup: 就像 operand number 一样是作为操作数使用。只需指出操作数序号, 并不需要其他任何参数,例如 (match dup 1)。当然,在匹配过程中 match dup 只适用于 早操作中并不变更的操作数,例如 swap Rt Rd,其操作数本身并没有发生任何变化。 ◦ match op dup: 像 match dup 定义一样的符号操作。 predicate:预订符 (predicate) 是一个 C 函数,返回一个为 1 或 0 的整数值。这是几 个预定义的函数[11][12] : 函数 作用 register operand 寄存器操作数 address operand 地址操作数 immediate operand 立即数或立即数地址操作数 const int operand 常量整型 const double operand 浮点型常量 nonimmediate operand 非立即数操作数 memory operand 存储器 (内存) 操作数 general operand 通用操作数 (寄存器,常量或存储器) indirect operand 简介寻址方式的地址操作数 comparison operator 比较运算操作 constraints: 限定符 (constraints) 是一组字符串,可以为空,在预订符 (predicate) 的 基础上再加以限制。操作没有限定符,而操作数一般都有。限定符描述操作数可能的组 合,对其加以限制。 target: 指令的输出目标,指令若是仅对标志进行置位,则在 target 上使用 cc0 来代 替。target 的格式和其他的操作数一样,但是不能为操作。 more setting-expressions: 可选项,这里的设置在平行 (parallel) 行为中被执行,所以 输出和输入不可以相互调用。 paternal-condition: 可选的状态设定。对于标准操作不需要进行操作数测试[3] 。 output template: 汇编代码输出的格式定义。汇编运算符,操作数位置,操作数形 式(ex.16 进制,10 进制),在 define insn 中直接设定输出,在 define expand 中则执行 此段的 C 语言程序,可以进行分支条件汇编代码输出。 attribute settings: 若本条指令不是默认定义或者已定义的指令,可以在此加入定 义。 3.2.4 匹配原理 GCC 通过使用生成的 RTL 代码片段对机器描述.md 文件进行匹配。对每一个 RTL 代码片段来说,都要从.md 的文件头开始扫描,逐个匹配直到找到一个 INSN DEFINE 定义的寄存器行为和 RTL 片段完全相同。若是扫描过后没有发现可匹配的 INSN 定义 则报错。 例如对于 C 语句"i++" 的编译过程: 11
  • 18. 第三章 GCC 编译与 GCC 移植 图 3.2 "i++" 的编译过程 通过对 GCC 编译器使用 RTL 输出命令"gcc -rd hello.c -o hello.s" 而生成中间 RTL 片 段,从中找出 i++ 相关的语句: (insn 22 20 24 (set (reg:SI 73) (plus:SI (reg:SI 74) (const_int 1))) -1 (nil) (nil)) 在.md 文件中可以和"i++" 的 RTL 片段匹配的定义片段为: (define_insn "addsi3" [(set (match_operand:SI 0 "register_operand" "=d") (plus:SI (match_operand:SI 1 "register_operand" "dJ") (match_operand:SI 2 "arith_operand" "dI")))] "TARGET_HARD_FLOAT" "addt%0,%1,%2" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 其中,addsi3 是定义的名称,名称与功能无关,仅作区分。这里的 add 是加,si 代 表的 single integer 模式,3 代表是三寄存器语句[11] ; set 和 plus 组合描述的是运算行为,将后面两个寄存器/操作数的内容相加,存储在 第一个寄存器中; 而 match operand 后面所描述的是对操作数的匹配,例如 register operand 是寄存器 约束。 由于"i++" 生成的 RTL 片段和.md 里的 INSN 定义行为相符,GCC 后端判断可以进 行匹配,就可以按照输出中设定的"addt%0, %1, %2" 进行汇编程序输出。我们可以得 到"add $2, $3, 1" 类似的汇编语句。 12
  • 19. 第三章 GCC 编译与 GCC 移植 3.2.5 移植方法 GCC 移植中最重要的就是修改和编写 target.md 文件。由于 MiniSys2 系统的指令集 是基于 MIPS32 指令集设定的,这里可以直接在 MIPS32 指令集的基础上进行修改和定 义的新增。 首先需要设置新的 MiniSys2 目标平台,将 GCC VERSION/config 下的 ss 文件夹复 制并重命名为 minisys2,同时将文件夹内所有文件名中的 ss 修改为 MiniSys2. 另外还需 要在 GCC 目录下的 configure 文件中新增 Minisys2 配置块 (复制并修改 sslittle 块)。在 config.sub 里添加 MiniSys2 类型信息,配置交叉编译器成功后,就可以进行 GCC 的移 植和测试了。 鉴于 MIPS32 指令集里没有自增和自减命令,这里先增加一个 INC 命令,汇编行 为"INC Rd",执行工作则是 Rd = Rd + 1;由于 GCC 对.md 文件的匹配过程中是按照自 上而下的顺序进行的,为了让新增加的 INC 指令可以被检测到,应该放在文件前面的 部分。否则,在扫描过程中将首先匹配 add 指令,因为 add 指令可以通过 add $2, $3, 1 的方式进行自增运算。 (define_insn "incsi1" [(set (match_operand:SI 0 "register_operand" "=d") (plus:SI (match_operand:SI 1 "register_operand" "0") (const_int 1)))] "" "INCt%0" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 这个 INSN 模板检测的行为是:寄存器和常数 1 相加,结果存在寄存器中。 通过配置 GCC 目录下的 configure 文件,对 host target 等进行配置,然后编译,就 可以得到可以执行交叉编译的 CC1 程序。为了检测新加入的模板,可以写一个简单 C 程序,例如: int main() { int i = 0; i++; return 0; } 通过对旧 CC1 编译器和新增过 INC 指令的新 CC1 编译器分别生成的汇编程序对 比,可以发现旧编译器对自增运算执行的是 addu 指令,而新的编译器执行的是 INC 指 令。通过对汇编程序的查看和演算,说明 INC 指令移植成功。下面的汇编程序片段中, addu 执行的是 $2 = $3 + 1, 13
  • 20. 第三章 GCC 编译与 GCC 移植 ; 原 cc1 编译器生成的汇编程序片段 li $2,0x00000005 # 5 sw $2,16($fp) lw $3,16($fp) addu $2,$3,1 move $3,$2 ; 加入 INC 的 cc1 编译器生成的汇编程序片段 li $2,0x00000005 # 5 sw $2,16($fp) lw $3,16($fp) INC $2 move $3,$2 接下来对 DEC 指令进行移植,DEC 的汇编格式是"DEC Rd",运算行为 Rd = Rd - 1, 根据模板的编写方法,直接将 INC 的 INSN 进行名称替换,并且将 PLUS 改为 MINUS 应该能正确识别 RTL 片段,但是这里却出现问题。 (define_insn "decsi1" [(set (match_operand:SI 0 "register_operand" "=d") (minus:SI (match_operand:SI 1 "register_operand" "0") (const_int 1)))] "" "DECt%0" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 尽管 INSN 模板完全修改自 INC,但是扫描过程中却始终不匹配。通过查看输出的 RTL 相关片段: (insn 31 29 33 (set (reg:SI 76) (plus:SI (reg:SI 77) (const_int -1))) -1 (nil) (nil)) 以及 ALU 运算方法可以发现,对于减法的运算本质和加法相同,只是符号进行区 别。所以尽管 RTL 模板规定里有 minus 这一运算方法,这里 d 的 INSN 模板仍旧要使用 PLUS 运算进行匹配。正确的 DEC 模板如下,使用 plus 行为对常数 -1 进行运算,可以 正确得到 GCC 匹配。 同时对 GCC 的算数编译方式提出疑问,是否对于立即数/常数的算数运算不论正负 都采用加法模式匹配?后面将通过实验来确定。 (define_insn "decsi1" [(set (match_operand:SI 0 "register_operand" "=d") (plus:SI (match_operand:SI 1 "register_operand" "dJ") 14
  • 21. 第三章 GCC 编译与 GCC 移植 (const_int -1)))] "" "DECt%0" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 按照 INC 和 DEC 的 INSN 模板形式,便可以新增或者修改机器描述,从而达到 GCC 移植的目的。当然因为 GCC 的复杂性和灵活性,移植后的编译器功能还需要进一 步的调试与优化修改。对于 MiniSys2 的移植,由于指令集和编译器同步开发,可以根 据 GCC 的限制和条件进一步的优化指令集设置,从而更深入的利用强大的 GCC 编译 功能,并将 MiniSys2 系统的功能效率发挥完善。 15
  • 22. 第四章 GCC 移植的详细过程 编译器的移植过程中,按照指令集中 [算数运算指令],[逻辑运算指令] 以及 [其他 指令] 的顺序逐一进行模板编写。同时,由于指令集设定中的指令并不全会被编译器识 别并输出,参照 GCC 的编译汇编过程,仅对可以编译并执行的指令进行移植。 4.1 算数指令 4.1.1 ADD, ADDI ADD 汇编描述 ADD Rd, Rs, Rt; 执行运算 Rd = Rs + Rt; ADDI 汇编描述 ADDI Rd, Rs, im16; 执行运算 Rd = Rs + (0x)im16; 通过对 C 语句"i = i + j" 和"i = i + 5" 的 GCC DUMP 的 RTL 片段为: (insn 56 54 58 (set (reg:SI 85) (plus:SI (reg:SI 83) (reg:SI 84))) -1 (nil) (nil)) (insn 40 38 42 (set (reg:SI 80) (plus:SI (reg:SI 79) (const_int 5))) -1 (nil) (nil)) 两条指令分别是寄存器/寄存器加法运算和寄存器/立即数加法运算。通过对 RTL 片 段和汇编格式的观察和分析,这两条指令功能相似,仅第三个参数不同,这种情况和 MIPS 指令集中的 XOR 与 XORI 相同。在 MIPS 中 XOR/XORI 是作为一条指令出现的, 复制并进行修改,新的 ADD/ADDI 机器描述如下: (define_insn "addisi3" [(set (match_operand:SI 0 "register_operand" "=d,d") (plus:SI (match_operand:SI 1 "uns_arith_operand" "%d,d") (match_operand:SI 2 "uns_arith_operand" "d,K")))] "" "@ ADDt%0,%1,%2 ADDIt%0,%1,%x2" [(set_attr "type" "arith") 16
  • 23. 第四章 GCC 移植的详细过程 (set_attr "mode" "SI") (set_attr "length" "1")]) 在这个 INSN 定义中同时识别两条指令,操作数匹配中使用逗号分割第一与第二个 指令限定。同样在汇编输出中,用 @ 表示第一行为第一个指令输出,第二行为第二个 指令输出。由于 ADD/ADDI 和 XOR/XORI 在指令手册中的形式和功能是相似的,所以 可以直接进行修改。 尽管可以分成两条单独的 ADD 和 ADDI 指令,但是由于每一条指令的模板是被顺 序匹配,则模板数量越多编译速度越慢。为了优化编译器的效率,两条指令还是合并为 一个模板。 4.1.2 SUB,SUBI SUB 和 SUBI 与同类型的加法指令相似,对于"i = j - i" 和"i = i - 6",其 RTL 片段为: (insn 65 63 67 (set (reg:SI 88) (minus:SI (reg:SI 86) (reg:SI 87))) -1 (nil) (nil)) (insn 47 45 49 (set (reg:SI 82) (plus:SI (reg:SI 81) (const_int -6))) -1 (nil) (nil)) 同样是在运算指令中的 SUB/SUBI 指令就不能像 ADD/ADDI 一样进行移植,因为 通过分析 GCC 对寄存器/立即数的减法编译可以得知,在立即数减法操作中调用的仍旧 是加法操作,只不过是加一个负数,例如 Rd = Rs + (-Rt); 这一点和 DEC 操作十分类似。 所以对 SUBI 仍旧需要使用加法判断: (define_insn "subisi3" [(set (match_operand:SI 0 "register_operand" "=d") (plus:SI (match_operand:SI 1 "reg_or_0_operand" "dJ") (match_operand:SI 2 "arith_operand" "dI")))] "GET_CODE (operands[2]) != CONST_INT || INTVAL (operands[2]) != -32768" "SUBIt%0,%z1,%x2" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 在 SUBI 的 INSN 模板中,尽管使用的是 plus 匹配,但是仍旧会出现误操作导致将 正常的加法混淆,这里采用简单的办法避免出现错误:由于 GCC 中 RTL CODE 对机器 描述的匹配是由上自下顺序扫描,所以将这个 INSN 放在之前所编写的 ADD/ADDI 模 板后面,当有正常加法运算的时候将先被 ADD/ADDI 匹配,从而避免问题。 17
  • 24. 第四章 GCC 移植的详细过程 值得注意的是,由于 SUBI 调用的加法运算,所以生成的汇编代码中立即数将表示 为负数,由于系统中立即数采用 16 进制表示,所以在后面的汇编器设计和处理器运算 处理上要格外注意。 而 SUB 指令的模板使用的还是 minus 运算方式,所以仅需将加法运算模板的运算 方式更改即可: (define_insn "subnewsi3" [(set (match_operand:SI 0 "register_operand" "=d") (minus:SI (match_operand:SI 1 "register_operand" "dJ") (match_operand:SI 2 "register_operand" "dJ")))] "" "SUBt%0,%1,%2" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) 另外在对 GCC 的汇编实验中发现,尽管多数指令集设计中算数运算的三个寄存器 可以不同,例如 MiniSys2 中的 ADD Rd, Rs, Rt,但是 GCC 的前端分析树生成以及后 端 RTL 片段的生成中目的寄存器(寄存器 0,Rd)和源寄存器 (寄存器 1, Rs) 是固定相 同的。即不管 C 语言中的加法指令是 i = i + j 或 i = j + k,后端转换中一定会生成如同 ADD $2, $2, $3 这样的命令。 在进行负数测试时会发现 GCC 匹配的另一组模板:negsi. 从机器描述文件中找到 negsi 所在的位置,将其中的汇编输出改为 SUB 即可满足指令集要求。同样的,减法运 算在多个模板内用到,包括对 $fp 的操作等,应该逐一的找到并将输出改为 SUB。 4.1.3 验证 这样算数运算指令之中的 6 个基本指令模板就完成了。编译 GCC 源代码,目录中 生成的 cc1 就是可以使用的编译器。验证过程中编写一段测试 C 程序,进行自增,自 减,寄存器加,立即数加,寄存器减,立即数减运算: int main() { int i = 0; int j = 1; int m = 10; i++; j--; i = j + 5; j = i - 5; i = i + j; 18
  • 25. 第四章 GCC 移植的详细过程 j = m - i; return 0; } 通过在 Linux 环境下使用生成的 cc1 进行编译,得到下面的汇编代码,汇编程序中 和算数运算没有关联的部分被省略: main: sw $0,16($fp) li $2,0x00000001 # 1 sw $2,20($fp) li $2,0x0000000a # 10 sw $2,24($fp) lw $3,16($fp) INC $2 move $3,$2 sw $3,16($fp) lw $3,20($fp) DEC $2 move $3,$2 sw $3,20($fp) lw $2,20($fp) ADDI $3,$2,0x0005 sw $3,16($fp) lw $2,16($fp) SUBI $3,$2,0xfffb sw $3,20($fp) lw $2,16($fp) lw $3,20($fp) ADD $2,$2,$3 sw $2,16($fp) lw $2,24($fp) lw $3,16($fp) SUB $2,$2,$3 sw $2,20($fp) move $2,$0 } 其中大写命令是更改或者新增的指令,其他的是 MIPS 的命令。可以看出 ADDI 和 SUBI 命令语句中的立即数是 16 进制表示,分别为 +5 和 -5. 另外,MiniSys2 系统指令集并没有设计乘法和除法运算,若是有此指令根据 MIPS 的指令模板进行简单修改即可。 19
  • 26. 第四章 GCC 移植的详细过程 4.2 逻辑指令 4.2.1 AND,OR,XOR 在 MIPS 指令集和 MiniSys2 指令集中 AND/ANDI, OR/ORI, XOR/XORI 完全一样, 只需简单更改汇编输出语句即可。需要修改的匹配模板是 andsi3, iorsi3, xorsi3. 由于立 即数逻辑运算需要汇编语句中的操作数表达为 16 进制,则要保证模板中输出部分的操 作数前有 x,表示 16 进制输出。例如"ANDIt%0, %1, %x2" 表示汇编的立即数和运算 输出语句。 在立即数逻辑运算中有一种特例。对于正数的立即数运算将正常匹配模板,而负数 的立即数逻辑运算不会直接匹配。使用 GCC 对测试 C 语句"i = i & -5" 进行编译,在编 译器的输出中,首先将 -5 存入了寄存器,再调用模板中的寄存器和运算进行处理。这 说明系统中对负数的逻辑运算将不会被用到。 4.2.2 NOR, NORI 尽管 NOR 指令在两个指令集中功能和运算相同,但是由于 NOR 在多个匹配 INSN 中使用,则需要在每个模板中都修改汇编输出。通过在机器描述中搜索 NOR 字符串, 有两个 INSN 在编译中会经常用到:norsi 和 one-cmplsi2. 前者进行标准的 NOR 指令逻 辑运算,后者执行单操作数非运算。通过修改输出汇编将 NOR 指令实现。 对于 NORI, 尽管修改成功却并不会在逻辑 (寄存器 | 立即数) 调用。这里通过编译 实验可以得知:NORI 逻辑会被 GCC 前端分解为 Rs 和立即数的和,以及其结果的非两 步。这个结果足以表明在 GCC 的编译中,NORI 将不会起任何作用,并且不会被任何 RTL 片段所匹配,如此 NORI 指令变没有存在的意义。 可以提出假设:对于其余的复杂指令,是否同 NORI 一样不会被访问到? 4.2.3 NAND,NANDI,EQL,EQLI 根据上述假设,需要对编译进行分析来确定这些指令会不会被用到。其中 AND 和 ANDI 执行的是 (操作数 1 & 操作数 2) 运算,而 EQL 和 EQLI 执行的是 ((Rs & Rt/im)|( Rs & Rt/im)) 运算。将四个模板编写好后,通过设计如下 C 程序片段: i = ~(i & j); i = ~(j & 5); i = ~((i & ~j) } (~i & j)); i = ~((j & ~5) } (~j & 5)); 在编译器输出的 RTL 片段和汇编结果中并没有发现 NAND/NANDI,EQL/EQLI 指 令的出现,而是被分解成了 AND,NOR 等简单逻辑运算。很明显在 GCC 的前端分析 树生成过程中,将复杂逻辑运算全部简单化,分解到二元运算。 但是同样为复杂运算的 NOR 却出现在了汇编程序中,通过对 NOR 的匹配来源分析 得知,NOR 在编译中仅被非运算调用,即二元的自身非运算。编译过程中三元逻辑 (操 20
  • 27. 第四章 GCC 移植的详细过程 作数 1 | 操作数 2)同样被分解为或运算和结果的非运算,即使将 NORSI3 模板提前也 不会被访问。 可以得出结论:GCC 编译过程中将所有的复杂逻辑运算全部分解为二元逻辑运算。 既然如此复杂逻辑指令在指令集中就没有意义了,NORI,NAND,NANDI,EQL 和 EQLI 指令可以从机器描述中删除,在指令集中只有通过编写汇编程序才可以调用 到。 4.2.4 验证 逻辑指令实现了 AND/ANDI, OR/ORI, XOR/XORI, NOR, 通过编写 C 程序片段来进 行验证。 int i = 1; int j = 5; int m = 10; i = i & 5; i = ~j | m; i = ~(i ^ j); i = ~5; 在编译器输出的汇编程序中,相关联的部分有: li $2,0x00000001 # 1 sw $2,16($fp) li $2,0x00000005 # 5 sw $2,20($fp) li $2,0x0000000a # 10 sw $2,24($fp) lw $2,16($fp) ANDI $3,$2,0x0005 sw $3,16($fp) lw $3,20($fp) NOR $2,$0,$3 lw $3,24($fp) OR $2,$2,$3 sw $2,16($fp) lw $2,16($fp) lw $3,20($fp) XOR $2,$2,$3 NOR $3,$0,$2 sw $3,16($fp) li $2,-6 # 0xfffffffa sw $2,16($fp) 21
  • 28. 第四章 GCC 移植的详细过程 4.3 其他指令 4.3.1 SW,LW,LB Store Word 汇编格式 SW Rs,Rt,im16 执行功能 [Rt + im16] = Rs; Load Word 汇编格式 LW Rd,Rs,im16 执行功能 Rd = Word[Rs + im16]; Load Byte 汇编格式 LB Rd,Rs,im15 执行功能 Rd = Byte[Rs + im16]; 这三个存取指令在 MIPS 和 MiniSys2 指令集中的功能和名称是一样的,并不需要对 模板进行改变,所以仅需将指令所在的程序找到即可。SW,LW 以及 LB 所在的位置并 非是机器描述文件中,而是在定义 C 程序中,处于寄存器存取的判断函数部分和汇编 程序结尾定义段。 MIPS 指令和 MiniSys2 指令在 SW 与 LW 指令格式不同,MIPS 是将基址和偏移地 址放在一个操作数内用括号分开 (16($fp)),而 MiniSys2 风格则是将基址和偏移地址 放在不同的操作数内 ($fp,16),而这里需要对 MIPS 的指令格式定义进行修改来满足 MiniSys2 指令集的条件。 操作数地址定义在机器定义 C 文件的 print operand address (file, addr) 函数中。在 函数结尾处有两个操作,output addr const (file, offset) 和 fprintf (file, "(%s)", reg names [REGNO (reg)]); 前者输出立即数偏移地址,后者输出寄存器基址。为了满足移植条件, 将两个函数的位置互相调换,并修改寄存器基址的格式,调整后的程序如下: fprintf (file, "%s,", reg_names [REGNO (reg)]); output_addr_const (file, offset); 在汇编程序结尾定义段还有两处 LW 需要修改: if (store_p || !TARGET_ABICALLS || regno != (PIC_OFFSET_TABLE_REGNUM - GP_REG_FIRST)) { fprintf (file, "t%st%s,%s,%ldn", (store_p) ? "LW" : "LW", reg_names[regno], reg_names[REGNO(base_reg_rtx)], gp_offset - base_offset); } 这样寻址方式的表达就调整完成了。在后面的汇编器设计中只需按照指令定义从不 同的操作数取出地址即可。 4.3.2 寄存器的立即数存储 在 ARM 和 X86 的汇编语言中,都有 mov 指令来对通用寄存器进行立即数赋值,在 MIPS 指令集中也有 li 指令执行该操作。而在 MiniSys2 系统中却没有对寄存器赋立即数 值的指令,只是可以用其他例如算数运算指令来间接实现。为了提高系统的效率,最好 22
  • 29. 第四章 GCC 移植的详细过程 在 MiniSys2 指令集中加入立即数存通用寄存器的命令,可以沿用 MIPS 的 li 指令,或 者在 mov 指令内加入存储立即数的功能。 4.3.3 MOV MOV 指令执行的命令是寄存器数据传送,在 MiniSys2 指令集中通过识别字来区分 全字节传送还是字节范围间的数据传送。在 GCC 的编译过程当中不提供寄存器字节片 段传送的功能,所以编译仅会用到 MOV 的第一条寄存器全字节的传送指令。 MiniSys2 的 MOV 指令和 MIPS 的 move 指令执行的功能相似,由于编译过程中不 会用到片段字节传送,仅可以对指令形式进行修改。在 GCC 中 move 指令识别三种寄 存器行为,一是寄存器向寄存器的数据传送,另一种是 0 号寄存器(接地)向通用寄存 器赋 0 值,还有一种是 fp 指针向 sp 指针数据的传送。在 C 程序的寄存器存取判断函数 和汇编结束处理函数部分可以找到三种识别语句,在 MOV 指令结尾需加上 0x0000 来 在 MiniSys2 系统中对指令传送范围进行识别。 4.3.4 BNE,BEQ BNE Rd,Rs,im16; BEQ Rd,Rs,im16; 执行条件跳转功能,指令判断第一,第二操作数是否相等并根据条件向立即数偏移 地址进行跳转。BNE 和 BEQ 在 MIPS 与 MiniSys2 指令集中功能完全一样,定义在机器 描述的 branch zero 模板。模板中通过对 RTL 代码片段的操作数行为来判断输出哪种条 件跳转指令: switch (GET_CODE (operands[0])) { case EQ: return "%*BEQ%?t%z1,%.,%2"; case NE: return "%*BNE%?t%z1,%.,%2"; case GTU: return "%*BNE%?t%z1,%.,%2"; case LEU: return "%*BEQ%?t%z1,%.,%2"; case GEU: return "%*Jt%2"; case LTU: return "%*BNE%?t%.,%.,%2"; } 当执行 EQ(Equal) 动作时输出 BEQ 指令,当执行 NE(Not Equal) 时输出 BNE 指令。 当 RTL 代码需要时可以对反向动作进行匹配,这时在模板中另有定义,当执行 EQ 时 输出 BNE 指令,当执行 NE 时出处 BEQ 指令。 4.3.5 JLI,JI,JR JLI 指令执行的是无条件跳转并 LINK 指令,将当前地址存入寄存器后向立即数地 址进行跳转,用于函数的调用和返回。一般将当前地址存入 31 号寄存器,当跳转返回 的时候从 31 号寄存器取出地址填入 PC。但是当返回时一般需要执行跳转源的下一条地 址,所以 JLI 指令应当存储跳转源的地址 +8;JLI 匹配位于 call internal1 模板。 23
  • 30. 第四章 GCC 移植的详细过程 JI 执行的是向操作数的立即数地址无条件跳转,与 MIPS 中的 J 指令相同,JR 执行 的是想寄存器中的地址无条件跳转,与 MIPS 中的 JR 指令相同。 4.3.6 AL,AR,SL,SR AL,AR 是依照寄存器内容左移/右移,而 SL,SR 是依照立即数内容左移/右移。 在 MIPS 指令集中并没有与 AL,AR 这两条相近的指令,所以对于 AL 和 AR 需要新 增模板。观察 MIPS 中的左移/右移指令模板,第三个操作数可以识别为立即数或寄存 器。而为了让 AL 和 AR 指令从中分离,可以新增同样操作的指令模板,在第三操作数 仅对寄存器 (register operand) 进行识别即可: (define_insn "ashlREGsi3" [(set (match_operand:SI 0 "register_operand" "=d") (ashift:SI (match_operand:SI 1 "register_operand" "d") (match_operand:SI 2 "register_operand" "dI")))] "" "ALt%0,%1,%2" [(set_attr "type" "arith") (set_attr "mode" "SI") (set_attr "length" "1")]) AR 的指令模板相似,仅是执行的运算操作不同。同时需要将 AR 和 AL 的指令模 板放在之前左移/右移的模板前,这样先对 AR/AL 进行识别就可以将两种不同的操作数 分开。 对于 SL 和 SR 指令,将 MIPS 中的移位模板汇编输出进行重命名即可。 4.3.7 比较指令 MiniSys2 中并没有类似于 MIPS 中的 slt 这样的比较指令,可以用来和 BNE,BEQ 等 指令配合进行比较条件跳转。标准的比较条件跳转是 C 语言中的 if(i < j)then 语句,对 于 i < j 的判断使用 slt 指令并对 i>j 和 i<j 两种情况分别向寄存器置 0 或 1,紧接着用 BNE 或 BEQ 指令对条件进行判断并跳转。 缺少这样的比较指令使得 MiniSys2 指令系统不具备编译判断大小语句的能力。应 当加入一条类似 slt 这样的指令,或者在跳转指令中加入对操作数比较然后跳转这样的 一体指令。 24
  • 31. 第五章 编译器综合测试 5.1 实现命令 编译器移植实现的命令如下: ADD Rd, Rs, Rt [Rd = Rs + Rt] SUB Rd, Rs, Rt [Rd = Rs - Rt] ADDI Rd, Rs, im16 [Rd = Rs + im16] SUBI Rd, Rs, im16 [Rd = Rs - im16] INC Rd [Rd = Rd + 1] DEC Rd [Rd = Rd - 1] AND Rd, Rs, Rt [Rd = Rs & Rt] ANDI Rd, Rs, im16 [Rd = Rs & (0x)im16] OR Rd, Rs, Rt [Rd = Rs | Rt] ORI Rd, Rs, im16 [Rd = Rs | (0x)im16] XOR Rd, Rs, Rt [Rd = Rs ∧ Rt] XORI Rd, Rs, im16 [Rd = Rs ∧ im16] NOR Rd, Rs, Rt [Rd = ∼(Rs | Rt)] SW Rs, Rt, im16 [(Rt + im16) = Rs] LW Rd, Rs, im16 [Rd = (Rs + im16)] LB Rd, Rs, im16 [Rd = (Rs + im16)] LI Rd, im16 [Rd = im16] MOV Rd, Rs, 0x0000 [Rd = Rs] BNE Rd, Rs, im16 [PC = (Rd!=Rs) ? NPC + im16 : NPC] BEQ Rd, Rs, im16 [PC = (Rd==Rs) ? NPC + im16 : NPC] JLI Rd, im20 [LINK, PC = Rd] JI im28 [PC = im28] JR Rd [PC = Rd] AL Rd, Rs, Rt [Rd = (Rs<<Rt)] AR Rd, Rs, Rt [Rd = (Rs<<Rt)] SL Rd, Rs, im16 [Rd = (Rs<<im16)] SR Rd, Rs, im16 [Rd = (Rs<<im16)] SLT Rd, Rs, Rt [Rd = (Rs < Rt) ? 1 : 0 ] 25
  • 32. 第五章 编译器综合测试 这些命令是编译产生汇编语言的基本命令。但是指令集只实现了单整数指令 (SIMODE),并没有对浮点数等提出命令定义。对于 MiniSys2 指令集,除了上述基本命 令其余的大都不会被编译器产生。编译器前端支持所有 GCC 的标准语言。 5.2 综合验证 这里编写 C++ 测试代码,测试赋值,数学运算,逻辑运算的功能,同时对常用的 循环,判断,符合运算等作出编译验证: 1 i n t i = 3; 2 i n t j = 2; 3 i n t m = 1; 4 i f ( i > j ) 5 i ++; 6 e l s e 7 i = j − 7; 8 while ( i > 9 ) 9 { 10 i −−; 11 j = i ˆ (˜ j & m) ; 12 i = j << 3; 13 } 14 i = j >> i ; 使用 cc1 编译器对 hello.cpp 文件进行普通编译,由于没有配置汇编器只生成汇编文 件 hello.s: 1 main : 2 SUB $sp , $sp ,40 3 SW $31 , $sp ,36 4 SW $fp , $sp ,32 5 MOV $fp , $sp ,0 x0000 6 JLI $31 , main 7 LI $2 ,0 x00000003 # 对寄存器赋值整数 3 并存入存储器 8 SW $2 , $fp ,16 9 LI $2 ,0 x00000002 # 对寄存器赋值整数 2 并存入存储器 10 SW $2 , $fp ,20 11 LI $2 ,0 x00000001 # 对寄存器赋值整数 1 并存入存储器 12 SW $2 , $fp ,24 13 LW $2 , $fp ,16 # 取出 i 26
  • 33. 第五章 编译器综合测试 14 LW $3 , $fp ,20 # 取出 j 15 SLT $2 , $3 , $2 # 比较两数大小并置数 16 BEQ $2 , $0 , $L2 # 若置数等于零则跳转L2 17 LW $3 , $fp ,16 # 取出 i 18 MOV $2 , $3 ,0 x0000 19 INC $2 # 自增 20 MOV $3 , $2 ,0 x0000 21 SW $3 , $fp ,16 # 将结果存入存储器 22 J I $L3 # 跳转至L3 23 $L2 : 24 LW $2 , $fp ,20 # 取出 j 25 SUBI $3 , $2 ,0 x f f f 9 # 执行j −7 26 SW $3 , $fp ,16 # 将结果存入存储器 27 $L3 : 28 . s e t n o r e o r d e r 29 NOP 30 . s e t r e o r d e r 31 $L4 : 32 LW $2 , $fp ,16 33 SLT $3 , $2 ,10 # 对寄存器和立即数进行比较并置数 34 BEQ $3 , $0 , $L6 # 若置数等于零则跳转L6 35 J I $L5 # 无条件跳转L5 36 $L6 : 37 LW $3 , $fp ,16 # 取出 i 38 MOV $2 , $3 ,0 x0000 39 DEC $2 # 自减 40 MOV $3 , $2 ,0 x0000 41 SW $3 , $fp ,16 # 存入存储器 42 LW $3 , $fp ,20 # 取出 j 43 NOR $2 , $0 , $3 # 取非 44 LW $3 , $fp ,24 # 取出m 45 AND $2 , $2 , $3 # 对二者进行逻辑和运算 46 LW $3 , $fp ,16 # 取出 i 47 XOR $2 , $3 , $2 # 与上述结果进行异或运算 48 SW $2 , $fp ,20 # 存入结果 49 LW $2 , $fp ,20 # 取出 j 50 SL $3 , $2 ,3 # 左移三位 51 SW $3 , $fp ,16 # 存入 i 27
  • 34. 第五章 编译器综合测试 52 J I $L4 # 无条件跳转L4 53 $L5 : 54 LW $2 , $fp ,20 # 取出 j 55 LW $3 , $fp ,16 # 取出 i 56 AR $2 , $2 , $3 # 右移(寄存器指定)位 57 SW $2 , $fp ,16 # 存入 i 58 LW $2 , $fp ,16 59 J I $L1 # 向结尾跳转 可以看出所有的基本指令都被移植成功,算数和逻辑指令执行准确,赋值和寄存器 存取指令正常。其中 LI 和 SLT 指令是新增加的,并不存在于初始的 MiniSys2 指令集, 同时两个指令也可以集成到其他的基本指令中。当然,还要在汇编器上做一些调整才 能正确的在 MiniSys2 计算机系统上运行成功。 当然由于没有开启 GCC 优化功能,所以汇编程序按照源程序直接转换,并没有对 前段的语法树或后端的 RTL 片段进行优化。若是开启 GCC 优化功能生成的程序将更加 简短高效。 28
  • 35. 第六章 GCC-MiniSys2 交叉编译器 6.1 编译环境与编译器设置 在不同平台上使用目标为 MiniSys2 的 GCC 编译器时需要先对 GCC 编译。使用 shell 进入 gcc 目录后对编译环境进行配置: .configure -host=i386-intel-linux -target=minisys2little-na-sstrix 命令行中 configure 是配置命令,-host 后面是编译平台的环境,分别为 i386(体系结 构)-intel(附加信息)-linux(操作系统)。而 -target 就是目标平台的环境,同样定义为体系 机构 -附加信息 -操作系统。从 -host 平台向 -target 平台进行的就是交叉编译,得到的程 序可以在目标平台上运行。 环境配置好后对编译目录进行清理 (make clean),之后就可以直接编译了 (make)。 最后得到的 cc1 就是 -i386-linux 平台上执行的对 minisys2little-sstrix 平台的交叉编译器。 编译好的 GCC-MiniSys2 交叉编译器可以在任何 Linux 发行版操作系统上使用。为 了程序正常的运行建议在 Linux 内核为 2.6.32-22 以上的版本中使用。 6.2 编译器的使用 6.2.1 编译器命令行界面 GCC-MiniSys2 交叉编译的默认工作环境是 Linux 的 Shell 命令行界面。 简单编译结果如下, 默认对环节时间成本进行统计: user@user−computer : ˜ / s i m p l e s c a l a r / gcc −2.6.3 $ . / cc1 h e l l o . cpp main time in parse : 0.010000 time in i n t e g r a t i o n : 0.000000 time in jump : 0.000000 time in cse : 0.000000 time in loop : 0.000000 time in cse2 : 0.000000 time in flow : 0.000000 time in combine : 0.000000 time in sched : 0.000000 29
  • 36. 第六章 GCC-MiniSys2 交叉编译器 time in l o c a l −a l l o c : 0.000000 time in global −a l l o c : 0.000000 time in sched2 : 0.000000 time in dbranch : 0.000000 time in shorten −branch : 0.000000 time in stack −reg : 0.000000 time in f i n a l : 0.000000 time in v a r c o n s t : 0.000000 time in symout : 0.000000 time in dump : 0.000000 6.2.2 编译程序 这是一个 MiniSys2 编译器的简单编译命令例子: .cc1 hello.cpp 这句命令对 hello 的 c++ 程序源代码进行编译。默认输出文件名为 hello.s 的汇编程 序代码。通常对于 GCC 前端支持的编程语言都可以进行编译。 6.2.3 编译调试 .cc1 -g hello.cpp -g 命令是 gcc 编译调试命令,编译时对汇编程序加入调试命令,例如: .loc 1 4 LI $2,0x00000002 # 2 SW $2,$fp,20 .loc 1 6 LW $3,$fp,16 INC $2 MOV $3,$2,0x0000 SW $3,$fp,16 6.2.4 RTL 片段输出 .cc1 -dr hello.cpp 编译器将生成 hello.cpp.rtl,hello.s;前者为 RTL 代码 DUMP 输出,后者为汇编程 序。 30
  • 37. 第六章 GCC-MiniSys2 交叉编译器 6.2.5 GCC 编译器的优化 GCC 编译器支持四级优化,分别由 0 到 3 代表的从无优化到最大优化。 .cc1 hello.c .cc1 -O1 hello.c 两句命令分别为无优化和等级 1 优化。 针对编译器的优化功能,这里对一段 C 程序进行编译,在无优化和等级 1 优化下对 编译的结果进行对比。 int main() { int i = 1; int j = 2; i++; i = ~(i ^ j); if( i < j ) { i = i + (j & 7); } return i; } 分别对程序进行两种优化,由于程序源代码不大,所以消耗时间忽略不计。当然如 果对很长的程序进行编译可能优化功能要消耗更长的时间。下面是对无优化和 O1 优化 程序成本的对比: 无优化 O1 优化 汇编程序大小 61 行 33 行 汇编字符统计 915 502 主函数统计 640 221 变量使用 8 0 寄存器使用 2 1 可以看出优化后汇编程序效率大大提高,由于汇编程序过于冗长就不放在此处,但 是根据源代码对比可以发现,GCC 无优化时的编译不对程序的进程进行预测,仅将立 即数数学运算进行合并;而 O1 优化同时对程序的进程进行预测并去除不会用到的分 支,由于作为测试的 C 程序执行的是常数的数学和逻辑运算,尽管存在寄存器数据和 立即数,编译器对程序的常数运算步骤进行合并,并预测判断和循环分支,直接给出运 31
  • 38. 第六章 GCC-MiniSys2 交叉编译器 行结果,所以优化后的汇编程序除去开头和结尾段仅有将结果直接输入寄存器和 PC 命 令两句。 6.2.6 汇编程序注释 .cc1 -dumpversion hello.cpp 这个命令将在生成的汇编程序内对每一句的匹配来源进行注释,例如对于立即数加 法运算的匹配,指出了使用哪一个匹配模板进行的输出: ADDI $3,$2,0x0005 # 22 addisi3/2 同时生产文件 hello.cpp.rtl,hello.cpp.cse 以及 hello.s,分别为 RTL 程序和带注释的 汇编语言程序。 6.2.7 程序成本统计 .cc1 -dumpmachine hello.cpp -dumpmachine 命令将产生很多文件,其中 hello.cpp.flow 和 hello.cpp.greg 文件内对 寄存器的使用次数和序号状态进行了统计。 6.3 IDE 集成/高级编译器界面 GCC-MiniSys2 交叉编译器可以被多种 IDE(Integrated development environment) 调用 并进行程序的编译。 图 6.1 QT4 结合 GCC-MiniSys2 交叉编译器 这里以 Linux 下的 QT4/QT creator 为例进行设置: 32
  • 39. 第六章 GCC-MiniSys2 交叉编译器 ◦ 在工程选项里使用 (Remove Step) 清空所有的 Build Step 和 Build Step. ◦ 在 Build Step 里新加入一个 Custom Process Step. ◦ 编译器路径选择项目目录里的 cc1 交叉编译器. ◦ 附加命令里填写 main.cpp 图 6.2 QT4 的编译器集成设置 这样就可以使用 QT4 的 IDE 进行程序设计和编译了。 33
  • 40. 第七章 项目总结 7.1 研究成果与说明 ⋄ 编译器移植源代码 ◦ Minisys2.md 机器描述/匹配模板 130,200bytes 4466 行 ◦ Minisys2.h 机器描述/宏文件 132,473bytes 3640 行 ◦ Minisys2.c 机器描述/宏文件 141,728bytes 5370 行 ⋄ 编译器可执行程序 ◦ cc1 7.2 总结 在 GCC 编译器的移植工作开始时,对于项目能做到什么程度并没有确切的认识, 尽管有移植成功例子和文献可以作为参考,但是整体内容并不明晰,并且需要大量的 准备工作和背景知识。尤其是机器描述中模板的 RTL 定义,由于没有准确的或者正式 的说明文档来介绍匹配模板的编写方法,以及 RTL 的语义定义等,所以对指令的移植 可以说是在摸索和猜测中进行。对于模板中每一部分,每一个参数的含义都要经过多 次试验改动才能确定确切的功能和意义,进而才可以进行修改,添加。 当对于模板内容和含义有了初步的理解之后,指令的定义工作才稍微有一些头绪。 当然,更为复杂的工作还在后面,例如算数减法的定义,有通过加法运算进行减法的操 作,也有直接调用减法运算的操作,而最终的匹配定义则是靠多次的猜测试验才能确 定。又如逻辑运算指令集,当所有的逻辑匹配模板定义完成后才发现有一部分是不会 用到的,为了验证这种猜测对数十种逻辑运算组合进行了试验,最后才能得出不使用 多余指令的结论。 另外,对于相关知识的了解是十分重要的。对于 GCC 编译器,各种指令集,乃至 Linux 系统都要十分熟悉才不会在工作中出现不必要的问题,这也是为什么有必要在移 植前对 GCC 的说明文档进行系统的学习。 尽管移植工作十分复杂,但是相较于开发完整的编译器,GCC 移植还是比较节省 成本和工作量需求的。通过 GCC 移植可以短期内开发出一个支持多种前端语言的高效 程序编译器,继而通过交叉编译直接生成可以在嵌入式平台可以直接运行的程序,又 避免了汇编程序开发的周期长,效率低等问题。 GCC 编译器对 MiniSys2 系统的移植也可以证明其良好的可移植性。GCC 对于其他 34
  • 41. 第七章 项目总结 的处理器架构也是可以完整移植的,当然主流的处理器几乎都可以被 GCC 原生支持, 新的架构尤其是嵌入式系统架构也可以根据移植原理短期内开发出自己的高级语言编 译器。 同时虽然 GCC 的移植是根据指令集的设定来进行,但是更多的需要在指令集设计 初始就对将两者合二为一进行考虑。因为 GCC 尽管是前后端独立,但是其分析树和 RTL 代码片段确是固定的,这也就导致了指令并不是完全会被 GCC 用到,也就说明了 指令集的设定应该根据 GCC 的功能来进行,才能将编译器和指令完整结合,更深入的 提高系统的效率。 7.3 展望 本文说明了 GCC 移植的理论并给出了 GCC 移植的方法。通过对关键文件的修改和 定义实现了 GCC 编译器对 MiniSys2 架构指令系统的兼容,并且测试了移植后的编译器 对程序的编译能力与汇编代码生成的准确程度。但是由于时间与能力所限,仅对一般 程序可以用到的指令进行了单整数 (SI mode) 操作移植定义,在测试时也只对普通的加 减法运算和简单逻辑运算进行验证,并没有涉及复杂运算,64 位运算等。 若是可以进一步的就 GCC 的后端移植进行改进和优化,例如根据硬件平台修改汇 编输出和 RTL 片段生成方式,并基于各种程序的编译方案将可应用的复杂指令也进行 修改移植,则 GCC 编译器本身的能力将会完全的发挥,MiniSys2 平台的应用效率也会 充分的得到利用。 35
  • 42. 参考文献 1 吴克寿, 任小西, 李仁发, et al. GCC 到 Nios 系统的移植研究与实现 [J]. 湖南大学学报: 自然科学 版, 2007, 34(008):70--73. 2 Free Software Foundation. GCC, the GNU Compiler Collection[EB/OL]. 2010. http://gcc.gnu. org/. 3 Stallman R. GNU compiler collection internals[J]. Free Software Foundation, 2002. 4 杨全胜, 李正兴, 成炼, 等. Minisys SoC 结构与设计 [DB/OL]. 2009. 5 Quansheng Y, Zhengxing L. Minisys 2 Instructions Set Quick Reference[EB/OL]. 2009. 6 MIPs Technologies. MIPS32® Architecture For Programmers Volume II: The MIPS32® Instruction Set[EB/OL]. 2009. 7 蔡杰. GCC 编译系统结构分析与后端移植实践 [D]:[Master's Thesis].[S.l.]: 浙江大学, 2004. 8 林秉毅, 劉興傑, 陳立杰, 等. 追蹤 GCC 核心原始碼與移植相關之研究 [DB/OL]. 2005. 9 Griffith A. GCC: The Complete Reference[M].[S.l.]: McGraw-Hill/Osborne, 2002. 10 冯钢, 郑扣根. 基于 GCC 的交叉编译器研究与开发 [J]. 计算机工程与设计, 2004, 25(011):1880-- 1883. 11 林丹. 解析 GCC 的移植机制 [C/OL]. 2002. 12 Nilsson H. Porting GCC for dunces[J]. Master Thesis, 2000, 5:43--54. 13 Parthey J. Porting the GCC-Backend to a VLIW-Architecture[J]. Chemnitz University of Technology, 2004. 14 郭学鹏, 赵克佳. 跨文件编译模式与基于 GCC 的实现 [J]. 计算机工程与科学, 2007, 29(004):111-- 115. 36
  • 43. 附录 A 研究与开发环境 开发平台和软件的说明来自 wikipedia.org A.1 Ubuntu Ubuntu 是一个以桌面应用为主的 GNU/Linux 操作系统,Ubuntu 基于 Debian 发行版和 GNOME 桌面环境,与 Debian 的不同在于它每 6 个月会发布一个新版本。Ubuntu 的目标在于为一般用户提 供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。 GCC 的移植工作主要在 Ubuntu 下进行,利用其自身的 GCC 编译器对 GCC 的交叉编译版本进 行编译。 A.2 GEDIT gedit 是一个 GNOME 桌面环境下兼容 UTF-8 的文本编辑器。它简单易用,有良好的语法高亮, 对中文支持很好,支持包括 gb2313、gbk 在内的多种字符编码。gedit 是一个自由软件。 gedit 包含语法高亮和标签编辑多个文件的功能。利用 GNOME VFS 库,它还可以编辑远程文 件。它支持完整的恢复和重做系统以及查找和替换。 它还支持包括多语言拼写检查和一个灵活的插件系统,可以动态地添加新特性。例如 snippets 和外部程序的整合。 在移植工作中使用 gedit 对 target.md,target.h 和 target.c 文件进行编辑。 A.3 Notepad++ Notepad++ 是一套自由软件的纯文本编辑器。该软件以 GPL 发布,有完整的中文化接口及支持 多国语言撰写的功能(采用万国码 UTF-8 技术)。它的功能比 Windows 中的 Notepad(记事簿)强 大,除了可以用来制作一般的纯文字说明文件,也十分适合当作撰写电脑程序的编辑器。 支持的程序设计语言如下:Java、C / C++、C#、HTML、PHP、XML、JavaScript、makefile、 ASCII 艺术、doxygen、ASP、VB / VBScript、Unix Shell Script、BAT (Batch file)、SQL、Objective- C、CSS、PASCAL、Perl、Python、Lua、TCL、Assembler、Ruby、Lisp、Scheme、Diff、Smalltalk、 Postscript and VHDL。 由于 Notepad++ 支持 lisp 语言,在 windows 下编辑文档时使用 notepad++ 作为主要的编辑器。 可以对机器描述文件准确的解析并高亮和折叠。 37
  • 44. 附录 A 研究与开发环境 A.4 LATEX LATEX 是一种基于 TeX 的排版系统,由美国计算机学家莱斯利 · 兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能。 在论文的撰写过程中实际是使用的 XeLaTex,便可以良好的支持多语种文本编辑和生成。 A.4.1 seuthesis seuthesis 是东南大学学位论文的 LaTex 模板,目前版本号为 seuthesis 2.0.0。根据模板设置只需 对定义项进行填写并使用 Tex 语法编写文章,即可生成符合规范格式的学位论文。这个系统同样适 用于生成从简单的信件到完整书籍的所有其他种类的文档。 38