编译器设计与实现.ppt

上传人:本田雅阁 文档编号:3459102 上传时间:2019-08-28 格式:PPT 页数:65 大小:1.55MB
返回 下载 相关 举报
编译器设计与实现.ppt_第1页
第1页 / 共65页
编译器设计与实现.ppt_第2页
第2页 / 共65页
编译器设计与实现.ppt_第3页
第3页 / 共65页
编译器设计与实现.ppt_第4页
第4页 / 共65页
编译器设计与实现.ppt_第5页
第5页 / 共65页
点击查看更多>>
资源描述

《编译器设计与实现.ppt》由会员分享,可在线阅读,更多相关《编译器设计与实现.ppt(65页珍藏版)》请在三一文库上搜索。

1、编译器设计与实现,Lcc原理剖析,华中科技大学计算机学院 张 德,2019/8/28,一、概述,1、编译器各阶段,2019/8/28,2、编译器各阶段的分组,前端:依赖于语言并很大程度上独立于目标机器。一般包括语法分析、词法分析、符号表的建立、语义分析、中间代码生成以及相关错误处理。 后端:依赖于目标机器的阶段或某些阶段的某些部分。一般来说,后端完成的任务不依赖于源语言而只依赖于中间语言。主要包括代码优化、代码生成以及相关的错误处理和符号表操作。,2019/8/28,二、符号表,符号表是编译器保存信息的中心库,编译器的各部分通过符号表进行交互,并访问符号表中的数据符号。 符号表把各种名字映射到

2、符号集合。常量、标识符和标号都是名字,不同名字有不同的属性。 符号管理不仅要处理符号本身,还管理符号的作用域。,2019/8/28,1、符号的表示,struct symbol char *name; /名称 int scope; /作用域 Coordinate src; /在源程序中位置 Symbol up; /连接符号表中上一个符号 List uses; /可保存一个Coordinate列表,表示使用情况 int sclass; /扩展存储类型 /符号标记 Type type; /如变量、函数、常量、结构或联合等信息 float ref; /被引用的粗略次数 union /联合u为标号、结构

3、、联合、枚举、常量、全局 /和静态变量提供附加信息 u; / Xsymbol x; /由后端处理,如为变量分配寄存器 / 为调试器产生数据信息 ,2019/8/28,1、符号的表示,scope域: enum CONSTANTS=1, LABELS, GLOBAL, PARAM, LOCAL ; 第k层中声明的局部变量其scope域等于LOCAL+k。 src域: typedef struct coord char *file; unsigned x, y; Coordinate; file指名包含该符号定义文件名,y和x表示出现的行号及行中位置。 sclass域:符号扩展类型 可以是AUTO、

4、REGISTER、STATIC或EXTERN等 首字母大写的类型表示全小写类型的指针,如Symbol。,2019/8/28,2、符号表的表示,extern Table constants; extern Table externals; extern Table globals; extern Table identifiers; extern Table labels; extern Table types; struct table int level; /同symbol中scope域 Table previous; /符号表链表,指向level-1的表 struct entry stru

5、ct symbol sym; struct entry *link; *buckets256; /这是一个哈希链数组,方便插入、查找 Symbol all; /指向当前及其外层所有符号列表的表头 ;,2019/8/28,3、符号表举例,int x, y; f(int x, int a) int b; y = x + a*b; if (y 5) int a; y = x + a*b; ,2019/8/28,2019/8/28,4、符号表的相关操作,查找和建立标识符 Symbol install(const char * name, Table * tpp, int level, int aren

6、a); Symbol lookup(const char *name, Table tp); 标号:与标识符相似,但不涉及作用域 常量:这些符号保存在constants表中 产生变量:用于产生静态变量保存字符串等,2019/8/28,三、代码生成接口,这一章内容定义了与目标机器无关的前端和与目标机器相关的后端之间的接口。 Lcc接口包括一些共享数据结构、18个函数和包括36个操作符的语言。该语言用于将可执行代码从源程序生成dag(有向无环图)。 共享数据结构可供前后端共享,但某些域为一端私有。symbol就是一个共享数据结构。,2019/8/28,1、类型度量,typedef struct m

