题目描述
本实验要做的是实现一个可以把四元式翻译成x86目标代码的代码生成器。代码生成器求解待用信息、活跃信息和寄存器描述符地址描述符等,根据它们分配寄存器,并逐条把四元式翻译成汇编代码,注意代码生成器需要在一个基本块范围内考虑如何充分利用寄存器,而全局代码的生成则是简单地将各个基本块代码串联。
代码生成器的输入
- 符号表
- 临时变量个数
- 四元式
符号表是通过分析声明语句构建的,我们假定本实验中将所有的形参都视为基本块中的局部变量。在后续中间代码生成中,又加入了临时变量信息,本实验中输入的临时变量信息是临时变量的个数。本实验中没有类型转换,不会出现整型和实型相加这类操作,且整型和实型的运算在目标代码的指令生成中亦不做区分。同时假设代码中不存在任何语义错误。
目标程序的形式
本实验我们选择汇编代码作为目标程序。汇编也有CISC和RISC之分,具体来说,我们输出以Intel和AMD为代表的X86架构的CISC指令集汇编。采用CISC指令集有很多特殊限制,如乘除指令需要固定使用EAX和EDX寄存器等,但是本实验我们不考虑寄存器的特殊限制,在设计代码生成算法时,我们设计一个通用算法,此时的寄存器表示为R0, R1,…的形式。且Mul和Div指令不考虑其使用寄存器的特殊性,按普通指令处理。
代码书写约定
我们把四元式编号前面加个?符号构成跳转指令的标号。更精确地,由于转移语句只可能会出现在基本块的最后一条语句,因此翻译到基本块的最后一条语句时,如果是转移语句,则在转移的目标语句前生成标号。
实际上,由于变量没有在静态数据区声明,而是在活动记录动态开辟空间,因此不能使用名字进行访问。在非嵌套过程语言的栈式实现中,我们需要通过EBP寄存器获得变量地址进行访问。假设变量在符号表中偏移量为offset,对于形参,应用[EBP+offset+8]来访问该变量,对于局部变量,假设保存寄存器个数为n,则使用[EBP-offset-4(n+1)]访问该变量。由于本实验中将所有的形参都视为了局部变量,所以对访问作出了一定程度的简化处理,即只需要利用[EBP-offset]访问变量即可。假设我们有两个寄存器,z为局部变量,在符号表中offset为8,用pushad保护z寄存器,则对应代码为mov [ebp-8], eax。
需要特殊处理的是临时变量。有的临时变量计算出来后接着被使用,且在后续代码中不活跃,这种临时变量无需存入栈帧,也有的临时变量计算出来后要等后面使用,如果寄存器数量不足,可能就需要存入栈帧,因此需要为其分配一个地址单元。在中间代码生成中,已经在符号表中保存了一个过程的总体偏移量offset,它就是一个过程中所有局部变量占用空间的总和。这个偏移量加上保存的寄存器空间,就是临时变量保存的起始空间,它等于运行时的ESP,但现在生成代码是编译时,需要记录下每个临时变量的地址,供后续访问使用。即当遇到需要保存的局部变量时,先从符号表查询到这个符号,如果该变量还未分配空间,则将临时变量起始空间作为当前临时变量地址并存入符号表,然后生成代码开辟这个空间再将地址偏移量返回。
而本实验对分配或获得一个临时变量的存储地址亦作出了一定程度的简化,在需要存入临时变量时按序依次将临时变量在局部变量之后的栈帧中开辟空间即可,可以像局部变量一样通过[EBP-offset]访问这个地址。
对寄存器的数量和名字我们也作一定程度的简化处理,数量我们约定为三个,名字约定为R0、R1和R2。分配寄存器也作简化,先分配R0,再分配R1,再分配R2,顺序分配。
待用信息和活跃信息的生成
对于一个四元式的每个变量,都有一个待用信息,它包括两方面的信息:
- 后续引用点(USE):在当前基本块中,该变量后续被引用的四元式编号。
- 活跃信息(LIVE):后续该变量是否活跃。
因此,待用信息用一个二元组(USE, LIVE)去表示,没有引用点的USE用 - 表示。
待用信息求解的结果是为每个四元式的每个变量求一个二元组(USE, LIVE),这些信息被附加在中间代码上,即为中间代码的每个四元式的每个变量增加这样两个属性。在求解活跃信息时,由于所有变量都被登记在符号表中,因此只需在符号表中为每个变量增加两个域,即USE和LIVE。初始时将符号表中个变量的USE设置为非待用,LIVE则根据基本块出口是否活跃设置。本实验中,把基本块中所有临时变量均看作基本块出口之后的非活跃变量,而把所有非临时变量看作基本块出口之后的活跃变量。
在基本块的出口处,活跃变量存入主存中时依照字典序存入。
寄存器和地址描述符
每个可用的寄存器都有一个寄存器描述符Rval,它是一个集合,用来记录哪些变量的值存放在此寄存器中。你可以用Rval(R0)表示寄存器R0中保存的变量,基本块级别的代码生成,初始可以将所有寄存器描述符置空。
而每个变量都有一个地址描述符Aval,它可以被记录在符号表中,也是一个集合,用来记录标记变量的值是存放在寄存器还是内存中。
目标代码的映射
各四元式对应的目标代码(注意在本实验中整型和浮点的运算不作区分):
中间代码 | 目标代码 | 说明 |
---|---|---|
(=,lhs/UINT/UFLOAT,-,dest) | mov R, lhs/UINT/UFLOAT | (1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成目标代码 |
(j,-,-,dest) | jmp ?dest | 无条件转移指令 |
(jnz,lhs,-,dest) | mov R, lhs cmp R, 0 jne ?dest |
如果lhs∈Rval®,则不生成第1条代码 |
(jθ,lhs,rhs,dest) | mov R, lhs cmp R, rhs jθ ?dest |
如果lhs∈Rval®,则不生成第1条代码 |
(+,lhs,rhs,dest) | mov R, lhs add R, rhs |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(-,lhs,rhs,dest) | mov R, lhs sub R, rhs |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(*,lhs,rhs,dest) | mov R, lhs mul R, rhs |
(1) R是新分配给dest的寄存器,不考虑特殊寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(/,lhs,rhs,dest) | mov R, lhs div R, rhs |
(1) R是新分配给dest的寄存器,不考虑特殊寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(==,lhs,rhs,dest) | mov R, lhs cmp R, rhs sete R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(!=,lhs,rhs,dest) | mov R, lhs cmp R, rhs setne R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(<,lhs,rhs,dest) | mov R, lhs cmp R, rhs setl R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(<=,lhs,rhs,dest) | mov R, lhs cmp R, rhs setle R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(>,lhs,rhs,dest) | mov R, lhs cmp R, rhs setg R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(>=,lhs,rhs,dest) | mov R, lhs cmp R, rhs setge R |
(1) R是新分配给dest的寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(&&,lhs,rhs,dest) | mov R, lhs and R, rhs |
(1) R是新分配给dest的寄存器,不考虑特殊寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(||,lhs,rhs,dest) | mov R, lhs or R, rhs |
(1) R是新分配给dest的寄存器,不考虑特殊寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(!,lhs,-,dest) | mov R, lhs not R |
(1) R是新分配给dest的寄存器,不考虑特殊寄存器 (2) 如果lhs∈Rval®,则不生成第1条目标代码 |
(R,-,-,dest) | jmp ?read(dest) | 视作无条件转移指令 |
(W,-,-,dest) | jmp ?write(dest) | 视作无条件转移指令 |
(End,-,-,-) | halt | 程序终止 |
Syntax Error | halt | 程序异常终止 |
样例输入
样例1
3
a 0 null 0
b 0 null 4
c 0 null 8
11
23
0: (R,-,-,TB0)
1: (=,0,-,T0_i)
2: (=,T0_i,-,TB1)
3: (=,0,-,T1_i)
4: (j>=,TB0,T1_i,6)
5: (j,-,-,21)
6: (=,1,-,T2_i)
7: (-,TB0,T2_i,T3_i)
8: (=,T3_i,-,TB0)
9: (=,2,-,T4_i)
10: (/,TB0,T4_i,T5_i)
11: (=,2,-,T6_i)
12: (*,T5_i,T6_i,T7_i)
13: (-,TB0,T7_i,T8_i)
14: (=,T8_i,-,TB2)
15: (=,0,-,T9_i)
16: (j!=,TB2,T9_i,18)
17: (j,-,-,3)
18: (+,TB1,TB0,T10_i)
19: (=,T10_i,-,TB1)
20: (j,-,-,3)
21: (W,-,-,TB1)
22: (End,-,-,-)
样例2
7
a 0 null 0
b 0 null 4
c 0 null 8
d 1 null 12
e 1 null 20
f 1 null 28
j 0 null 36
26
56
0: (=,4.600000,-,T0_d)
1: (=,T0_d,-,TB3)
2: (=,6.400000,-,T1_d)
3: (-,0,T1_d,T2_d)
4: (=,5,-,T3_i)
5: (*,T2_d,T3_i,T4_i)
6: (=,T4_i,-,TB4)
7: (*,TB3,TB4,T5_d)
8: (+,TB3,TB4,T6_d)
9: (<=,T6_d,TB3,T7_i)
10: (/,T5_d,T7_i,T8_i)
11: (!,T8_i,-,T9_i)
12: (=,T9_i,-,TB5)
13: (=,4,-,T10_i)
14: (=,T10_i,-,TB0)
15: (=,5,-,T11_i)
16: (=,T11_i,-,