当前位置:文档之家› 目标代码的生成第8章

目标代码的生成第8章

生符先号前指已令编和译利好用的宏其机他制程来序帮模助块生。成常代用码的,编目译前程不序少大编多译采程用 序采这用种这可种浮代动码的形代式码。形式。
8.1.2 目标代码
本章采用汇编语言代码作为目标代码,但不针对某种特 定的目标机指令系统或汇编语言来生成目标代码,而是假设 有一台计算机,其指令系统等均按某种需要而设定,为教学 目的往往采取这种虚拟机目标代码形式。
2.按真转编写程序
8.2.2 虚拟机的汇编指令
3.按假转编写程序
8.3 从中间代码生成目标代码
8.3.1 从逆波兰表示生成目标代码 8.3.2 从四元式序列生成目标代码
8.3.1 从逆波兰表生成目标代码
从表达式的逆波兰表示生成相应的目标代码的算法可给出如下。 首先设立一个运算分量栈(栈),用来存放暂时不能处理的运算 分量的名(即工作单元地址)以及中间运算结果的名(即存放中间结 果的工作单元地址或累加器AC)。 其具体算法步骤如下。 从左到右扫描所给定的逆波兰表达式中的每个符号: 若扫描到运算分量,则将其下推入运算分量栈。 若扫描到运算符,按该运算符是几目运算,把运算分量栈 中相应个数的栈顶元素取出,生成该运算相应的目标代码,此后 栈上退去相应个数的运算分量,运算结果存放在“AC”中,把“AC” 标志入运算分量栈。 如此继续,直到整个逆波兰表达式处理完毕为止。
该虚拟机是一台地址单累加器的计算机,用“AC”表示该 累加器;设OP为操作码,d为地址,则指令: 0P d
表示 AC OP d=>AC
即累加器AC中的内容与d中的内容进行某种运算,结果送 到累加器AC中;其内存容量足够大;提供了21条符号汇 编指令。
8.2.2 虚拟机的汇编指令
虚拟机的汇编指令如表8.1所示。
各种代码的形式
• 中间代码: 后缀式,三地址代码,四元式 • 符号表中的项:名字,类型,嵌套深度,偏移
量 • 目标代码:绝对机器代码,可重定位代码,汇