7、etrics unsigned char size, align, outofline; Metrics; size:类型的大小; align:对齐字节数; outofline:控制相关类型的常量的放置。为1时,不出现在dag中,存于静态变量中。 Metrics charmetric; Metrics shortmetric; Metrics intmetric; Metrics floatmetric; Metrics doublemetric; Metrics structmetric;,2019/8/28,2、接口记录,typedef struct interface Xinterfac

8、e x; Interface; lcc为每一种目标机器形成一个独有的接口实例。x域是对interface的扩展,后段使用它存放与目标及其相关的接口数据和函数,对后端私有。,2019/8/28,3、dag操作,可执行代码用dag来描述。函数体是用dag组成的序列或森林。每个dag都可以同过gen函数传给后端。 dag节点 struct node short op; short count; Symbol syms3; Node kids2; Node link; Xnode x; ;,2019/8/28,3、dag操作,op域存放dag操作符。 dag操作符后缀表示操作数类型: enum F=F

9、LOAT, I=INT, U=UNSIGNED, P=POINTER, V=VOID, B=STRUCT ; 如CNST,有变体CNSTI、CNSTU、CNSTP等。 CNST = 14; CNSTC=CNST+F; CNSTI=CNST+I; ,2019/8/28,2019/8/28,2019/8/28,3、dag操作,举例: int i, *p; f() i = *p+;,2019/8/28,4、接口标志,unsigned little_endian:1; 目标机器存储是低位优先还是高位优先 unsigned mulops_calls:1; 有硬件实现乘、除和求余,mulopes_call

10、s应等于0 unsigned wants_callb:1; 通知前端产生CALLB节点以调用返回结构的函数 unsigned wants_argb:1; 通知前端节点产生ARGB节点以产生结构参数 unsigned left_to_right:1; 告诉前端按照从左到右的顺序计算和提交参数给后端 unsigned wants_dag:1; 告诉前端传递dag给后端,2019/8/28,5、函数,前端将函数编译为私有数据结构。将函数的任意部分传递给后端之前,前端必须先对每个函数进行完整的分析。 函数的处理:function函数包括前端过程gencode遍历前端的私有数据结构,将dag的每个森林传

11、给后端过程gen。gen选择代码,在dag上添加注释并将返回一个dag指针。gencode还可以调用local宣告新的局不变量。前端过程emitcode再次遍历,将gen返回的指针传递给emit函数发送代码。,2019/8/28,6、上行调用,前段调用后端以执行代码生成和发送。后端调用前端完成输出、分配存储空间、查询类型等功能。上行调用即后端调用前端。 allocate分配空间,保证对齐方式符合机器多 数类型 newnode分配新的dag节点 newconst符号表中创建新的常量 newtemp符号表中创建新的变量 ,2019/8/28,四、词法分析,词法分析器读入源程序,产生语言的基本词法单

12、元。 例:*prt 56;,2019/8/28,1、输入,2019/8/28,n,cp,limit,当limit-cp小于某一个固定值时,调用fillbuf函数填充buffer,2、单词识别,部分文法: token: keyword identifier constant operator punctuator punctuator: one of ( ) * , : = ; ,2019/8/28,定义: ID 标识符 FCON 浮点常量 ICON 整型常量 SCON INCR DECR DEREF ,emun #define xx(a,b,c,d,e,f,g) a=b, #define yy

13、(a,b,c,d,e,f,g) #include “token.h” LAST token.h文件: yy(0, 0, 0, 0, 0, 0, 0) xx(FLOAT, 1, 0, 0, 0, CHAR, “float“) xx(DOUBLE, 2, 0, 0, 0, CHAR, “double“) xx(CHAR, 3, 0, 0, 0, CHAR, “char“) xx(SHORT, 4, 0, 0, 0, CHAR, “short“) xx(INT, 5, 0, 0, 0, CHAR, “int“) xx(UNSIGNED, 6, 0, 0, 0, CHAR, “unsigned“) x

