620 likes | 814 Views
编译原理与技术. 中间代码生成. 中间代码生成. -中间代码形式 -控制流语句翻译. 中间代码生成. 中间代码的种类 - 后缀式(逆波兰式) e.g.1 a + b * -c a b c @ * + e.g.2 1 ) a := b* -c + b * -c ,其后缀式为 a b c @ * b c @ * + assign 2 ) if a>b then c := c + 1 a b > c c 1 + assign IF - 语法树 vs. 分析树 e.g.3 1 ) a := b* -c + b * -c ,其语法树为.
E N D
编译原理与技术 中间代码生成 《编译原理与技术》讲义
中间代码生成 -中间代码形式 -控制流语句翻译 《编译原理与技术》讲义
中间代码生成 • 中间代码的种类 - 后缀式(逆波兰式) e.g.1 a + b * -c a b c @ * + e.g.2 1)a := b* -c + b * -c,其后缀式为 a b c @ * b c @ * + assign 2)if a>b then c := c + 1 a b >cc 1 + assignIF - 语法树 vs. 分析树 e.g.3 1)a := b* -c + b * -c,其语法树为 《编译原理与技术》讲义
中间代码的种类 - e.g.3 1)a := b* -c + b * -c 语法树 vs. 分析树 赋值语句 assign E assign E a + a E + E * * E * E E * E b @ b @ @ @ E E b b c c c c 《编译原理与技术》讲义
中间代码的种类 - e.g.3 2)a := b* -c + b * -c 语法树 vs. DAG assign assign a + a + * * * b @ b @ b @ c c c 《编译原理与技术》讲义
中间代码的种类 • e.g.4 构造表达式的语法树(DAG) 产生式 语义规则 EE1 + E2 E.nptr := mknode(‘+’,E1.nptr, E2.nptr) EE1 - E2 E.nptr := mknode(‘-’,E1.nptr, E2.nptr) EE1 * E2 E.nptr := mknode(‘*’,E1.nptr, E2.nptr) EE1 / E2 E.nptr := mknode(‘/’,E1.nptr, E2.nptr) E( E1 ) E.nptr := E1.nptr E - E1 E.nptr := mknode(‘@’,E1.nptr, -) Enumber E.nptr := mkleaf(‘NUM’,number.lex_val) Eid E.nptr := mkleaf(‘ID’,id.entry) 《编译原理与技术》讲义
中间代码的种类 • e.g.4 构造表达式的语法树(DAG) E.nptr- E的语法树(根结点指针) mknode(op, left, right)-建立一个表达式语法树结点,它的运算符为op,左、右运算对象是left和right所指的语法树。 mkleaf(‘NUM’,number.lex_val)- mkleaf(‘ID’,id.entry)- 建立表达式语法树的叶子结点。 《编译原理与技术》讲义
+ * @ ID ID a b NUM 4 中间代码的种类 • e.g.4 构造表达式a+b*-4的语法树(DAG) 《编译原理与技术》讲义
中间代码的种类 • 三地址代码 一般形式:x := y op z 或 x := op y e.g.5 a := b* -c + b * -c的三地址代码 1)t1 := - c 1’) t1 := - c 2)t2 := b * t1 2’) t2 := b * t1 3)t3 := - c 3’) t3 := t2 + t2 4)t4 := b * t3 4’) a := t3 5)t5 := t2 + t4 6)a := t5 《编译原理与技术》讲义
中间代码的种类 • 常用的三地址代码 - x := y op z 二元运算 - x := op y 单目运算 - x := y 值拷贝 - goto L 无条件转移到代码标号L处 - if x RELOP y goto L 条件转移代码:若x和y满足RELOP关系,则转移至代码标号L处 - param X 参数X - CALL proc,N 调用过程proc,参数个数为N 《编译原理与技术》讲义
中间代码的种类 • 常用的三地址代码(续) - return y 过程返回,返回值为y - x := y [ i ] x[ i ] := y 变址语句 - x := &y *x := y x := *y 指针操作语句 • 三地址代码实现形式 - 四元式: ( op, arg1, arg2, result ) - 三元式: ( op, arg1, arg2) - 间接三元式(三元式的指针表) 《编译原理与技术》讲义
中间代码的种类 • e.g.6 a := b* -c + b * -c 的四元式和三元式 四元式 vs. 三元式 1)( @, c, -, t1) ① ( @, c, -) 2)( * , b, t1, t2) ② ( * , b, ①) 3)( @, c, -, t3) ③ ( @, c, -) 4)( * , b, t3, t4) ④ ( * , b, ③) 5)( + , t2, t4, t5 ) ⑤ ( + , ②, ④) 6)(:=, t5,-, a) ⑥ (:=, a, ⑤) 《编译原理与技术》讲义
中间代码的种类 e.g.7 a := b * c + d / f 的间接三元式 间接三元式 / 调整次序 三元式 ⑴ ⑬ ⑭ ⑬ ( * , b , c ) ⑵ ⑭ ⑬ ⑭ ( / , d , f ) ⑶ ⑮ ⑮ ⑮ ( + , ⑬, ⑭) ⑷⑯⑯ ⑯( := , a , ⑮) 《编译原理与技术》讲义
中间代码的种类 《编译原理与技术》讲义
说明语句的翻译 • 一般不产生代码;仅将有关变量的属性填入符号表(如类型、存储偏移位置-offset) • e.g. 8 一个说明语句的翻译方案 文法G1如下: PD DD ; D Did : T Tint | real | array [ number ] of T1 | T1 《编译原理与技术》讲义
e.g. 8 一个说明语句的翻译方案(续) - 有关符号的属性 T.type - 变量所具有的类型,如 整型 INT 实型 REAL 数组类型 array(元素个数,元素类型) 指针类型 pointer(所指对象类型) T.width - 该类型数据所占的字节数 offset - 变量的存储偏移地址 《编译原理与技术》讲义
e.g. 8 一个说明语句的翻译方案(续) 《编译原理与技术》讲义
e.g. 8 一个说明语句的翻译方案(续) (1)PM D (2)DD1 ; D2 (3)Did : T { // 填入变量的相关属性 enter( id.name, T.type, offset) // 计算下一可用偏移位置 offset = offset + T.width } (4)M { offset := 0 // 偏移初始为0} 《编译原理与技术》讲义
e.g. 8 一个说明语句的翻译方案(续) (5)Tint { T.type := INT; T.width := 4 } (6)Treal { T.type := REAL; T.width := 4 } (7)Tarray [ number ] of T1 { T.type := array( number.val, T1.type); T.width := number.val * T1.width } (8)Tpointer( T1 ) { T.type := pointer( T1.type) ; T.width := 4 } 《编译原理与技术》讲义
e.g. 9 一个嵌套说明语句的翻译方案 • 文法G2如下: PD DD1 ; D2 Did : T D proc id ; D1 ; S Tint | real | array [ number ] of T1 | T1 Sa 《编译原理与技术》讲义
e.g. 9 一个嵌套说明语句的翻译方案 • 产生式 D proc id ; D1 ; S 引入过程id的声明;D1为其局部说明语句,可以声明变量或嵌套定义其它过程; • 约定每个过程有自己独立的符号表且嵌套定义的过程符号表头有指针指向外围(父)过程的符号表;而父过程符号表中有该嵌套子过程的条目(涉及子过程名和子过程符号表的指针等属性)。 《编译原理与技术》讲义
相关定义: /* 在父过程符号表中建立子过程名的条目*/ enter-proc(parent-table, sub-proc-name, sub-table) /* 将所声明变量的类型、偏移填入当前符号表*/ enter(current-table, name, type, current-offset) /* 建立新的符号表,其表头指针指向父过程符号表*/ mktable(parent-table) addwidth(table,offset )//记录变量占用的总空间 符号表栈tblptr和偏移栈offset(栈顶值分别表示当前分析的过程的符号表及可用变量偏移位置) 《编译原理与技术》讲义
e.g. 9 一个嵌套说明语句的翻译方案 PM D { addwidth( top(tblptr), top(offset) ), pop( tblptr ) ; pop( offset ) } /* 保留变量分配空间大小并清空符号表和偏移栈 */ M { t := mktable(null); push(t,tblptr); push(0,offset) } /* 建立主程序(最外围)的符号表 偏移从0开始 */ DD1 ; D2 《编译原理与技术》讲义
D proc id ; N D1 ; S { t := top( tblptr ); addwidth( t, top( offset ) ); pop( tblptr ); pop( offset ); enter-proc( top( tblptr ), id.name, t ) } /* 保留当前(子)过程声明变量的总空间;弹出符号表和偏移栈顶(露出父过程的符号表和偏移);在父过程符号表中填写子过程名有关条目 */ N { t := mktable( top( tblptr ) ); push( t, tblptr ) ; push( 0, offset ) } /* 建立子过程的符号表和偏移从0开始 */ 《编译原理与技术》讲义
Did : T { enter( top( tblptr ), id.name, T.type, top( offset ) ); top( offset ) := top( offset ) + T.width; } /* 将变量name的有关属性填入当前符号表 */ /* 以下产生式的翻译方案略 */ Tint | real | array [ number ] of T1 | T1 Sa 《编译原理与技术》讲义
e.g.10 过程嵌套声明 i : int; j : int ; PROC P1 ; k : int; f : real ; PROC P2; l : int ; a1 ; a2; PROC P3; temp : int ; max : int ; a3; P0 P1 P2 P3 过程声明层次图 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • 初始:M null 总偏移: P0 top P0 0 符号栈 偏移栈 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • i : int ; j : int ; null 总偏移: P0 i INT 0 j INT 4 top P0 8 符号栈 偏移栈 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • PROC P1 ; (N) null 总偏移: P0 i INT 0 j INT 4 top 总偏移: P1 P1 0 P0 8 符号栈 偏移栈 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • k : int ; f : real ; null 总偏移: P0 i INT 0 j INT 4 top 总偏移: P1 P1 8 k INT 0 P0 8 f real 4 符号栈 偏移栈 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • PROC P2 ; (N) null 总偏移: P0 i INT 0 j INT 4 top P2 0 总偏移: P1 P1 8 k INT 0 P0 8 f real 4 符号栈 偏移栈 总偏移: P2 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) • l : int ; null 总偏移: P0 i INT 0 j INT 4 top P2 4 总偏移: P1 P1 8 k INT 0 P0 8 f real 4 符号栈 偏移栈 总偏移: P2 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移: • a1 ; P0 i INT 0 j INT 4 总偏移: P1 k INT 0 top P1 8 f real 4 P0 8 P2 proc 符号栈 偏移栈 总偏移:4 P2 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移: P0 • a2 ; i INT 0 j INT 4 P1 proc 总偏移:8 P1 k INT 0 top P0 8 f real 4 符号栈 偏移栈 P2 proc 总偏移:4 P2 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移: P0 • PROC P3 ; (N) i INT 0 j INT 4 P1 proc 总偏移: P3 总偏移:8 P1 k INT 0 f real 4 top P2 proc P3 0 P0 8 总偏移:4 P2 符号栈 偏移栈 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移: P0 • temp : int; max : int; i INT 0 j INT 4 P1 proc 总偏移: P3 temp INT 0 max INT 4 总偏移:8 P1 k INT 0 f real 4 top P2 proc P3 8 P0 8 总偏移:4 P2 符号栈 偏移栈 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移: P0 i INT 0 • a3; j INT 4 P1 proc P3 proc 总偏移:8 P3 temp INT 0 max INT 4 总偏移:8 P1 k INT 0 f real 4 P2 proc top P0 8 总偏移:4 P2 符号栈 偏移栈 l INT 0 《编译原理与技术》讲义
e.g.10 过程嵌套声明(续) null 总偏移:8 P0 i INT 0 • P M D; j INT 4 P1 proc P3 proc 总偏移:8 P3 temp INT 0 max INT 4 总偏移:8 P1 k INT 0 f real 4 P2 proc 总偏移:4 top P2 符号 栈空 偏移 栈空 l INT 0 《编译原理与技术》讲义
记录的说明 • 记录(record )语法如下: T record D end 说明D含义同前面。(一般不含有过程声明,但C++可以) • 可以将记录中的域变量声明放在单独的符号表中(参照嵌套过程声明的做法,但外围过程指针为空)。 《编译原理与技术》讲义
记录说明的翻译 T record L D end { T.type := record( top( tblptr ) ); //记录类型定义 T.width := top( offset ); // 记录的占用空间大小 pop( tblptr ); pop( offset ) } L { t := mktable( null ); // 无外围“过程” push(t, tblptr ); push( 0, offset ); // 域变量偏移从0开始 } D的翻译同前。 《编译原理与技术》讲义
e.g.11 记录域的偏移 • 有2个C语言的结构定义如下: struct A { struct B { char c1; char c1; char c2; long l; long l; char c2; double d; double d; } S1; } S2; 《编译原理与技术》讲义
e.g.11 记录域的偏移 • 数据(类型)的对齐-alignment 在 X86-Linux下: char :对齐1,起始地址可分配在任意地址 int,long,double :对齐4,即从被4整除的地址开始分配 注* :其它类型机器,double可能对齐到8,如sun-SPARC 《编译原理与技术》讲义
e.g.11 记录域的偏移 • 结构A 和 B的大小分别为16和20字节(Linux) 0 0 衬垫 padding 4 4 8 8 12 12 16 16 20 结构 A 结构 B 《编译原理与技术》讲义
e.g.11 记录域的偏移 • 2个结构中域变量的偏移如下: struct A { struct B { char c1; 0 char c1; 0 char c2; 1 long l; 4 long l; 4 char c2; 8 double d; 8 double d; 12 } S1; } S2; 《编译原理与技术》讲义
赋值语句的翻译 • 含简单变量的赋值语句,如id := E,其中, id为普通变量,而E是简单的算术表达式。文法定义如下: A id := E E E1 + E2 E E1 * E2 E - E1 E ( E1 ) E id 1) E的属性定义: E.place : 存放表达式E“值”的场所 2) newtemp - 获取临时变量以存放中间结果 3) emit - 产生三地址码(TAC)的过程 4) lookup( name ) -检查name是否在符号表中有定义(条目) 《编译原理与技术》讲义
含简单变量的赋值语句的翻译 A id := E { p = lookup (id.name); if ( p != null ) emit( p ‘:=‘ E.place) else error } E E1 + E2 { t := newtemp; E.place := t ; emit( t ‘:=‘ E1.place ‘+’ E2.place ) } E E1 * E2 { t := newtemp; E.place := t; emit( t ‘:=‘ E1.place ‘*’ E2.place ) } E - E1 { t := newtemp; E.place := t; emit( t ‘:=‘ ‘-’ E1.place ) } E ( E1 ) { E.place := E1.place } E id { p = lookup (id.name); if ( p != null ) E.place := p else error } 《编译原理与技术》讲义
e.g.12 赋值语句a := -b*c+d的翻译 TAC: A 1) t1 := - b a := E.place=t3 2) t2 := t1 * c 3) t3 := t2 + d E.place=t2 + E.place=d 4) a := t3 E.place=t1 E.place=c d * - E.place=b c b 《编译原理与技术》讲义
e.g.13 赋值语句后缀式代码生成 A L := E { print ( ‘:=‘ ) } E E1 + E2 { print ( ‘+’ ) } E E1 * E2 { print ( ‘*’ ) } E - E1 { print ( ‘@’ ) } E ( E1 ) E id { p = lookup (name); if ( p != null ) print( p ) else error } Lid { p = lookup (name); if ( p != null ) print( p ) else error } 《编译原理与技术》讲义
数组元素的翻译 • 数组类型的声明 e.g. Pascal的数组声明, A : array[ low1..high1,…,lown..highn] of integer ; 数组元素:A[ i , j, k,…] 或 A[i][j][k]… (下界)low1 i high1(上界) , … e.g. C的数组声明, int A [100][100][100]; 数组元素:A[ i ][30][40] 0 i (100-1) 《编译原理与技术》讲义
数组元素的翻译 • 数组元素的地址计算 仅讨论按行优先排列(即行主序)。约定数组名,如A,表示整个数组的起始地址(偏移);w 表示数组元素所占字节数。 一维:A[ i ] 的地址addr为, addr = A + ( i - low1 ) * w = i * w + ( A - low1 * w ) 二维:A[ i1 , i2 ]的地址addr为,( n2=high2-low2+1) addr = A + ( ( i1 - low1 ) * n2 + ( i2 - low2 ) )*w = ( i1 * n2 + i2 ) *w + (A-(low1*n2+low2)*w) 《编译原理与技术》讲义