1.1k likes | 1.2k Views
编 译 原 理. 指导教师 : 杨建国. 二零一零年三月. 第十二章 代码生成. 第一节 代码生成概述. 第二节 一个简单的代码生成程序. 第三节 几种常用的代码生成程序的开发方法. 第四节 全局寄存器分配(图着色法). 第五节 代码生成程序的自动化构造. 知识结构. 第十二章 代码生成. 12.1 代码生成概述. 代码生成 是把经过语法分析或优化后的中间代码转换成 特定目标机的机器语言或汇编语言,这样的转换程序称 为 代码生成程序 衡量目标代码的质量主要从占用 空间 和执行 效率 两个方 面综合考虑.
E N D
编 译 原 理 指导教师:杨建国 二零一零年三月
第十二章 代码生成 • 第一节 代码生成概述 • 第二节 一个简单的代码生成程序 • 第三节 几种常用的代码生成程序的开发方法 • 第四节 全局寄存器分配(图着色法) • 第五节 代码生成程序的自动化构造
第十二章 代码生成 • 12.1 代码生成概述 • 代码生成是把经过语法分析或优化后的中间代码转换成 特定目标机的机器语言或汇编语言,这样的转换程序称 为代码生成程序 • 衡量目标代码的质量主要从占用空间和执行效率两个方 面综合考虑
一.代码生成程序在编译系统中的位置 • 代码生成程序在编译系统中的位置如图12.1所示:
目标代码一般有3种形式: (1)能够立即执行的机器语言代码,所有地址均已定位 (2)待装配的机器语言模块,当需要执行时,由连接装入 程序把它们和某些运行程序连接起来,转换成能执行的机 器语言代码 (3)汇编语言代码,尚需经过汇编程序汇编,转换成可执 行的机器语言代码
二.设计代码生成程序的基本问题 1.代码生成程序的输入: • 代码生成程序的输入由前端所产生的中间表示和符号表 中的信息组成 • 代码生成程序可以利用符号表中的信息来决定中间代码 中的名字所指示的数据对象的运行时地址
2.指令选择: • 所谓指令选择,是指寻找一个合适的目标机指令序列以 实现给定的中间表示 • 例如,对中间代码x:=y+z,其中x,y,z均为静态分配的变 量,可以翻译成下述代码序列: • LDR R0,y /*将y放入寄存器R0*/ • ADD R0,z /*将z与R0相加*/ • STR R0,x /*将R0的值存入x*/ 这是ARM指令, 如果是inter80×86 MOV R0,y ADD R0,z MOV x,R0
生成代码的质量取决于它的执行速度和代码序列的长度生成代码的质量取决于它的执行速度和代码序列的长度 • 例如,如果目标机器有“加1”指令(INC),那么代码 a:=a+1用INC a实现是最有效的,而不是用以下的指令序 列实现: • LDR R0,a • ADD R0,#1 • STR R0,a
指令选择的基本原则: (1)减少产生代码的尺寸 (2)减少目标代码的执行时间 (3)降低目标代码的能耗 这三者在某些情况下有可能会出现冲突,在具体选择 的过程中应做折中考虑
3.寄存器分配: • 寄存器分配的工作是确定在程序的哪个点将哪些变量或 中间量的值放在寄存器中比较有益 • 将经常使用的操作数保存在寄存器中是比较有利的 • 一些目标机可能具有不同类型的寄存器,对寄存器使用 的一致性方面也存在一定的约束
寄存器的使用可以分成: (1)分配阶段:为程序的某一点选择驻留在寄存器中的一 组变量 (2)指派阶段:挑出变量将要驻留的具体寄存器,即寄存 器赋值
寄存器分配原则: (1)生成某变量的目标对象值时,尽量让变量的值或计算 结果保留在寄存器中直到寄存器不够分配为止 (2)当到基本块出口时,将变量的值存放在内存中 (3)在同一基本块内后面不再被引用的变量所占用的寄存 器应迟早释放,以提高寄存器的利用率
例如,考虑图12.2的两个三地址代码序列,它们仅有的区例如,考虑图12.2的两个三地址代码序列,它们仅有的区 别是第2个语句的算符不同,其最短代码序列在图12.3中 给出:
4.指令调度: • 指令调度是指确定程序指令的执行顺序 • 例如,若在MIPS4KC上计算表示式(a+b)+c,可用表 12.1中两个不同的指令序列,它们的主要不同在于指令 顺序和寄存器的赋值:
12.2 一个简单的代码生成程序 一.计算机模型 • 假定一台M计算机具有n+1个通用寄存器为R0,R1,…,Rn, 它们既可作为累加器又可作为变址器 • 如果用op表示运算符,用M表示内存单元,用变量名表示 该变量所在的单元,C表示常量,*表示间址方式存取
j • 指令形式可包含以下四种类型,见表12.2: • 若op是一目运算符,则op Ri,M的意义为:op(M)Ri • 以上指令中的op包括一般计算机上常见的一些运算符
二.待用信息链表法 • 若在一个基本块中,变量A在四元式i中被定值,在i后面 的四元式j中要引用A值,且从i到j之间没有其他对A的定 值点,这时称j是四元式i中对变量A的待用信息或称下次 引用信息,同时也称A是活跃的,若A被多处引用则可构 成待用信息链与活跃信息链 • 为了得到在一个基本块内每个变量的待用信息和活跃信 息,可以从基本块出口的四元式开始由后向前扫描,为 每个变量名建立相应的待用信息链和活跃变量信息链
考虑到处理的方便,假定对基本块中的变量在出口处都是考虑到处理的方便,假定对基本块中的变量在出口处都是 活跃的,而对基本块内的临时变量可分为两种情况处理: ①对于没有经过数据流分析,且中间代码生成的算法中不允 许在基本块外引用的临时变量,则这些临时变量在基本块 出口处都认为是不活跃的 ②如果中间代码生成时的算法允许某些临时变量在基本块外 引用时,则假定这些临时变量被认为是活跃的
在变量的符号表的记录项中设定了待用信息和活跃信息的在变量的符号表的记录项中设定了待用信息和活跃信息的 栏目,其算法步骤如下: (1)对各基本块的符号表中的“待用信息”栏置“非待用”, 对“活跃信息”栏,按在基本块出中处是否为活跃而置成“活 跃”或“非活跃”,假定外部变量都是活跃的,临时变量都是 非活跃的
(2)从后向前依次处理每个四元式i : A:=B op C,在符号 表中依次执行下述步骤: ①A的待用信息和活跃信息附加到i上 ②把A置成非待用F和非活跃F ③B和C的待用信息和活跃信息附加到i上 ④把B和C待用信息置为i,活跃信息置成活跃L
例如,四元式如下: (A,B,C,D是变量,T,U,V是中间变量) (1)T:=A-B (2)U:=A-C (3)V:=T+U (4)D:=V+U
F (2) (1) L L L F (1) L L F (2) L L F F L F F (3) F F L F (4)D:=V+U (3)V:=T+U (2)U:=A-C (1)T:=A-B (4) L F F (3) F L F F F (4) L F F (4)D:=V+U (3)V:=T+U (2)U:=A-C (1)T:=A-B (4)D:=V+U (3)V:=T+U (2)U:=A-C (1)T:=A-B (4)D:=V+U (3)V:=T+U (2)U:=A-C (1)T:=A-B 表12.4 待用信息链和活跃信息链 (4)D:=V+U (3)V:=T+U (2)U:=A-C (1)T:=A-B (4)DFL:=VFF+UFF (3)V(4)L:=TFF+U(4)L (2)U(3)L:=AFL-CFL (1)T(3)L:=A(2)L-BFL
(4)(3)(2)(1) DVUVTUUACTAB ④ ③ ② ① 变量 待用信息 活跃信息 初值 待用信息链 初值 活跃信息链 A F (2) (1) L L L B F (1) L L C F (2) L L D F F L F T F (3) F F L F U (4) L F F (3) F L F V F F (4) L F F ④ ③ ② ① ④ ③ ② ① • 表12.4中“待用信息链”与“活跃信息链”的每列,从左至右 为每当从后向前扫描一个四元式时相应变量的信息变化情 况,空白处为没变化
三.代码生成算法 • 寄存器和内存地址的描述: • RVALUE[Ri]={A,C} 表示Ri的现行值是变量A,C的值 • AVALUE[A]={A} 表示A的值在内存中 • AVALUE[A]={Ri,A} 表示A的值在Ri中又在内存中 • AVALUE[A]={Ri} 表示变量A的值在寄存器Ri中
寄存器分配和代码生成的具体算法: • 设RETREG是一个函数过程,它的参数是一个形如i: A:=B op C的四元式,每次调用GETREG(i: A:=B op C)则返回 一个寄存器R,用以存放A的结果值 • 对如何给出寄存器R,要用到四元式i上的待用信息,以使 寄存器分配合理,对每个四元式的代码生成都要调用函数 GETREG
GETREG分配寄存器的算法为: (1)如果B的现行值在某寄存器Ri中,且该寄存器只包含B 的值,或者B与A是同一标识符,或B在该四元式后不会再被 引用,则可选择Ri为所需的寄存器R,并转(4)
(2)如果有尚未分配的寄存器,则从中选用一个Ri为所需的(2)如果有尚未分配的寄存器,则从中选用一个Ri为所需的 寄存器R,并转(4)
(3)从已分配的寄存器中选取一个Ri作为所需寄存器R,其(3)从已分配的寄存器中选取一个Ri作为所需寄存器R,其 选择原则为:占用该寄存器的变量值同时在主存中,或在基 本块中引用的位置最远,这样对寄存器Ri所含的变量和变量 在主存中的情况必须先做如下调整:即对RVALUE[Ri]中的 每一变量M,如果M不是A且AVALUE[M]不包含M,则需完 成以下处理: ①生成目标代码STRi,M;即把不是A的变量值由Ri中送 入内存中 ②如果M不是B,则令AVALUE[M]={M},否则,令 AVALUE[M]={M,Ri} ③删除RVALUE[Ri]中的M
这样,一旦得到一个为四元式运算的操作寄存器R,就可这样,一旦得到一个为四元式运算的操作寄存器R,就可 以进行代码生成,而当目标代码生成完成后,则又需修改 寄存器的使用信息和地址信息 • 用图12.4和图12.5给出算法的流程图:
12.3 几种常用的代码生成程序的开发方法 一.解释性代码生成法 • 解释性代码生成文法是建立一个代码生成专用语言,用这 种语言以宏定义、子程序等形式描述代码生成过程 • 通过这些宏定义和子程序把中间语言解释为目标代码 • 这种方法使机器描述与代码生成算法结合在一起,与机器 的联系直接反映在算法中 • 机器描述是通过过程的形式提供的,如采用把源程序映像 成两地址代码序列的方法进行代码生成过程中,对加法的 代码生成算法如下:
macro ADD x,y • if type of x=integer and type of y=integer • then IADD x,y • else if type of x=float and type of y=float • then FADD x,y • else error
其中含有对IADD与FADD的宏调用,以生成目标机上的其中含有对IADD与FADD的宏调用,以生成目标机上的 整数和浮点数加法指令,如对IBM360机,IADD可写为: • macro ADD a,b • from a in R1,b in R2 • emit (AR a,b) result in R1 • from a in R,b in M • emit(A a,b) result in R • from a in M,b in R • emit(A b,a) result in R
这种算法的局限性在于: (1)由于目标机的多样性、寻址方式、指令的差异等等, 给中间代码的设计带来困难 (2)代码生成语言与机器密切相关,可移植性受到限制 (3)目标机的描述与代码生成算法混在一起,当描述改变 时,势必引起算法的改变 (4)需进行指令的选择、指令的排序等低层次的繁琐工作 ,产生的目标代码质量依赖于设计者的经验和能力 (5)代码生成的视野有限,虽可进行一定范围的优化,但 对协调上下文有关的优化较困难
二.模式匹配代码生成法 • 模式匹配代码生成方法是把对机器的描述与代码生成的算 法分开 • 而对在解释性代码生成方法中,所需做的较繁重的具体情 况分析的解释工作用模式匹配来代替
也就是建立一个代码生成用的机器描述语言,用以形式地也就是建立一个代码生成用的机器描述语言,用以形式地 描述目标机的资源、指令及其语义等有关信息 • 代码生成程序根据这些信息,自动地把中间语言程序翻译 成目标机的汇编语言或机器代码 • 但在这种方法中,需通过形式描述的模式如实地反应机器 的特性,这并不是一件容易的事,而且进行模式匹配时耗 费时间很长,其目标代码的质量也不太理想
三.表驱动代码生成法 • 表驱动代码生成方法,实质上是模式匹配代码生成方法的 更进一步自动化,它是模仿从语法描述构造表和表驱动的 一种语法分析方法 • 首先,把对目标机的形式化描述进行预加工转换成代码生 成表 • 然后,用表驱动的代码生成程序,来驱动代码生成表 • 最后,把中间语言的内部表示翻译成目标机的汇编代码 • 也就是说,它是用一个代码生成程序的生成器自动地构造 一个代码生成程序
这种表驱动代码生成方法的好处是:容易使用和修改,并这种表驱动代码生成方法的好处是:容易使用和修改,并 且能较容易地为不同的计算机构造适合于它们自己的代码 生成程序 • 这样将能增强编译程序的可移植性和灵活性,但是它所生 成的目标代码的质量,将依赖于机器描述的完善程序 • 最好的方法是用形式化的方法完善地描述一台计算机,但 这并不是一件容易的事,因而这种方法有待进一步改进和 完善
12.4 全局寄存器分配(图着色法) 一.概述 • 为减少存取操作的次数,可以将频繁使用的变量保留在固 定的寄存器中,并使之贯穿不同的基本块边界,这就是全 局寄存器分配问题 • 1971年John Cokes提出,全局寄存器分配可以视为一种图 着色问题 • 图着色法最初在实验性编译程序IBM370PL/I中使用, 且很快得到广泛的推广
图着色法的基本思想:将一个程序的所有对象和可供使用图着色法的基本思想:将一个程序的所有对象和可供使用 的实际寄存器视为一个无向图的不同结点 (已知一个图g和m>0种颜色,在只准使用这m种颜色对g的结 点着色的情况下,是否能使图中任何相邻的两个结点都具有 不同的颜色呢?这个问题称为m-着色判定问题。在m-着色最 优化问题则是求可对图g着色的最小整数m。这个整数称为 图g的色数)
若两个对象间满足下列条件之一,则在它们之间引入一条若两个对象间满足下列条件之一,则在它们之间引入一条 边,以表示它们相互干扰: ①同时为活跃变量的两个对象 ②一个对象与之不能也不应该分配的寄存器
通过这种方式,构造出相应程序的干扰图 • 然后利用最大可供使用寄存器数的颜色种数,对干扰图的 所有结点进行着色 • 在对干扰图进行着色过程中,用尽量少的颜色、且将相邻 结点赋予不同的颜色
不同的颜色对应于不同的实际寄存器,若目标机具有R个不同的颜色对应于不同的实际寄存器,若目标机具有R个 寄存器,则可用R种颜色对该干扰图进行着色 • 这个着色过程就是对该干扰图进行有效的寄存器赋值 • 若不存在R种颜色,必须将其中一些变量或临时变量放在 内存中而不是寄存器中,这个过程称为spilling
图着色的基本算法如下: (1)建立Web对象 (2)用相邻矩阵表示干扰图 (3)利用相邻矩阵合并寄存器,查找拷贝sisj,若si与sj 不相互干扰,将sj的使用替换为si的使用,并将代码中的sj去 掉。若完成寄存器合并,返回到第(1)步,否则继续下一步 (4)构造干扰图的相邻列表表示 (5)计算spill开销。对每个符号寄存器,计算将其spill到内 存后又将其重新存入到寄存器中的开销