14、x(POINTER, 7, 0, 0, 0, 0, “pointer“) xx(VOID, 8, 0, 0, 0, CHAR, “void“) xx(STRUCT, 9, 0, 0, 0, CHAR, “struct“) ,2019/8/28,3、关键字的识别,可以通过查表完成,也可以通过硬编码方式识别。 例如,当起始小写字母为i时由gettok函数中switch语句的case i处理。,2019/8/28,case i: if (rcp0 = f,4、标识符识别,case h: case j: case k: case m: case n: case o: case p: case q: c

15、ase x: case y: case z: case A: case B: case C: case D: case E: case F: case G: case H: case I: case J: case K: case M: case N: case O: case P: case Q: case R: case S: case T: case U: case V: case W: case X: case Y: case Z: id: if (limit - rcp MAXLINE) cp = rcp - 1; fillbuf(); rcp = +cp; ,2019/8/28,a

16、ssert(cp = rcp); token = (char *)rcp - 1; while (map*rcp,检查是否需要填充buffer,5、其他,另外还有: 数字识别 字符常量和字符串识别 都是有gettok函数实现,实现方法相似。 词法分析器可以有象LEX这样的工具实现。Lcc手工实现了词法分析器,体积更小。,2019/8/28,五、语法分析,根据语言的文法分析,以确认输入是否符合语言规则,并建立输入源程序的内部表示。 Lcc采用递归下降的语法分析。 语法分析以形式语言理论为基础,采取语法制导翻译方法,处理程序中的错误。,2019/8/28,1、表达式,表达式的表示: (a+b)+b

17、*(a+b),2019/8/28,表达式的分析: c语言的小部分表达式语法: expr: term+term term: factor *factor factor: ID| ( expr ) T(expr) T(term+term) T(term)T(+term) term();T(+term) term();while(t = +) T(+term) term();while(t = +) T(+)T(term) term();while(t = +) t = gettok();T(term) term();while(t = +) t = gettok(); term() 同理得分析函数

18、term是:factor();while(t = *) t = gettok(); factor(),2019/8/28,void factor() if(t=ID) t=gettok(); else if (t = () t=gettok(); expr(); expect() ; ,c语言表达式分析 赋值表达式: assignment-expression: conditional-expression unary-expression assign-operator assignment-expression Tree expr1(int tok) static char stop =

19、IF, ID, 0 ; Tree p = expr2(); if (t = = | (prect = 6 else return p ,2019/8/28,条件表达式: conditonal-expression: binary-expression? expression : conditional-expression static Tree expr2(void) Tree p = expr3(4); if (t = ?) Tree l, r; Coordinate pts2; if (Aflag 1 ,2019/8/28,另有二元表达式、一元表达式、后缀表达式和基本表达式。 表达式分析

20、多是用递归和大量switch语句实现。 在编译领域用一个分析函数代替n个函数处理n级优先是非常流行的。 关于表达式的分析还包括表达式语义的分析,如类型检查转换、函数调用分析等各种操作。,2019/8/28,2、语句分析,代码的表示:表达式首先被编译为分析树然后转化为dag。每个函数的dag在代码表中被串起来,代码表表示了函数的代码。 code结构: struct code enum Blockbeg, Blockend, Local, Address, Defpoint, Label, Start, Gen, Jump, Switch kind; Code prev, next; union

21、u; ,2019/8/28,语句的识别: void statement(int loop, Swtch swp, int lev) float ref = refinc; if (Aflag = 2 ,2019/8/28,if语句的识别: if expression = 0 goto L statement1 goto L+1 L: statement2 L1: static void ifstmt(int lab, int loop, Swtch swp, int lev) t = gettok(); expect(); /判断if后的( definept(NULL); walk(condi

22、tional(), 0, lab); /包含listnode函数生成dag并加入 refinc /= 2.0; /森林,把入口加入代码表.同时根 statement(loop, swp, lev); /据接过设置flab,tlab if (t = ELSE) branch(lab + 1); t = gettok(); definelab(lab); statement(loop, swp, lev); if (findlabel(lab + 1)-ref) definelab(lab + 1); else definelab(lab); ,2019/8/28,在循环、switch、goto语

23、句中都用到了标号 和跳转,标号使通过definelab函数定义的, 而跳转通过branch函数生成。 除语句识别外,还有声明的识别。声明的识别非常复杂,c语言中声明的形式很多,处理时大量的相互递归调用。 经过前端的分析后,将源程序转化为dag,并添加进代码表。,2019/8/28,3、小结,六、中间代码生成,编译器的后端通过function接口函数调用gencode和emitcode来遍历代码表。 walk和listnodes函数操作处理dag森林。 newnode函数为节点分配内存并用它的参数只来初始化节点的域。 listnode还负责删除公共子表达式。,2019/8/28,1、构建节点,N

24、ode listnodes(Tree tp, int tlab, int flab) Node p = NULL, l, r; int op; if (tp = NULL) return NULL; if (tp-node) /node标识listnode访问过的树 return tp-node; if (isarray(tp-type) op = tp-op + sizeop(voidptype-size); else op = tp-op + sizeop(tp-type-size); switch (generic(tp-op) tpnode p; return p; ,2019/8/2

25、8,2、控制流,最简单的一元和二元操作加入结点表,但是并不会出现在根中。赋值等操作可以用这种情况解决。 要改变控制流需要跳转。 case JUMP: l = listnodes(tp-kids0, 0, 0); list(newnode(JUMP+V, l, NULL, NULL); reset(); break;,2019/8/28,static void list(Node p) if (p ,2019/8/28,forest是一个循环链表,不为空则指向链表最后一个节点,为空则将其初始化,link域可以表示根结点。,case LT: /LT代表大于转移,是接口dag标识符 l = list

26、nodes(tp-kids0, 0, 0); r = listnodes(tp-kids1, 0, 0); if (tlab) list(newnode(generic(tp-op) + opkind(l-op), l, r, findlabel(tlab); else if (flab) switch (generic(tp-op) case EQ: op = NE; break;case NE: op = EQ; break; case GT: op = LE; break; case LT: op = GE; break; case GE: op = LT; break; case LE

27、: op = GT; break; default: assert(0); list(newnode(op + opkind(l-op), l, r, findlabel(flab); if (forest ,2019/8/28,2019/8/28,ai&ai+bi0&ai+bi10的森林,七、代码生成器,代码生成器:为编译前端提供接口函数,接口函数用与目标机器相关的指令来实现无关的中间代码。接口函数也为临时变量指派寄存器、固定的函数单元或栈空间。 Lcc将大部分与机器无关的函数重组到一个大的与机器无关的程序中。,2019/8/28,2019/8/28,1、选择和发送指令,Lcc的指令选择器时

28、由程序lburg根据紧缩规范自动生成的,lburg是代码生成器的生成器。 lburg接收紧缩规范并产生一个用c语言编写的树分析程序,该程序为目标机器选择指令。树分析程序接受中间代码的主题树,并将它分割成与目标机器相对应的程序块,成为数覆盖。,2019/8/28,模式:ADDI(reg,con)表示如果ADDI的第一个子节点能递归的匹配reg,第二个子节点匹配con,那么该模式就在ADDI除匹配一棵树。 规则:addr:ADDI(reg,con)规定了非终结符addr与上述模式相匹配 规则:stmt: ASGNI(addr,reg)规定了ASGNI节点的每子节点递归的与addr和reg匹配非终结

29、符stmt就与该ASGNI匹配。 例: ASGNI(ADDP(INDIRP(ADDRLP(p),CNSTI(4),CNSTI(5)的覆盖,2019/8/28,2019/8/28,2019/8/28,2、发送器,Lcc发送器(emitter)的作用是为目标机器输出代码。发送器并不依赖于目标机器,由两个描述与机器相关的数据的数组驱动。Lburg为每个BURM生成一些c语言代码,用来声明并初始化这两个数组。 两个数组都是通过规则号索引。 规则生成模板数组:static char *_template; 标记与指令对应的模板,以区别子指令(如寻址指令): static char *_isinstruc

30、tion;,2019/8/28,lburg从1开始为规则编号,并通过返回规则号来报告匹配情况,这样当需要的时候就可以找到响应的模板。如果模板以一个换行符为结尾表示它是一条指令,否则就必然是某条指令的一部分,比如是一个操作数。 rc: reg “%0“ rc: con “%0“ reg: ADDI4(reg,mrc1) “?mov %c,%0nadd %c,%1n“ 1 reg: ADDP4(reg,mrc1) “?mov %c,%0nadd %c,%1n“ 1,2019/8/28,emitasm对规则结构及汇编程序代码模板进行了解释。 emitasm递归调用自身,以处理地址计算之类的子指令。

31、emitasm的遍历从一个指令开始,当递归到达为该指令提供值的指令时结束。 emitasm由emit调用,emit确保emitasm以正确的顺序来处理这些指令,这样便可以处理指令间的顺序。,2019/8/28,例如:发送器解释字符串“lw r%c,%1n” 先生成“lw r”,然后是目标寄存器的名字(通常是一个数字),接着是一个逗号。如果nts1中保存了表示非终结符addr的整数,那么递归生成p-kids1作为一个addr。最后emitasm生成一个换行符。,2019/8/28,3、寄存器的分配,从上节我们可以看出,代码发送器可以生成汇编代码,但是汇编代码中的寄存器是如何分配的? 寄存器分配包

32、括两个内容: 分配:决定哪些值占用寄存器 指派:为每个值指派特定的寄存器,2019/8/28,2019/8/28,在后端选择指令并将子指令从树中分离出来,linearize采用前序遍历分离的树,并按照最后执行的顺序链接指令。gen再将每条指令传递给ralloc函数,ralloc一般首先调用putreg来释放不再被其子节点使用的寄存器,然后调用getreg函数为自身分配一个寄存器。对于临时变量,ralloc在首次赋值的时候为它分配一个寄存器,在最后一次使用的时候释放该机存器。,2019/8/28,寄存器状态的跟踪 unsigned freemask2; unsigned usedmask2; 用

33、掩码表示寄存器的状态 对于寄存器r: int n = r-set; r-mask & freemaskn 为0表示寄存器忙,2019/8/28,寄存器分配: 寄存器分配器对森林进行三遍扫描: 第一遍对所有使用临时变量的节点建立一个表。该列表指名了临时变量节点的最后一次使用。 第二遍对森林的扫描删去一些用于寄存器复制的指令,把计算源寄存器的表达式重定向,使用目的寄存器。 最后一遍扫描为每个节点分配寄存器。,2019/8/28,寄存器的溢出: 寄存器溢出是指寄存器分配器用完寄存器时需要生成代码来空出一个忙寄存器,将其值存储回存储器中,并将那些未被处理的使用该寄存器的节点替换成存储器结点。 存储器分

34、配用完时最有选择是把最远使用的寄存器存回存储器。这类似于操作系统中的内存调度,原理相同。,2019/8/28,八、总结,lcc是构造编译器的一种方法。在设计过程中对数以百计的技术策略进行了选择,这些策略很多都是可行的方法。 lcc中运用了许多编程技巧,使的很多方法得以巧妙的实现,减小了代码的体积。 lcc中的各个部分还可单独拿出做其他的应用,如语法分析可用于处理电子数据表,lburg可处理各种树的模式匹配问题。,2019/8/28,1、数据结构,由于lcc的共享数据结构不多,因此可以很好的处理前端与代码生成之间的数据结构共享。 这样也有不足之处:比较其它简单的设计方法,这些结构更为复杂。 有些

35、人认为是c语言导致这种复杂性,用定义单独结构的方法可以减少这些复杂程度。例如用面向对象语言将结构分割。,2019/8/28,2、接口,lcc剔除了许多冗余部分并做了简单性假定,一次是的代码很紧凑,但是这些假定限制了接口在其他语言和机器上的应用能力。 lcc接口假定符号和无符号整数以及长整数都具有相同的长度。假定所有指针表示相同。,2019/8/28,3、语法和语义分析,lcc的语法和语义分析穿插进行。 lcc采用一遍扫描的策略,与AST相比她的开销更小,速度更快。,2019/8/28,4、代码生成和优化,代码生成需要综合平衡各种因素。功能强大的优化器可以产生更好的代码。但是他的速度太慢了。 就每棵树来说,lcc的指令选择是最佳的。但相邻数的代码边界处就差一些。lcc可以在最后使用窥孔优化解决。 lcc的寄存分配器比较原始,目前可以采用图的着色方法分配能力更为出色,但这样做会使代码多出很多。,2019/8/28,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1