山东大学编译原理实验3(新)

题目描述

本实验要做的是实现一个可以把四元式翻译成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,-,
  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
山东大学编译原理实验C是指山东大学计算机科学与技术专业的编译原理实践课程中,学生需要使用C语言进行编写的实验项目。编译原理是计算机科学中的重要基础课程,主要研究如何将源代码转换为可执行的程序。在这门课程中,学生将学习如何设计和实现编译器,了解编译过程中的词法分析、语法分析、语义分析、中间代码生成等关键技术。 在山东大学编译原理实验C中,学生将通过编写C语言程序,实现这些编译器的各个模块。通过实验,学生能够深入理解编译器的工作原理和实现过程,加深对编译原理的理解。实验内容可能包括编写词法分析器,实现对源代码的词法分析和生成记号流;编写语法分析器,实现对记号流的语法分析和生成抽象语法树;编写语义分析器,对生成的抽象语法树进行语义检查和类型推导等。 在实验过程中,学生需要掌握C语言的基本语法和相关的数据结构,熟悉编程环境和工具,如gcc编译器、调试工具等。同时,学生需要学习和理解编译原理中的相关理论知识,如正则表达式、文法、自动机等,以便能够正确地进行实验设计和实现。 通过山东大学编译原理实验C,学生能够加深对编译原理的理解,并提升编程和软件设计的能力。这门实践课程为学生今后从事编译器设计和开发、编程语言实现等相关领域的研究提供了坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕噜咕噜咕噜128

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值