890 likes | 1.05k Views
第七章 中间代码生成 序 7.1 中间语言 7.2 说明语句 7.3 赋值语句 7.4 布尔表达式 7.6 回填 7.7 过程语句 练习. 序 ◆ “中间代码生成”程 序的 任务 是:把经过语 法分析和语义分析而获得的源程序中间表 示翻译为中间代码表示。 ◆方法:语法制导翻译。 ◆ 采用独立于机器的中间代 码的好处 : 1. 便于编译系统的 建立和编译系统的移植; 2. 便于进行独立于机器的代码优化工作。. 7.1 中间语言 语法树 后缀式
E N D
第七章 中间代码生成 序 7.1 中间语言 7.2 说明语句 7.3 赋值语句 7.4 布尔表达式 7.6 回填 7.7 过程语句 练习
序 ◆“中间代码生成”程 序的任务是:把经过语 法分析和语义分析而获得的源程序中间表 示翻译为中间代码表示。 ◆方法:语法制导翻译。 ◆采用独立于机器的中间代 码的好处: 1. 便于编译系统的 建立和编译系统的移植; 2. 便于进行独立于机器的代码优化工作。
7.1 中间语言 • 语法树 • 后缀式 • 三地址代码表示 • 7.1.1 图表示法 • 语法树,有向非循环图和后缀式表示 • 源程序的自然层次结构,例如: • a:=b * - c+b * -c
assign a + * * b assign assign b c c (a)语法树
assign a + * b assign c (b)dag(Directed Acyclic Graph)
表7.1 产生赋值语句语法树的语法制导定义 产生式 语义规则 S→id:=E E→E1+E2 E→E1*E2 S.nptr:=mknode('assign', mkleaf(id,id.place),E.nptr) E.nptr:=mknode('+', E1.nptr,E2.nptr) E.nptr:=mknode('*', E1.nptr,E2.nptr)
产生式 语义规则 E→-E1 E→(E1) E→id E.nptr:=mknode('uminus', E1.nptr) E.nptr:=E1.nptr E.nptr:=mkleaf(id,id.place)
赋值语句:a:=b*-c+b*-c 后缀式:a b c uminus * b c uminus * + assign id b assign id c unimus 1 ida * 0 2 id b + id c unimus 5 * * * 4 6 idb idb + 3 7 id a uminus uminus assign 9 8 idc idc ...
7.1.2 三地址代码 ◆一般形式 x:=y op z 图7.3 相应于图7.1的树和dag的三地址代码 t1 := -c t1:=-c t2 := b* t1 t2:=b*t1 t3 := -c t5:=t2+t2 t4 := b* t3 a:= t5 t5 := t2+t4 a := t5 (a)对于语法树的代码 (b)对于dag的代码
7.1.3 三地址语句的种类 (1)赋值语句 x:=y op z,op为二目算术算 符或逻辑算符; (2)赋值语句 x:=op y ,op为一目算符, 如一目减uminus、逻辑非not、移位算符 及转换算符; (3)复制语句 x:=y; (4)无条件转移语句goto L; (5)条件转移语句 if x relop y goto L,关系运 算符号relop(< ,=,>= 等等);
(接上页) (6)过程调用语句 param x 和 call p, n ; 过程返回语句 return y; (7)索引赋值 x:=y[i] 及 x[i] :=y ; (8)地址和指针赋值 x:=&y,x:=* y 和 * x:=y。 ◆ 在设计中间代码形式时,选择多少种算 符 需 要 trade off .
7.1.4 语法制导翻译生成三地址代码 产生式 语义规则 S→id:=E E→E1+E2 E→E1*E2 S.code:=E.code ║gen(id.place':='E.place) E.place:=newtemp; E.code:=E1.code║E2.code ║gen(E.place':='E1.place '+'E2.place) E.place:=newtemp; E.code:=E1.code║E2.code ║gen(E.place':='E1.place '*'E2.place)
(续上表) 产生式 语义规则 E→-E1 E→(E1) E→id E.place:=newtemp; e.code:=E1.code ‖gen(E.place´:=´´uminus´ E1.place) E.place:=E1.place; E.code:=E1.code E.place:=id.place; E.code:=‘’ 表7.2 产生赋值语句三地址代码的语法制导定义
(1)E.place表示存放E值的名字。 (2)E.code表示对E求值的三地址语句序列。 (3) newtemp是个函数,对它的调用将产生 一个新的临时变量。 ◆ 三地址语句序列是语法树的线性表示,用 临时变量代替语法树中的结点。 ◆实际实现中,三地址语句序列往往是被存放 到一个输出文件中,而不是将三地址语句序 列置入code属性之中
7.1.5 三地址代码的具体实现 1.四元式op, arg1, arg2, result 2.三元式op, arg1, arg2 3.间接三元式 间接码表+三元式表 ◆四元式需要利用较多的临时单元,四元式之 间 的联系通过临时变量实现。 ◆中间代码优化处理时,四元式比三元式方 便的多,间接三元式与四元式同样方便,两 种实现方式需要的存储空间大体相同。
对于语句a:=b*-c+b*-c 的三种表示方法 表7.3(a) 三地址语句的四元式表示 op arg1 arg2 result (0) (1) (2) (3) (4) (5) uminus * uminus * + assign c b c b t2 t5 t1 t3 t4 t1 t2 t3 t4 t5 a
表7.3(b) 三地址语句的三元式表示 op arg1 arg2 (0) (1) (2) (3) (4) (5) uminus * uminus * + assign c b c b (1) a (0) (2) (3) (4) 树:三元式中使用指向三元式语句的指针。
表7.3(b) 三地址语句的间接三元式表示 statement op arg1 arg2 (0) (1) (2) (3) (4) (5) (14) (15) (16) (17) (18) (19) (14) (15) (16) (17) (18) (19) uminus * uminus * + assign c b c b (15) a (14) (16) (17) (18) 语句的移动仅改变左边的语句表
7.2 说明语句 ◆说明语句的翻译:对每个局部名字,在符 号表中建立相应的表项,填写有关的信息 如类型、嵌套深度、相对地址等。 ◆相对地址:相对静态数据区基址或活动记 录中局部数据区基址的一个偏移值。 7.2.1 过程中的说明语句 ◆一个过程中的所有说明语句作为一个类集 来处理。用一个全程变量Offset来记录下 一个数椐在活动记录中的位置。
P→ {offset:=0} D D→D; D D→id :T {enter(id.name,T.type,offset); offset:=offset+T.width} T→integer {T.type :=integer; T.width:= 4} T→real {T.type:=real; T.width :=8} T→array[num]of T1 {T.type:=array(num.val, T1.type ); T.width: =num.val *T1.width} T→ ↑T1 {T.type:=pointer(T1.type); T.width := 4} 图7.4计算说明语句中名字的类型和相对地址
7.2.2 保留作用域信息 1 .问题的提出 一般的语言中,标识符的作用在程序正文 中有一个确定的范围。因此,同一个标识符在不同的程序正文中可能标识不同的对象,具有不同的性质,要求分配不同的存储空间。于是提出下面的问题:如何组织符号表,使得同一个标识符在不同的作用域中得到正确的引用而不产生混乱。 编译程序分析说明语句时建立符号表,编译执行语句时查找符号表。Lookup(id)返回正
确的 id.entry。 2. 程序结构 一组嵌套过程,每个过程说明的局部名字建立一个符号表,所有正在翻译过程的符号表组成整个源程序的符号表。翻译语句部分时查找符号表,查找过程的符号表的路线相当于当前被 翻译过程的静态链。 翻译时,实际上,为每一个过程维持一个符号表。
Sort a,x R i a E i,j x,a Qsort m,n,k,v k P y,z,i,j a,v,e(i,j)
sort nil header a x to readarray readarray to exchange exchange quicksort readarray exchange quicksort header header header i k v partition partition header 图7.5 嵌套过程的符号表 i j
编译过程:用先根次序遍历上一页中的树,用栈来存储编译过程中使用的量,象table和offset。编译过程:用先根次序遍历上一页中的树,用栈来存储编译过程中使用的量,象table和offset。 进入过程partion,quicksort过程的编译并未结束,活动记录的设计和符号表的建立尚未完成,因此,要把quicksort过程使用的table和offset保存于栈中。 使用两个栈,分别保存刚编译过程的符号表箭头table和offset的值。
图7. 6 处理嵌套过程中的说明语句 P→MD {addwidth(top(tblptr),top(offset)); pop(tblptr);pop(offset)} M→ε {t:=mktable(nil); push(t,tblptr);push(0,offset)} D→D1;D2 D→proc id; N D1;S {t:=top(tblptr); addwidth(t,top(offset)); pop(tblptr);pop(offset); enterproc(top(tblptr),id.name,t)}
D→ id: T {enter(top(tblptr),id.name,T.type, top(offset)); top(offset):= top(offset) +T.width} N→ε {t :=mktable(top(tblptr)); push(t,tblptr);push(0,offset)} 如下操作: 1. mktable( previous)创建一张新符号表 2. enter(table,name,type,offset)插入表项 3. addwidth(table,width)记录总域宽 4. enterproc(table,name, newtable) 建立过程新表项
7.2.3 记录中的域名 T→ record LD end { T.type:=record(top(tblptr)); T.width:=top(offset); pop(tblptr);pop(offset)} L→ε {t:=mktable(nil); push(t,tblptr);push(0,offset))} 图7.7 为-个记录中的域名建立一张符号表 该翻译模式强调了作为一个语言结构的记录的设计与活动记录之间的相似处.
关于符号表的进一步讨论: 1. 符号表的数椐结构 (a) 线性表; ( b ) 散列表; ( c ) 树结构。 2. 符号表上的运算: (a)插入(insert); (b)查找(lookup); ( c )删除 (delete).
3. 符号表必须维持源程序中的作用域信息, 源程序中的作用域规则因语言而异,对 一种语言,根椐符号表采用的数据结构, 维持源程序中的作用域信息方法不同。 4. 符号表中名字的属性信息由两方面组成: (a)名字在源程序中的属性信息,例如, 名字源程序表示,种类,类型等; (b)经分析加工得到的有用信息,例如, 对于变量来说,嵌套深度,在活动 记录中的偏移量等。
7.3 赋值语句 • 赋值语句的翻译 • 表达式的类型可以是整型、实型、数组 和记 录 • 7.3.1 符号表中的名字 • ◆名字可以理解为指向符号表中相应该名字 • 表项的指针 • 如何根据名字查找符号表的表项?
◆过程lookup(id.name)检查是否在符号表中存在相应此名字的表项。◆过程lookup(id.name)检查是否在符号表中存在相应此名字的表项。 采用最近嵌套作用域规则查找非局部名字时,lookup过程先通过top(tblptr)指针在当前符号表中查找名字为name的表项。 若找到,返回有关信息。 若未找到,lookup就利用当前符号表表头 的指针找到该符号表的外围符号表,然后在那里查找名字为name的表项,直到所有外围过程的符号表中均无此name表项,则lookup返回nil,表明查找失败。
图7.8 产生赋值语句三地址代码的翻译模式 S→id:=E {p:=lookup(id.name); if p<>nil then emit(p':=' E.place) else error } E→E1+E2{E.place=newtemp; emit(E.place':='E1.place'+'E2.place')} E→E1*E2{E.place=newtemp; emit(E.place':='E1.place'*'E2.place')} E →-E { E.place:=newtemp; emit(E.place ´ := ´ ´uminus´E1.place}
E→(E1) {E.place:=E1.place} E→id {p:=lookup(id.name); if p<>nil then E.place:=p else error} lookup(id.name)= id.entry nil emit 它将生成的三地址代码送到输出文件上。 语义动作应包括类型分析,文法符号应有类型属性,在类型分析的基础上,进行相容和赋值相容检查,生成类型转换的三地址代码。
7.3.2 数组元素地址分配 数组元素的三地址代码是什么? 如何生成数组元素的三地址代码。 一. 数组元素地址的计算公式 ◆数组A的下标为i的元素的开始地址 VAR a:ARRAY [low..high] OF real; 求a[i]的地址. base+(i-low )* w (7.3) =bace-low*w + i*w 常量部分(可在编译时计算出来)+变量部分 其中, base 是数组元素a[low]的地址。
◆对于一个二维数组,可以按行或按列存放 若按行存放,则可用如下公式计算A[i1,i2] 的相对地址: base+((i1 一low1)* n2+i2 一low2)*w) = base-((low1 *n2)+low2)*w + (i1*n2)+i2)* w (7.4) 令c= ((low1 *n2)+low2)*w 则常量部分=a[low1,low2]-c. ◆计算元素A[i1,i2,...,ik] 相对地址的推广公式((...((i1*n2+i2)*n3+i3...)*nk+ik)*w+base-((...((low1*n2+low2)*n3+low3...)*nk+lowk)*w (7.5)
c=((...((low1*n2+low2)*n3+low3)...)*nk +lowk) * w 变量部分=((...((i1*n2+i2)*n3+i3...)*nk+ik)*w a[i1,i2,…in]的地址 =base-c+变量部分 x:=a[i1,i2] 三地址代码结构: t1: =变量部分 t2:=base-c t3:=t2[t1] x:=t3 ◆计算动态数组元素地址的公式与在固定长度 数组情况下是同样的,只是上、下界在编译 时是未知的。
数组的类型信息: VAR a:ARRAY [1..10,1..20] OF real; 符号表中的信息可组织如下: a 嵌套深度 偏移量 类型 名字表 C=84 low1=1 high1=10 n1=10 low2=1 high2=20 n2=20 基类型 信息向量 Real 标准类型 8
◆ 数组元素引用的文法 L→ id [Elist] | id Elist→Elist,E|E 变量部分:((i1*n2+i2)*n3+i3)*n4+…… Elist + i1,i2,…,in nj:=highj-lowj+1 (7.5) em=em-1*nm+im (7.6) 为获得有关数组各维长度nj的信息,改写为: L→Elist]| id Elist→Elist,E|id[ E
◆ 有关变量与函数的说明 Elist.ndim:记录Elist中的下标表达式的个数, 即维数; 函数limit(array,j):返回nj ,即由array所 指示的数组的第j维长度; Elist.place:临时变量,用来临时存放由Elist 中的下标表达式计算出来的值; em=em-1*nm Elist 引进综合属性array,指向a的符号表地址。
7.3.3 访问数组元素的翻译模式 ◆给定文法: (1)S→L:=E (2)E→E+E (3)E→(E) (4)E→L (5) L→Elist] (6)L→id (7)Elist→Elist,E (8)Elist→id[ E
◆相应语义动作 • 若L是一个简单的名字,将生成一般的赋值; • 若L为数组元素引用,则生成对L所指示地址的索引赋值 • 1.S→L :=E • {if L. offset=null • then emit(L.place' :=' E.place) else • emit(L.place'['L.offset']':= ' E. Place)}2.E→E1+E2 • {E.place:=newtemp; • emit(E.place':=' E1.place'+'E2.place)}
(接上页) 3.E→(E1) { E.place :=E1.place} 使用索引来获得地址L.place[L.offset] 的内容: 4. E→L {if L.offset=null then E.place:=L.place /* L is a simple id */ else begin E.place:=newtemp; emit(E.place':='L.place'[L.offset']') end}
(接上页) 5. L→Elist] {L.place:=newtemp; emit(L.place':='Elist.array'-' c) ((…((low1 nt2+low2)n3+low3)…) *nk +lowk) * w )); L.offset:=newtemp; emit(L.offset':='w'*'Elist.place)} 一个空的offset表示一个简单的名字: 6. L→ld (L.place:=id.place;L.offset:=null}
(接上页) 应用递归公式扫描下一个下标表达式 7.Elist→Elist1,E {t:=newtemp; m: =Elist1.ndim+1; emit(t' := ' Elist1.place '*'’ limit (Elist1.array,m)); emit(t ' := ’ t ' +’ E.place); Elist.array:=Elist1.array; Elist.place:=t; Elist.ndim:=m}
8. Elist→id[E {Elist.place:=E.place; Elist.ndim:=1: Elist.array:=id.place} 例7.1设A为一个10*20的数组,即n1= 10, n2= 20。并设w=4,数组第一个元素为 A[1,1] 。则有, ((low1*n2)+low2)*w=(1*20 +1)*4=84。对赋值语句x:= A[y,z]的带注释的分析树如图7.10所示。
图7.10 关于x := A[y, z]的带注释的分析树 S L.place = x L.offset = null := E.place = t4 L.place = t2 L.offset = t3 x Elist.place = t1 Elist.ndim = 2 Elist.array = A ] (未完)
Elist(place=t1,ndim=2,array=A) Elist.place = y Elist.ndim = 1 Elist.array = A E.place = z , L.place = z L.offset = null A [ E.place = y L.place = y L.offset = null z y
该赋值语句在由底向上的分析中被翻译成如下三地址语句序列:该赋值语句在由底向上的分析中被翻译成如下三地址语句序列: t1:= y*20 t1:= t1+z t2:= A-84 t3: = 4*t1 t4:= t2[t3] x : = t4 注意:20、84、4都是编译中引入的常量。
7.3.4 访问记录中的域 编译器将记录的域的类型和相对地址保存在相应域名的符号表表项之中,可以把用在符号表中查找名字的程序同样用来查找域名。 例如:表达式:p↑.info+1 变量P是一个指向某个记录类型的指针,info 为其中一个算术型类型的域名。 编译程序内部把 p表示为: pointer(record(t)) ,域名info将可以在t所指向的符号表中查找。