自己动手开发编译器(一)编译器的模块化工程

摘要:
而编译器的模块划分得越清晰,工作就越简单。在今天如果一种语言还允许显式指定某个变量是寄存器变量,就会干扰寄存器分配模块的设计。除了简化设计之外,将编译器的各个阶段模块化还有更大的价值。将编译器内部结构和中间结果暴露给用户是必然的趋势。应该说现代编译器研究的工作重点是编译器的后端,因为前端的技术已经相对非常成熟。

本系列的第一篇,我想概述一下编译器的构造,同时帮助大家了解编译器中各个组成部分的用途。想必大家看别的编译原理书籍,大都在第一章或者序言之类的地方,将编译器分成许多模块,然后每一个模块负责编译的特定阶段,最后串起来组成完整的编译器。比如下面这张图就是虎书(Modern Compiler by Andrew W. Appel)第一章中出现的编译器阶段示意图:

img

那么,为什么要将编译器拆成一个个阶段,一个个模块呢?答案是,为了更加容易设计和理解。一个完成编译器怎么也算是一项大工程,如果不将其分解,将是非常难以编写和维护的。而编译器的模块划分得越清晰,工作就越简单。比如在词法分析阶段将输入的字符流转化成单词(token)流,就大大减少了语法分析阶段需要判断的输入种类,在简化设计的同时还有助于提高性能。此外模块化还将编译器各个阶段的工作尽量独立开。比如编译器可以进行与具体CPU无关的优化,也可以针对某种CPU进行特定的优化,都可以分别独立进行而不用重新设计整个系统。

有个事实可能会令人感到惊讶,编译器的各个阶段和模块如何设计,甚至跟这种编程语言的语法有关。比如早期的编程语言Fortran,在设计当初人们还没有掌握现在这么多编译原理的理论,它的语法就不能像当今的语言一样清晰地分成词法分析和语法分析等阶段。因为Fortran的语法并不包含可以用自动机独立处理的词法结构。于是,Fortran语言的编译器在语法分析方面就比较繁杂。有一些历史背景的语言也可能会具有这种复杂化的语法,比如Visual Basic也属于不能用独立的、基于自动机的词法分析器来扫描的语言。因此VB的语法分析器就要比诸如C#等思路较新语言的难写很多。另外一个例子是早期的Pascal语言和某些C语言允许用一些特定的语法来指定某变量为寄存器变量(也许在近期的Delphi中仍然存在,求证)。这是因为当时还没有非常有效的寄存器分配算法,需要程序员凭自己的经验来决定。在今天如果一种语言还允许显式指定某个变量是寄存器变量,就会干扰寄存器分配模块的设计。综上所述我想给各位未来的编译器设计师们一个建议,好好设计你们的语法,就能大大简化编译器的设计!

除了简化设计之外,将编译器的各个阶段模块化还有更大的价值。原先我们认为编译器只要把源文件编译成最终的目标代码就好了。但是随着各种各样的开发工具出现——编辑器、自动完成、调试器、重构工具、测试覆盖率检测、性能剖析器…… 人们发现编译器编译过程中,各个阶段产生的结果都可能是非常有价值的。将编译器内部结构和中间结果暴露给用户是必然的趋势。比如Visual Studio下一代产品中将提供的Compiler as a Service特性,其做法就是将编译器的内部模块暴露给用户成为一种服务。我举几个例子可以让大家看到编译器模块的输出有哪些可能的用途:

编译器的阶段

产生的结果

用途

词法分析

单词流

语法高亮

语法分析

抽象语法树

语法高亮;代码格式化;代码折叠

语义分析

带类型信息和符号表的抽象语法树

重命名;重构;代码自动生成;代码自动改写

数据流分析

控制流图、冲突图

编辑后继续运行(Edit and Continue)

这里我只是举几个简单的例子,以上结果的用途当然不会仅限于此。我相信将编译器的内部模块暴露给用户还能产生无数有趣和有价值的应用。

