编译器设计与实现.ppt_第1页
编译器设计与实现.ppt_第2页
编译器设计与实现.ppt_第3页
编译器设计与实现.ppt_第4页
编译器设计与实现.ppt_第5页
已阅读5页,还剩60页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

编译器设计与实现,Lcc原理剖析,华中科技大学计算机学院 张 德,2019/6/17,1、编译器各阶段,2019/6/17,2、编译器各阶段的分组,前端:依赖于语言并很大程度上独立于目标机器。一般包括语法分析、词法分析、符号表的建立、语义分析、中间代码生成以及相关错误处理。 后端:依赖于目标机器的阶段或某些阶段的某些部分。一般来说,后端完成的任务不依赖于源语言而只依赖于中间语言。主要包括代码优化、代码生成以及相关的错误处理和符号表操作。,2019/6/17,二、符号表,符号表是编译器保存信息的中心库,编译器的各部分通过符号表进行交互,并访问符号表中的数据符号。 符号表把各种名字映射到符号集合。常量、标识符和标号都是名字,不同名字有不同的属性。 符号管理不仅要处理符号本身,还管理符号的作用域。,2019/6/17,1、符号的表示,struct symbol char *name; /名称 int scope; /作用域 Coordinate src; /在源程序中位置 Symbol up; /连接符号表中上一个符号 List uses; /可保存一个Coordinate列表,表示使用情况 int sclass; /扩展存储类型 /符号标记 Type type; /如变量、函数、常量、结构或联合等信息 float ref; /被引用的粗略次数 union /联合u为标号、结构、联合、枚举、常量、全局 /和静态变量提供附加信息 u; / Xsymbol x; /由后端处理,如为变量分配寄存器 / 为调试器产生数据信息 ,2019/6/17,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、REGISTER、STATIC或EXTERN等 首字母大写的类型表示全小写类型的指针,如Symbol。,2019/6/17,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 struct symbol sym; struct entry *link; *buckets256; /这是一个哈希链数组,方便插入、查找 Symbol all; /指向当前及其外层所有符号列表的表头 ;,2019/6/17,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/6/17,2019/6/17,4、符号表的相关操作,查找和建立标识符 Symbol install(const char * name, Table * tpp, int level, int arena); Symbol lookup(const char *name, Table tp); 标号:与标识符相似,但不涉及作用域 常量:这些符号保存在constants表中 产生变量:用于产生静态变量保存字符串等,2019/6/17,三、代码生成接口,这一章内容定义了与目标机器无关的前端和与目标机器相关的后端之间的接口。 Lcc接口包括一些共享数据结构、18个函数和包括36个操作符的语言。该语言用于将可执行代码从源程序生成dag(有向无环图)。 共享数据结构可供前后端共享,但某些域为一端私有。symbol就是一个共享数据结构。,2019/6/17,1、类型度量,typedef struct metrics unsigned char size, align, outofline; Metrics; size:类型的大小; align:对齐字节数; outofline:控制相关类型的常量的放置。为1时,不出现在dag中,存于静态变量中。 Metrics charmetric; Metrics shortmetric; Metrics intmetric; Metrics floatmetric; Metrics doublemetric; Metrics structmetric;,2019/6/17,2、接口记录,typedef struct interface Xinterface x; Interface; lcc为每一种目标机器形成一个独有的接口实例。x域是对interface的扩展,后段使用它存放与目标及其相关的接口数据和函数,对后端私有。,2019/6/17,3、dag操作,可执行代码用dag来描述。函数体是用dag组成的序列或森林。每个dag都可以同过gen函数传给后端。 dag节点 struct node short op; short count; Symbol syms3; Node kids2; Node link; Xnode x; ;,2019/6/17,3、dag操作,op域存放dag操作符。 dag操作符后缀表示操作数类型: enum F=FLOAT, I=INT, U=UNSIGNED, P=POINTER, V=VOID, B=STRUCT ; 如CNST,有变体CNSTI、CNSTU、CNSTP等。 CNST = 14; CNSTC=CNST+F; CNSTI=CNST+I; ,2019/6/17,2019/6/17,2019/6/17,3、dag操作,举例: int i, *p; f() i = *p+;,2019/6/17,4、接口标志,unsigned little_endian:1; 目标机器存储是低位优先还是高位优先 unsigned mulops_calls:1; 有硬件实现乘、除和求余,mulopes_calls应等于0 unsigned wants_callb:1; 通知前端产生CALLB节点以调用返回结构的函数 unsigned wants_argb:1; 通知前端节点产生ARGB节点以产生结构参数 unsigned left_to_right:1; 告诉前端按照从左到右的顺序计算和提交参数给后端 unsigned wants_dag:1; 告诉前端传递dag给后端,2019/6/17,5、函数,前端将函数编译为私有数据结构。将函数的任意部分传递给后端之前,前端必须先对每个函数进行完整的分析。 函数的处理:function函数包括前端过程gencode遍历前端的私有数据结构,将dag的每个森林传给后端过程gen。gen选择代码,在dag上添加注释并将返回一个dag指针。gencode还可以调用local宣告新的局不变量。前端过程emitcode再次遍历,将gen返回的指针传递给emit函数发送代码。,2019/6/17,6、上行调用,前段调用后端以执行代码生成和发送。后端调用前端完成输出、分配存储空间、查询类型等功能。上行调用即后端调用前端。 allocate分配空间,保证对齐方式符合机器多 数类型 newnode分配新的dag节点 newconst符号表中创建新的常量 newtemp符号表中创建新的变量 ,2019/6/17,四、词法分析,词法分析器读入源程序,产生语言的基本词法单元。 例:*prt 56;,2019/6/17,1、输入,2019/6/17,n,cp,limit,当limit-cp小于某一个固定值时,调用fillbuf函数填充buffer,2、单词识别,部分文法: token: keyword identifier constant operator punctuator punctuator: one of ( ) * , : = ; ,2019/6/17,定义: ID 标识符 FCON 浮点常量 ICON 整型常量 SCON INCR DECR DEREF ,emun #define xx(a,b,c,d,e,f,g) a=b, #define yy(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“) xx(POINTER, 7, 0, 0, 0, 0, “pointer“) xx(VOID, 8, 0, 0, 0, CHAR, “void“) xx(STRUCT, 9, 0, 0, 0, CHAR, “struct“) ,2019/6/17,3、关键字的识别,可以通过查表完成,也可以通过硬编码方式识别。 例如,当起始小写字母为i时由gettok函数中switch语句的case i处理。,2019/6/17,case i: if (rcp0 = f,4、标识符识别,case h: case j: case k: case m: case n: case o: case p: case q: case 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/6/17,assert(cp = rcp); token = (char *)rcp - 1; while (map*rcp,检查是否需要填充buffer,5、其他,另外还有: 数字识别 字符常量和字符串识别 都是有gettok函数实现,实现方法相似。 词法分析器可以有象LEX这样的工具实现。Lcc手工实现了词法分析器,体积更小。,2019/6/17,五、语法分析,根据语言的文法分析,以确认输入是否符合语言规则,并建立输入源程序的内部表示。 Lcc采用递归下降的语法分析。 语法分析以形式语言理论为基础,采取语法制导翻译方法,处理程序中的错误。,2019/6/17,1、表达式,表达式的表示: (a+b)+b*(a+b),2019/6/17,表达式的分析: 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() 同理得分析函数term是:factor();while(t = *) t = gettok(); factor(),2019/6/17,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 = IF, ID, 0 ; Tree p = expr2(); if (t = = | (prect = 6 else return p ,2019/6/17,条件表达式: 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/6/17,另有二元表达式、一元表达式、后缀表达式和基本表达式。 表达式分析多是用递归和大量switch语句实现。 在编译领域用一个分析函数代替n个函数处理n级优先是非常流行的。 关于表达式的分析还包括表达式语义的分析,如类型检查转换、函数调用分析等各种操作。,2019/6/17,2、语句分析,代码的表示:表达式首先被编译为分析树然后转化为dag。每个函数的dag在代码表中被串起来,代码表表示了函数的代码。 code结构: struct code enum Blockbeg, Blockend, Local, Address, Defpoint, Label, Start, Gen, Jump, Switch kind; Code prev, next; union u; ,2019/6/17,语句的识别: void statement(int loop, Swtch swp, int lev) float ref = refinc; if (Aflag = 2 ,2019/6/17,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(conditional(), 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/6/17,在循环、switch、goto语句中都用到了标号 和跳转,标号使通过definelab函数定义的, 而跳转通过branch函数生成。 除语句识别外,还有声明的识别。声明的识别非常复杂,c语言中声明的形式很多,处理时大量的相互递归调用。 经过前端的分析后,将源程序转化为dag,并添加进代码表。,2019/6/17,3、小结,六、中间代码生成,编译器的后端通过function接口函数调用gencode和emitcode来遍历代码表。 walk和listnodes函数操作处理dag森林。 newnode函数为节点分配内存并用它的参数只来初始化节点的域。 listnode还负责删除公共子表达式。,2019/6/17,1、构建节点,Node 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/6/17,2、控制流,最简单的一元和二元操作加入结点表,但是并不会出现在根中。赋值等操作可以用这种情况解决。 要改变控制流需要跳转。 case JUMP: l = listnodes(tp-kids0, 0, 0); list(newnode(JUMP+V, l, NULL, NULL); reset(); break;,2019/6/17,static void list(Node p) if (p ,2019/6/17,forest是一个循环链表,不为空则指向链表最后一个节点,为空则将其初始化,link域可以表示根结点。,case LT: /LT代表大于转移,是接口dag标识符 l = listnodes(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: op = GT; break; default: assert(0); list(newnode(op + opkind(l-op), l, r, findlabel(flab); if (forest ,2019/6/17,2019/6/17,ai&ai+bi0&ai+bi10的森林,七、代码生成器,代码生成器:为编译前端提供接口函数,接口函数用与目标机器相关的指令来实现无关的中间代码。接口函数也为临时变量指派寄存器、固定的函数单元或栈空间。 Lcc将大部分与机器无关的函数重组到一个大的与机器无关的程序中。,2019/6/17,2019/6/17,1、选择和发送指令,Lcc的指令选择器时由程序lburg根据紧缩规范自动生成的,lburg是代码生成器的生成器。 lburg接收紧缩规范并产生一个用c语言编写的树分析程序,该程序为目标机器选择指令。树分析程序接受中间代码的主题树,并将它分割成与目标机器相对应的程序块,成为数覆盖。,2019/6/17,模式:ADDI(reg,con)表示如果ADDI的第一个子节点能递归的匹配reg,第二个子节点匹配con,那么该模式就在ADDI除匹配一棵树。 规则:addr:ADDI(reg,con)规定了非终结符addr与上述模式相匹配 规则:stmt: ASGNI(addr,reg)规定了ASGNI节点的每子节点递归的与addr和reg匹配非终结符stmt就与该ASGNI匹配。 例: ASGNI(ADDP(INDIRP(ADDRLP(p),CNSTI(4),CNSTI(5)的覆盖,2019/6/17,2019/6/17,2019/6/17,2、发送器,Lcc发送器(emitter)的作用是为目标机器输出代码。发送器并不依赖于目标机器,由两个描述与机器相关的数据的数组驱动。Lburg为每个BURM生成一些c语言代码,用来声明并初始化这两个数组。 两个数组都是通过规则号索引。 规则生成模板数组:static char *_template; 标记与指令对应的模板,以区别子指令(如寻址指令): static char *_isinstruction;,2019/6/17,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/6/17,emitasm对规则结构及汇编程序代码模板进行了解释。 emitasm递归调用自身,以处理地址计算之类的子指令。 emitasm的遍历从一个指令开始,当递归到达为该指令提供值的指令时结束。 emitasm由emit调用,emit确保emitasm以正确的顺序来处理这些指令,这样便可以处理指令间的顺序。,2019/6/17,例如:发送器解释字符串“lw r%c,%1n” 先生成“lw r”,然后是目标寄存器的名字(通常是一个数字),接着是一个逗号。如果nts1中保存了表示非终结符addr的整数,那么递归生成p-kids1作为一个addr。最后emitasm生成一个换行符。,2019/6/17,3、寄存器的分配,从上节我们可以看出,代码发送器可以生成汇编代码,但是汇编代码中的寄存器是如何分配的? 寄存器分配包括两个内容: 分配:决定哪些值占用寄存器 指派:为每个值指派特定的寄存器,2019/6/17,2019/6/17,在后端选择指令并将子指令从树中分离出来,linearize采用前序遍历分离的树,并按照最后执行的顺序链接指令。gen再将每条指令传递给ralloc函数,ralloc一般首先调用putreg来释放不再被其子节点使用的寄存器,然后调用getreg函数为自身分配一个寄存器。对于临时变量,ralloc在首次赋值的时候为它分配一个寄存器,在最后一次使用的时候释放该机存器。,2019/6/17,寄

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论