主要研究GCC指令生成阶段的各个步骤,重点在编译器代码与机器描述之间的接口函数和数据结构。以一小段代码片段为示例,逐步追溯直至汇编代码生成的整个过程。
引言
机器描述文件是形如*.md的文件,其核心部分是两类定义:“define_insn”和“define_expand”。
图1 md文件中define_insn模版示意图
define_insn主要分为以下几个部分。
- 名字,对应图1中的“addqi3_cc”。
- 样式(pattern),对应于图1中的“[(set (reg:CC… … match_dup 2)))]”。规定了RTL指令体中的各种操作以及操作数的位置和操作数必须满足的条件。
- 条件,对应于图1中的“ix86_binary_operator_ok (PLUS, QImode, operands)”,指出此样板有效的前提条件。
- 输出模板(output template),对应于图1中的“add{b}\t{%2, %0 | %0, %2}”。确定与此样式相匹配的RTL指令的汇编输出形式。可分为三种:单汇编模板、多汇编模板和C代码。前两种给出的就是机器的汇编指令,第三种不能直接给出指令,C代码被编译器吸收并且动态产生汇编模板。
- 指令属性,对应于图1中的“[(set attr … … )])”。给出与该样式所匹配的指令的属性。在编译器的编译过程中,编译器主体与机器描述文件相交互,最终生成汇编代码,完成编译器的使命。
下文中将逐步介绍x86架构下(i386.md文件)后端执行的流程,主要分为三个部分:RTL指令的生成及汇编代码的生成;md文件的分析;编译器后端辅助工具的分析。
RTL及汇编代码的生成
相关文件
GCC编译器从源代码转换到汇编代码的过程,主要经历了三个阶段的中间表示:GIMPLE→RTL→ASM。GIMPLE是比较高层次的语法树;RTL代表指令的rtx表达式;ASM是汇编码。
前端调用后端的函数接口主要是expand_*系列函数集,这一系列函数的作用是完成从tree到rtl的转换。这些函数按照功能分类在以下一些文件中被实现:stmt.c, calls.c, expr.c, explow.c, expmed.c, function.c, optabs.c, integrate.c。
- stmt.c中的expand_*函数的作用是将前端的语句级别上的语法树转换为等义的rtl。因此stmt.c中的这些expand_*函数是被前端parser最先调用的。换句话说,这些函数是“GCC后端”的第1功能层(顶层)。它们会调用exp*.c的一些expand_*函数来真正完成对“表达式”的求值(也即表达式的tree→rtx,并返回rtx),以及调用emit-rtl.c中gen_rtx和gen_reg_rtx等功能函数。
- calls.c也是语句级别上的,它将函数调用语句的tree转换为rtl。
- function.c将函数一级的tree转换为rtl。
- expr.c, explow.c, expmed.c完成对表达式的tree到rtx的转换。这些文件中的expand_*函数基本可以归纳为第2功能层。它这里会将一步引用insn_emit.c文件中的gen_*等功能函数以及optabs.c文件中的expand_*函数。
- optabs.c文件中的expand_*函数,作用是将基本的一元操作和二元操作转换为rtx。它属于 expand_*函数集中的第3功能层。它这里接着往下就会引用与平台相关的一些函数模块(从 md 自动生成的一些程序文件,如 insn-output.c、insn-emit.c以及其它 insn-*系列文件)。
常量的生成
rtx包含不同的类型,主要有常量、指令等。指令rtx的生成,需要从md文件中读取信息来实现;而常量的