在前面的文章中已经介绍过,lcc中跟硬件平台相关的配置由src中*.md配置,本文以x86为例,详解这一部分的工作机制。
熟悉汇编的同学都知道32位x86机器有八个通用寄存器:
eax ebx ecx edx esi edi esp ebp
而ebp和esp两个寄存器是有固定作用的,其保存的帧指针和栈指针是组成栈帧的基本组成部分,所以这两个寄存器不参与分配。
所以参与分配的通用寄存器只有六个,lcc用枚举将这两个寄存器编号:
enum { EAX=0, ECX=1, EDX=2, EBX=3, ESI=6, EDI=7 };
在x86.md的最下面是一个Interface结构体的填充,关于Interface结构体中字段的详细作用,请看这里。
其代码如下:
Interface x86IR = {
1, 1, 0, /* char */
2, 2, 0, /* short */
4, 4, 0, /* int */
4, 4, 0, /* long */
4, 4, 0, /* long long */
4, 4, 1, /* float */
8, 4, 1, /* double */
8, 4, 1, /* long double */
4, 4, 0, /* T * */
0, 1, 0, /* struct */
1, /* little_endian */
0, /* mulops_calls */
0, /* wants_callb */
1, /* wants_argb */
0, /* left_to_right */
0, /* wants_dag */
0, /* unsigned_char */
address,
blockbeg,
blockend,
defaddress,
defconst,
defstring,
defsymbol,
emit,
export,
function,
gen,
global,
import,
local,
progbeg,
progend,
segment,
space,
0, 0, 0, 0, 0, 0, 0,
{1, rmap,
blkfetch, blkstore, blkloop,
_label,
_rule,
_nts,
_kids,
_string,
_templates,
_isinstruction,
_ntname,
emit2,
doarg,
target,
clobber,
}
};
这个结构体填充之后,一个完整的编译器就可以工作了,前端就可以通过通过函数指针驱动后端生成代码了。
这里要插入一个知识点Iburg的介绍,lburg是一个代码生成器的生成器,以lburg规范为语法,类似yacc和lex可以生成词法分析器和语法分析器一样。
lburg接收紧缩规范并产生一个用C语言编写的树分析程序,该程序为目标机器选择指令。
lburg规范的核心是树文法,与常规的文法类似,树文法也是一个规则列表,每个规则的左边是一个非终结符号,右边是终结符号和非终结符号组成的模式。
具体EBNF表示如下(term表终结符号,nonterm表示非终结符号):
grammar:
'%{' configration '%}' { dcl } %% {rule} [%% C code]
dcl:
%start nonterm
%term { term = integer }
rule:
nonterm: tree template [ C expression ]
tree:
term[ '(' tree [, tree] ')']
nonterm
template:
" { any charactor except double quote} "
lburg以行为单位,%{ %[ %%必须单独一行,configuration是C语言代码,每个dcl和rule必须出现在一行上。
lburg翻译程序由lburg文件夹编译生成,在一级目录下执行make lburg即可,makefile中相关代码如下:
lburg: $Blburg$E
$Blburg$E: $Blburg$O $Bgram$O; $(LD) $(LDFLAGS) -o $@ $Blburg$O $Bgram$O
$Blburg$O $Bgram$O: lburg/lburg.h
$Blburg$O: lburg/lburg.c; $(CC) $(CFLAGS) -c -Ilburg -o $@ lburg/lburg.c
$Bgram$O: lburg/gram.c; $(CC) $(CFLAGS) -c -Ilburg -o $@ lburg/gram.c
由于这一些列文章主要是研究编译器本身的,对于lburg程序就不在继续深入分享,有兴趣的同学可以自己深入理解。
有了lburg,就可以将*.md翻译.c文件,makefile中相关部分代码如下:
$Bdagcheck.c: $Blburg$E src/dagcheck.md; $Blburg src/dagcheck.md $@
$Balpha.c: $Blburg$E src/alpha.md; $Blburg src/alpha.md $@
$Bmips.c: $Blburg$E src/mips.md; $Blburg src/mips.md $@
$Bsparc.c: $Blburg$E src/sparc.md; $Blburg src/sparc.md $@
$Bx86.c: $Blburg$E src/x86.md; $Blburg src/x86.md $@
$Bx86linux.c: $Blburg$E src/x86linux.md; $Blburg src/x86linux.md $@
可以看到x86.md和x86linux.md被翻译成x86.c和x86linux.c,这就是本篇blog重点分析的部分。
下面就是生成的x86linux.c,这个文件比较长,原因在于计算指令耗损的_label函数占用了较大的篇幅,为了便于理解,下面的只挑出来一些重要的部分进行源码分析。