编译器设计与实现 ——Lcc原理剖析
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
编译器设计与实现
——Lcc原理剖析
华中科技大学计算机学院 张 德
2016/4/9
一、概述
1、编译器各阶段
源程序 词法分析器
语法分析器
语义分析器
符号表管理器
中间代码生成器 代码优化器 代码生成器 目标程序
2016/4/9
错误处理器ຫໍສະໝຸດ 2、编译器各阶段的分组
前端:依赖于语言并很大程度上独立于目 标机器。一般包括语法分析、词法分析、 符号表的建立、语义分析、中间代码生成 以及相关错误处理。 后端:依赖于目标机器的阶段或某些阶段 的某些部分。一般来说,后端完成的任务 不依赖于源语言而只依赖于中间语言。主 要包括代码优化、代码生成以及相关的错 误处理和符号表操作。
struct symbol { char *name; //名称 int scope; //作用域 Coordinate src; //在源程序中位置 Symbol up; //连接符号表中上一个符号 List uses; //可保存一个Coordinate列表,表示使用情况 int sclass; //扩展存储类型 <symbol flag> //符号标记 Type type; //如变量、函数、常量、结构或联合等信息 float ref; //被引用的粗略次数 union { //联合u为标号、结构、联合、枚举、常量、全局 <appendent info> //和静态变量提供附加信息 } u; // Xsymbol x; //由后端处理,如为变量分配寄存器 <debugger extension>// 为调试器产生数据信息 }
2016/4/9
5、函数
前端将函数编译为私有数据结构。将函数 的任意部分传递给后端之前,前端必须先 对每个函数进行完整的分析。 函数的处理:function函数包括前端过程
gencode遍历前端的私有数据结构,将dag的每个 森林传给后端过程gen。gen选择代码,在dag上添 加注释并将返回一个dag指针。gencode还可以调 用local宣告新的局不变量。前端过程emitcode再 次遍历,将gen返回的指针传递给emit函数发送代 码。
2016/4/9
三、代码生成接口
这一章内容定义了与目标机器无关的前端和 与目标机器相关的后端之间的接口。 Lcc接口包括一些共享数据结构、18个函数和 包括36个操作符的语言。该语言用于将可执 行代码从源程序生成dag(有向无环图)。 共享数据结构可供前后端共享,但某些域为 一端私有。symbol就是一个共享数据结构。
2016/4/9
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") ……
buffer+
MAXLINE
buffer+
MAXLINE+MAXSIZE
2016/4/9
2、单词识别
部分文法:
定义:
ID FCON ICON SCON INCR DECR DEREF …… 标识符 浮点常量 整型常量 …
token: keyword identifier constant operator punctuator punctuator: one of [ ] ( ) { } * , : = ; …
2016/4/9
2016/4/9
2016/4/9
3、dag操作
举例: int i, *p; f() { i = *p++;}
2 INDIRP
5 ASGNP
8 ASGNI
1 ADDRGP p
4 ADDP
6 ADDRGP i
7 INDIRI
3 CNSTI 4
2016/4/9
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给后端
2016/4/9
3、dag操作
op域存放dag操作符。 dag操作符后缀表示操作数类型: enum { F=FLOAT, I=INT, U=UNSIGNED, P=POINTER, V=VOID, B=STRUCT }; 如CNST,有变体CNSTI、CNSTU、CNSTP等。 CNST = 1<<4; CNSTC=CNST+F; CNSTI=CNST+I; … …
2016/4/9
可以通过查表完成,也可以通过硬编码方式识别。 例如,当起始小写字母为i时由gettok函数中switch语句的 case ‘i’处理。
case 'i': if (rcp[0] == 'f'&& !(map[rcp[1]] &(DIGIT|LETTER))){ cp = rcp + 1; return IF; } if (rcp[0] == 'n'&& rcp[1] == 't' && !(map[rcp[2]]&(DIGIT|LETTER))) { cp = rcp + 2; tsym = inttype->u.sym; return INT; } goto id;
2016/4/9
3、dag操作
可执行代码用dag来描述。函数体是用dag 组成的序列或森林。每个dag都可以同过 gen函数传给后端。 dag节点
struct node { short op; short count; Symbol syms[3]; Node kids[2]; Node link; Xnode x; };
2016/4/9
四、词法分析
词法分析器读入源程序,产生语言的基本 词法单元。 例:*prt = 56;
单词编码 ‘*’ ID “prt” 附加值
‘=’ ICON ‚56‛
2016/4/9
1、输入
\n
cp
当limit-cp小于某一个固定值时,调用fillbuf 函数填充buffer
limit
buffer
2016/4/9
2、符号表的表示
extern extern extern extern extern extern Table Table Table Table Table Table constants; externals; globals; identifiers; labels; types;
2016/4/9
3、符号表举例
int x, y; f(int x, int a){ int b; y = x + a*b; if (y < 5){ int a; y = x + a*b; } }
2016/4/9
3 0 0
0
4
0 5
0
6 0
2016/4/9
a
b
x
y
4、符号表的相关操作
查找和建立标识符
2016/4/9
6、上行调用
前段调用后端以执行代码生成和发送。后端调 用前端完成输出、分配存储空间、查询类型等功 能。上行调用即后端调用前端。 allocate分配空间,保证对齐方式符合机器多 数 类型 newnode分配新的dag节点 newconst符号表中创建新的常量 newtemp符号表中创建新的变量 ……
2016/4/9
二、符号表
符号表是编译器保存信息的中心库,编译 器的各部分通过符号表进行交互,并访问 符号表中的数据——符号。 符号表把各种名字映射到符号集合。常量 、标识符和标号都是名字,不同名字有不 同的属性。 符号管理不仅要处理符号本身,还管理符 号的作用域。
2016/4/9
1、符号的表示
2016/4/9
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。
2016/4/9
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;
struct table { int level; //同symbol中scope域 Table previous; //符号表链表,指向level-1的表 struct entry { struct symbol sym; struct entry *link; } *buckets[256]; //这是一个哈希链数组,方便插入、查找 Symbol all; //指向当前及其外层所有符号列表的表头 };
2016/4/9
2、接口记录
typedef struct interface { <metrics> <interface flags> <interface functions> Xinterface x; } Interface;
lcc为每一种目标机器形成一个独有的接口实例。x 域是对interface的扩展,后段使用它存放与目标 及其相关的接口数据和函数,对后端私有。
标号:与标识符相似,但不涉及作用域 常量:这些符号保存在constants表中 产生变量:用于产生静态变量保存字符串等
Symbol install(const char * name, Table * tpp, int level, int arena); Symbol lookup(const char *name, Table tp);
2016/4/9
3、关键字的识别
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; }
——Lcc原理剖析
华中科技大学计算机学院 张 德
2016/4/9
一、概述
1、编译器各阶段
源程序 词法分析器
语法分析器
语义分析器
符号表管理器
中间代码生成器 代码优化器 代码生成器 目标程序
2016/4/9
错误处理器ຫໍສະໝຸດ 2、编译器各阶段的分组
前端:依赖于语言并很大程度上独立于目 标机器。一般包括语法分析、词法分析、 符号表的建立、语义分析、中间代码生成 以及相关错误处理。 后端:依赖于目标机器的阶段或某些阶段 的某些部分。一般来说,后端完成的任务 不依赖于源语言而只依赖于中间语言。主 要包括代码优化、代码生成以及相关的错 误处理和符号表操作。
struct symbol { char *name; //名称 int scope; //作用域 Coordinate src; //在源程序中位置 Symbol up; //连接符号表中上一个符号 List uses; //可保存一个Coordinate列表,表示使用情况 int sclass; //扩展存储类型 <symbol flag> //符号标记 Type type; //如变量、函数、常量、结构或联合等信息 float ref; //被引用的粗略次数 union { //联合u为标号、结构、联合、枚举、常量、全局 <appendent info> //和静态变量提供附加信息 } u; // Xsymbol x; //由后端处理,如为变量分配寄存器 <debugger extension>// 为调试器产生数据信息 }
2016/4/9
5、函数
前端将函数编译为私有数据结构。将函数 的任意部分传递给后端之前,前端必须先 对每个函数进行完整的分析。 函数的处理:function函数包括前端过程
gencode遍历前端的私有数据结构,将dag的每个 森林传给后端过程gen。gen选择代码,在dag上添 加注释并将返回一个dag指针。gencode还可以调 用local宣告新的局不变量。前端过程emitcode再 次遍历,将gen返回的指针传递给emit函数发送代 码。
2016/4/9
三、代码生成接口
这一章内容定义了与目标机器无关的前端和 与目标机器相关的后端之间的接口。 Lcc接口包括一些共享数据结构、18个函数和 包括36个操作符的语言。该语言用于将可执 行代码从源程序生成dag(有向无环图)。 共享数据结构可供前后端共享,但某些域为 一端私有。symbol就是一个共享数据结构。
2016/4/9
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") ……
buffer+
MAXLINE
buffer+
MAXLINE+MAXSIZE
2016/4/9
2、单词识别
部分文法:
定义:
ID FCON ICON SCON INCR DECR DEREF …… 标识符 浮点常量 整型常量 …
token: keyword identifier constant operator punctuator punctuator: one of [ ] ( ) { } * , : = ; …
2016/4/9
2016/4/9
2016/4/9
3、dag操作
举例: int i, *p; f() { i = *p++;}
2 INDIRP
5 ASGNP
8 ASGNI
1 ADDRGP p
4 ADDP
6 ADDRGP i
7 INDIRI
3 CNSTI 4
2016/4/9
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给后端
2016/4/9
3、dag操作
op域存放dag操作符。 dag操作符后缀表示操作数类型: enum { F=FLOAT, I=INT, U=UNSIGNED, P=POINTER, V=VOID, B=STRUCT }; 如CNST,有变体CNSTI、CNSTU、CNSTP等。 CNST = 1<<4; CNSTC=CNST+F; CNSTI=CNST+I; … …
2016/4/9
可以通过查表完成,也可以通过硬编码方式识别。 例如,当起始小写字母为i时由gettok函数中switch语句的 case ‘i’处理。
case 'i': if (rcp[0] == 'f'&& !(map[rcp[1]] &(DIGIT|LETTER))){ cp = rcp + 1; return IF; } if (rcp[0] == 'n'&& rcp[1] == 't' && !(map[rcp[2]]&(DIGIT|LETTER))) { cp = rcp + 2; tsym = inttype->u.sym; return INT; } goto id;
2016/4/9
3、dag操作
可执行代码用dag来描述。函数体是用dag 组成的序列或森林。每个dag都可以同过 gen函数传给后端。 dag节点
struct node { short op; short count; Symbol syms[3]; Node kids[2]; Node link; Xnode x; };
2016/4/9
四、词法分析
词法分析器读入源程序,产生语言的基本 词法单元。 例:*prt = 56;
单词编码 ‘*’ ID “prt” 附加值
‘=’ ICON ‚56‛
2016/4/9
1、输入
\n
cp
当limit-cp小于某一个固定值时,调用fillbuf 函数填充buffer
limit
buffer
2016/4/9
2、符号表的表示
extern extern extern extern extern extern Table Table Table Table Table Table constants; externals; globals; identifiers; labels; types;
2016/4/9
3、符号表举例
int x, y; f(int x, int a){ int b; y = x + a*b; if (y < 5){ int a; y = x + a*b; } }
2016/4/9
3 0 0
0
4
0 5
0
6 0
2016/4/9
a
b
x
y
4、符号表的相关操作
查找和建立标识符
2016/4/9
6、上行调用
前段调用后端以执行代码生成和发送。后端调 用前端完成输出、分配存储空间、查询类型等功 能。上行调用即后端调用前端。 allocate分配空间,保证对齐方式符合机器多 数 类型 newnode分配新的dag节点 newconst符号表中创建新的常量 newtemp符号表中创建新的变量 ……
2016/4/9
二、符号表
符号表是编译器保存信息的中心库,编译 器的各部分通过符号表进行交互,并访问 符号表中的数据——符号。 符号表把各种名字映射到符号集合。常量 、标识符和标号都是名字,不同名字有不 同的属性。 符号管理不仅要处理符号本身,还管理符 号的作用域。
2016/4/9
1、符号的表示
2016/4/9
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。
2016/4/9
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;
struct table { int level; //同symbol中scope域 Table previous; //符号表链表,指向level-1的表 struct entry { struct symbol sym; struct entry *link; } *buckets[256]; //这是一个哈希链数组,方便插入、查找 Symbol all; //指向当前及其外层所有符号列表的表头 };
2016/4/9
2、接口记录
typedef struct interface { <metrics> <interface flags> <interface functions> Xinterface x; } Interface;
lcc为每一种目标机器形成一个独有的接口实例。x 域是对interface的扩展,后段使用它存放与目标 及其相关的接口数据和函数,对后端私有。
标号:与标识符相似,但不涉及作用域 常量:这些符号保存在constants表中 产生变量:用于产生静态变量保存字符串等
Symbol install(const char * name, Table * tpp, int level, int arena); Symbol lookup(const char *name, Table tp);
2016/4/9
3、关键字的识别
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; }