上述编译器的各个阶段还可以根据其用途分成两个大阶段:词法分析、语法分析和语义分析重点在处理编程语言的符号系统上,统称为编译器的前端(front-end),而中间代码生成、规范化、指令选择、控制流分析、数据流分析、寄存器分配、指令流出、汇编、连结等着重处理代码计算逻辑的阶段统称为编译的后端(back-end)。应该说现代编译器研究的工作重点是编译器的后端,因为前端的技术已经相对非常成熟。但是前端的技术对我们日常开发来讲可能更有机会用到,而且通常更具趣味。所以我也会花较多时间在前端技术上。当大家完成一种编译器的前端后,有几种实现后端的选择:

  1. 使用CLR或Java虚拟机作为后端。因为这些大型虚拟机的抽象程度极高,这种方法是最容易的。非常适合动态语言和脚本。
  2. 采用可靠的开源或商业后端框架。比如著名的LLVM(http://llvm.org/)。这样可以直接利用LLVM的性能优化成果,以及跨平台等特性。
  3. 自己实现后端。要做的事情比较多,但更有助于理解翻译和优化代码的技术。
  4. 解释执行。不解释。。。

我将展示的例子miniSharp虽然是C#语法的子集,但是并没有限定必须运行在CLR之上。我会将它设定成一个可重定向的语言,即可以针对多种后端。这样就可以用一个例子演示尽可能多的技术。我也会视我自己的能力范围和工作进度动态调整本系列的内容。也希望大家继续关注VBF.Compilers项目(https://github.com/Ninputer/VBF)和我的微博(http://weibo.com/ninputer)!敬请期待下一篇。

免责声明:文章转载自《自己动手开发编译器(一)编译器的模块化工程》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇DrawText的使用Android的一些常用命令提示符(cmd)指令[转]下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

数据产品-数据埋点-02

1.埋点方式 1.1客户端埋点   1.1.1代码埋点 代码埋点主要有app研发手动在程序中写下代码进行统计,通过触发某个动作后程序自动发送数据。 优点:具有很强得灵活性,可以控制发散得时间和发散方式等。 缺点:人力成本和维护成本太高,需要以来app发版生效   1.1.2可视化埋点 可视化埋点以前端可视化的方式记录前端设置页面元素与对其操作的关系然后以后...

C++ this指针

1. this指针的用处:   一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各...

Fortran入门:Windows平台的Fortran编译器安装和使用

因为课程需要,今年开始学习FORTRAN语言。之前学校的计算概论用的是C,后来又学了C++和Python作为面向对象的工具,数值计算方面主要通过学校的许可证用的MATLAB。因为专业侧重数值模拟和反演问题,对于FORTRAN这一门上古的数值计算语言早有耳闻,在学习Scientific Computing的时候也经常讲到一些原本是基于FORTRAN优化的子程...

iOS Xcode中LLVM编译器

Xcode中Apple LLVM编译器 一、LLVM 介绍 参考:百度百科https://baike.baidu.com/item/LLVM/3598690?fr=aladdin 可参考苹果官方文档更具体 https://developer.apple.com/library/content/documentation/CompilerTools/Conc...

打造你的前端神器-webstorm11

说起前端编辑器,用过dw,sublime,hbuilder,webstorm也不陌生,之前的版本8有用过一下,但是觉得比sublime重量太多,但是随着后来用node的开始,发现需要打造个web前端神器才能满足我的需求,于是乎重拾webstorm,目前发现11是新的版本,对node,npm支持性更加好。 ok,开始我们的前端神器 这里提供下载地址 http...

一款优秀的前端JS框架—AngularJS

  前  言 AngularJS 是一个 JavaScript 框架。它可通过 <script> 标签添加到 HTML 页面。 AngularJS 通过 指令 扩展了 HTML,且通过 表达式 绑定数据到 HTML。 AngularJS 是一个 JavaScript 框架。它是一个以 JavaScript 编写的库。 AngularJS 是以...