代码生成器的输出必须是正确和高质量的 产生最优化代码的问题是不可判定的
目标代码的生成
目标代码生成是编译程序的最后一个工作阶段。它取先行阶段所产 生的源程序的中间语言或优化后的中间语言表示作为输入,产生等价的 目标代码作为输出,如下图8.1所示。
如果对X:=a[10]语句不采用填地址指令,其程序为 100 CLA <10> 101 ADD <<a[0]>> 102 STO M 103 ICA M 104 STO X
其中M为临时工作单元变量,用来存放<a[10]>,第103条指令使用间接取的方式,实现了X: = a[10]。
8.2.2 虚拟机的汇编指令
目标代码生成程序的输入、输出
编译程序的最终输出是目标代码,这在编译程序的代码 生成阶段完成,也可在语义分析阶段生成。一般地,代码生 成部分称为代码生成程序。代码生成程序的功能是为源程序 生成与之等价的目标机器代码。也就是说,其输入是由先行 端产生的源程序的中间表示,输出是目标代码。
假定在代码生成前,编译的先行端已扫描、分析和翻译 源程序,成为足够详细的中间表示,这样,中间语言中名字 的值可以表示为目标机器能够直接操作的量(位、整数、实 数、指针等),还假定必要的类型检查与插入类型转换等已 完成,不仅语法上正确,语义也是正确的,这样代码生成阶 段可以认为它的输入是正确的。
对于目标机器,如果有多个寄存器可供目标程序运行时使用,在编
译时根应据采访用问合内理存的数方来法定分义配每这条些指寄令存的器执。行把代运价算,数则据对存于放以在下寄的存一器中些,操
将作会 有减其少相访应问的内执存行的代次价数: ,从而可以提高执行速度。
分配操中作不码是把寄存器操平作均数分l 配给各个操变作量数使2用,而是从可执用行的代寄价存
② 检查达栈有式无进元行素语。法若没语有义,分则析当。前运算符入栈,然后转回到步骤①检查下一 个栈为,符了然号;后处否转理回则简步检单骤查起①运算,见符否,则的规转优定到先被步级。骤处③若理。当的前简的单比栈表顶达的式大的,前则当后前都运有算符入 特殊符号“#”,即呈下列形式:
③ 检查当前符号和栈顶元素。若当前符号是“)”,而栈顶元素是“(”,则“(”上
8.2.2 虚拟机的汇编指令
1.填地址指令
设有一维数组a: array[m..n]of integer; 其元素有a[m], a[m+1], … , a[i], … , a[n],一共需要n-m+1个存储单元: <a[m]>,<a[m+1]>,…,<a[i]>,…,<a[n]> 一般有存储单元: <a[i]>=<a[m]>+i-m 由于<a[0]>=<a[m]-m,所以有<a[m]>=<a[0]>+m。从而对于一维数组的存储单
器中分出几O个P ,固定分配寄给存几器个简单变量寄使存用器。按照什么标准来l分配呢? 为此引入一个术语:指令寄的存执器行代价,并规内定存访单问元内存一次的代价2为1。
寄存器
寄存器间接地址
2
寄存器
内存间接地址
3
寄存器分配
基于以上原因,分配中尽可能把变量值保留在寄存器中,当一个寄 存器的值不再需要时,该寄存器可以收回留作他用,但这只有对程序做 了全面的数据流分析后才能确定,这种分析要做大量的额外工作不太实 际,在寄存器的分配中常常以基本块为单位进行。
图8.1 目标代码生成过程
目标代码生成程序中的有关问题
对于一个好的代码生成程序,要求其使所生成的目标 代码尽可能地短,并能较充分地发挥目标计算机可用资源的 效率。如充分利用计算机的寄存器或变址器,以节省访问内 存的时间,尽可能使用执行速度较快的指令,存储管理合理, 等等。
熟悉目标机器和它的指令系统是设计一个好的代码生 成程序的先决条件,但在代码生成的一般性讨论中,不能对 目标机器细节描述到足够详细的程度,以便对一个完整的语 言产生好的代码。本章不以某一台具体的计算机做背景,只 是针对一个假想的计算机模型——虚拟机给出生成目标代码 的算法。
下面就以一种虚拟机指令系统来讨论目标代码的生成。
寄存器分配
通常,计算机存储之间不直接打交道,而是通过寄存器。寄存器可 以用来保存中间计算结果,而且运算对象在寄存器中的指令一般比运算 对象在内存的指令短些,执行速度也快些,因此,充分利用寄存器对生 成高质量目标代码尤其重要。寄存器的分配也自然成为目标代码生成中 的主要问题。
运行时的存储管理
通常所见到的计算机都是冯·诺依曼体系结构的,其特征是 变量——存储字,即用变量来仿效存储字,变量名实际上是 存储字的名字。
对于编译程序来说,必须对源程序中出现的变量与常量分 配运行时刻的存储空间,这一工作由先行端的分析和代码生 成程序共同完成。另从符号表的信息可以确定一个名字(标识 符)所代表数据对象在过程数据区中的相对地址。为了存储分 配的正确实现,除了必须考虑标识符的作用域问题外,还必 须考虑字边界对齐问题,即对于字节编址的计算机,必须注 意对于不同类型的量所分配存储区域的起始地址都必须符合 边界要求。
8.3.1 从逆波兰表生成目标代码
例如,对于逆波兰表达式:
所生成的目标代码为
ห้องสมุดไป่ตู้ab*cd+e/-
CLA a MPY b
具体生成目标代码处理过程如表8.2所示ST。O M
CLA c
ADD d
DIV e
SUB M
8.3.1 从逆波兰表生成目标代码
又如,对于逆波兰表达式:
所生成的目标代码为
aQbc*-d/
下推入栈,最后转回步骤②重复处理当前运算符。另外,在形成目标代码时 应注意“AC”有无被别的量占用,并进行相应处理。
元,有公式: <a[i]>=<a[0]>+i 其中<a[0]>称为数组的假头,<a[m]>称为数组的真头。
8.2.2 虚拟机的汇编指令
例如,有赋值语句:
X:=a[10]
设数组a为单块连续存放方式存放,<a[0]>为假头,则<a[10]>=<a[0]>+10。
假设指令编号从100开始,于是汇编指令编写的程序为 100 CLA <10> 101 ADD <<a[0]>> 102 STA l03 103 CLA 104 STO X
在一个基本块内,按照中间语言代码的顺序,逐个基本块产生目标 代码。生成的目标代码应尽可能将运算的结果存放在寄存器中,直到该 寄存器必须用来存放别的变量或基本块结束为止。这样可以减少基本块 访问内存的次数,从而提高运行速度。
如果把寄存器分配给某些变量,则该变量在定值前每引用一次,将 可少访问一次内存,从而可节省执行代价1;如果某变量在基本块中被定 值,且出基本块后还要被引用,则把寄存器固定分配给该变量,可省去 把它保存到内存单元的操作,从而节省执行代价2。
退栈并转#<回简第单一表步达;若式当>#前是“#”,栈项也是“#”,则处理完毕,算法结束。
若不是上述两种情况则转到步骤④。
④ 应根并的据把目与栈标顶“运代运#算”码算视分,符为量然的优后栈性先同质(栈时(数单退)为目,栈0或相和的双栈应运目。算算运运法算符算符如。结)下果从同存。栈时放顶引在取进“一运A个C”或算中两,符个并栈分把(量“栈生A)C成”标相志
SGN a STO M
具体生成目标代码的处理过程如表8.3所CL示A 。b
MPY c
ISU M
DIV d
8.3.1 从逆波兰表生成目标代码
① 从上左面到右首扫先描把简简单单表达表式达中式的改每造个符成号中。间若语扫言描到的运逆算波分兰量形时就式下,推然入栈, 若扫描后到由运逆算波符兰(包表括达括号式和生“成#”目)时标就代转码到②。,实如际此中一也直扫常描把下两去步,合直到整 个表达为式一扫步描,结根束为据止运。算符优先数的大小关系,直接对简单表
相关主题