You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

18 KiB

42到这里我们的收获和未尽的工作有哪些

你好,我是宫文学。

到今天这节课为止,我们已经把这门课程的主要内容都学完了。感谢你一路的坚持!

所以在今天这节课我想做一个简单的总结。我想先带你回顾一下我们一起闯过的那些技术关卡以及取得的成果。接下来我还想梳理一下我们尚未完成的工作也对我们后续作为开源项目的PlayScript语言做一下规划。

在这个过程中,你可以暂时从技术细节中解脱出来,站在一个语言的架构师的角度,一起做一些高层面的思考,锻炼一下架构思维。

首先,我们简单总结一下当前已经完成的工作。

当前的收获

到目前为止我们在40多节课的内容里塞进了大量的知识点。我们按课程顺序来梳理一下。

基础篇:三大关卡

在第一部分基础篇中,我带你连续闯了三个关卡。

第一个关卡,是编译器前端技术,包括词法分析、语法分析和语义分析技术。

在词法和语法分析方面我们这门课没有带你进入相关算法的迷魂阵而是带你去掌握最佳实践。一方面这些算法我在《编译原理之美》课程中已经讲过了。另一方面如果你只是写个编译器而不是写个像Yacc、Antlr这样的编译器生成工具其实不需要深究那些算法只要大概明白原理就行了。

即使是这样对于递归下降中的左递归问题这样偏理论性的知识点很多同学免不了还是有疑惑。比如有同学会问我我在课程里用到的有些文法为什么仍然有一些是左递归的呢这里其实涉及到PEG文法的一个知识点我会在后面的加餐里讲一下PEG。其实并不是所有的左递归都没有办法处理。关于左递归直到现在仍然是做算法研究的人感兴趣的一个领域。

在语义分析方面,我们体会了如何建立符号表、如何做引用消解、如何检查一些语义错误的过程,这样,你会对课本中讲到的一些抽象概念建立具象的理解。

为了编译TypeScript语言编译器在语义分析阶段最重要的功能是进行类型的处理其中的关键点又是类型的联合和窄化。在这门课里我们主要采用了集合运算的手法来处理类型。在PlayScript开源项目中我计划把这部分进一步优化让类型计算更简洁、更精准。

对很多同学来说,闯过编译器前端的这个关卡,其实已经收获满满,可以在自己的项目里大展身手了。不过,如果你喜欢钻研底层实现,显然还不会满足止步于此,那你就可以继续闯第二关。

在第二关我们实现了两个版本的字节码虚拟机。一个版本是用TypeScript实现的另一个版本使用C语言实现的。

通过这个实现过程你会了解到像Java这样的成熟语言的字节码是如何设计的又是如何实际运行的。这样在需要的时候我希望你能够敢于自己生成Java或.NET的字节码实现自己想要的软件编程功能。

并且通过实现C语言版本的虚拟机我们也能初步了解运行时的功能。特别是你要知道如果我们不能做好内存的管理系统运行的性能就会大受影响。而且通过我们多次的性能测试你应该已经对一个解释器中影响性能的因素产生了直观的理解这样你自己写程序的时候也能够进行更明智的决策。

在初步实现了字节码解释器以后,我们又进入了第三关,挑战把源代码编译成二进制的可执行程序。

为了完成第三关的任务我们必须对程序的运行机制有深入的了解包括程序运行跟硬件是什么关系跟操作系统和ABI又是什么关系。再进一步我们还需要了解CPU架构和它支持的指令集学会阅读甚至手写汇编代码。

生成汇编代码有两个关键点第一要熟悉ABI正确地维护栈桢和寄存器的状态否则程序运行时就会报segment错误第二就是要实现寄存器分配算法。你要知道不同的指令集会使用不同的寄存器并且我们在函数调用前后要保护某些寄存器。做好这些以后程序就可以充分利用寄存器飞一般地运行了。

在闯完这三关以后,你已经从前端到后端打通了技术路线。接下来在第二部分进阶篇中,我们把这条路线做了拓宽。

进阶篇:拓宽路径

怎么去做拓宽呢?我们的主线是支持更多的数据类型,包括浮点数、字符串和数组这几个基础类型,还包括自定义对象、高阶函数这种高级的类型。

为了支持这些类型,我们必须增强运行时的功能,需要设计对象的内存布局。在访问对象属性、数组成员的时候,我们也要能够正确计算出内存地址来。

进阶篇最难的部分,是**自动内存管理功能,**包括基于Arena的内存申请机制以及垃圾回收机制。而实现垃圾回收的关键点在于找到GC根并顺着GC根去找到直接引用和间接引用的对象。因此我们需要保存栈桢的布局信息、对象的元数据信息、闭包的元数据信息等各种静态信息。

如果你充分掌握了内存管理涉及的技术点,那么你在后面实现很多高级功能的时候都能用上。比如对程序做调试、支持运行时的类型判断和元编程功能,都需要用到我们提前保存的元数据信息。

学完第二部分以后,你对实现像面向对象、函数式编程等各种语言特性基本上心里有数了。在第三部分优化篇,我们就专注于解决一个问题,就是优化问题。

优化篇基于图的IR

优化是编译器最重要的功能之一。优化可以发生在全生命周期包括前端、中端、后端和运行过程中。第三部分的核心就是一个基于图的IR。这个IR被Java和JavaScript的编译器采用你可以想象一定有它的优势。

在使用这个IR的过程中我们确实发现我们很多优化的实现都变得很简单了。像公共子表达式删除我们在生成IR的过程中顺带着就能完成。还有像拷贝传播、死代码删除、值编号等这些优化实现起来也很简单。最重要的是这个IR更容易打通本地优化、全局优化和过程间优化三者的边界让代码更容易在不同的基本块中移动获取我们想要的优化效果。

并且我们还了解了基于这个IR不断地做lower直到生成LIR然后基于LIR做指令选择、寄存器分配等后端优化的完整过程。通过这一部分的学习你对于前端、中端、后端优化要做的工作就都比较清晰了。

好了,以上就是这门课中我们领略的各种技术风光,我希望你能够充分掌握,一方面这能开阔你的技术思路,另一方面这些技术也能用到你的实际项目中。

不过受限于时间我还没有把一门完整的语言完全实现完。所以我后面会把这门课的示例代码作为一个开源项目继续迭代下去并形成完整的、实用的版本。至于当前已有的基础我们就把它作为0.1版本吧!

那如果我们要实现一个实用的版本,还有哪些工作要做呢?

后续工作

第一,我们要对编译器前端做比较大的增强和重构。

首先当前我们已有的词法分析、语法分析和语义分析功能都要支持更多的特性比如除了我们已经支持的for循环和if分支语句外还有while循环、switch语句等等。

而且,我们对编译错误的处理要更加友好。你应该也感受到了,目前我们的编译器,在遇到某些语法错误的时候,会持续不停地尝试,不断打印错误信息。这显然太不友好了,要做优化。

然后还有一个比较大的工作就是对类型系统进行升级。我们要重构一下我们之前类型计算的算法让它变得更加简洁和准确。我们目前使用的Nominal的类型系统也要修改成支持structural的类型系统并且我们还要让我们的类型计算支持泛型。

另外我们在升级编译器前端的时候对AST和符号表这两个重要的数据结构也需要重构和优化一下。

第二,我们要把面向对象和函数式编程的特性实现完整。

比如面向对象方面我们需要实现严格的对象的初始化流程需要支持访问权限还要支持接口。在函数式编程方面怎么着也要把Lambda表达式这些基础功能实现。这主要是工作量的问题但需要前端、中端、后端和运行时各方面的配合。

第三,是升级编译器的中端优化功能。

基于目前的IR我们只实现了少量的优化并且还没有支持面向对象等复杂的语言特性这些都需要进行扩展和支持。在这个实现的过程中我们IR的数据结构也会得到丰富和完善。

第四,是升级编译器的后端功能。

我们之前的编译器后端主要是基于AST来生成汇编代码。所以在引入IR以后我们编译器的后端也需要重构一下。从AST生成IR后再基于HIR生成LIR然后在LIR的基础上重新实现指令选择和寄存器分配。

另外我们目前只支持x86-64架构并且也没有在多个操作系统上做测试。在后面我们要支持至少两种CPU架构我计划先支持的是x86-64和Aarch64。前者被广泛用于PC和服务器中后者被广泛用于智能手机和苹果新一代Mac电脑中。而且我们还要兼容多种操作系统。

第五,是内存管理方面的升级。

在垃圾收集方面我们的GC还是很基础的达不到实用级别。那么接下来我们首先要完善基本的标记-清除算法。之后我计划实现一个自动引用计数的ARC的机制。

ARC的原理是记录每个对象的引用数当引用数为零的时候就自动作为垃圾清理掉。ARC的好处就是垃圾回收不会引起大的停顿能让系统的响应比较平缓。苹果的Objective-C和Swift都采用了ARC这也是苹果的系统很少卡顿的原因之一。

第六,实现并发机制。

你应该也注意到了,目前我们这门课中并没有涉及并发机制。但如果不实现并发机制,显然会是一个遗憾。所以后面,我会给出协程功能的一个参考实现。至于另外的并发功能的设想,我接下来还会介绍到。

好了,上面就是要实现一个完整的、实用的、静态编译的语言会涉及到的工作量,细细看一下,还真是挺大的。不过,你也不用怵,这里并没有太多技术点是我们这门课没有涵盖到的,更多的是工作量和工程化的问题。

花费这么多的工作量我并不完全是为了兴趣爱好或者是做技术验证还是想未来有一天能把它用于一些实际的应用场景中。那接下来我就谈谈对开源的PlayScript语言项目的一些规划。

对PlayScript的规划

为什么我会产生自己动手实现一门语言的想法呢?这其实是出于一些实际的需求。现有的语言,或者已有语言的现有实现,有时会让我很不满意。所以我就想,与其等着别人来满足我的需求,不如自己动手试试看。

我就分享一下我的这几个需求,看看你是否也遇到过类似的问题。

首先,我对后端编程语言不满意。

你知道吗要实现像微信这样的应用的后端你只能使用C++这样的语言。并且,微信团队还开发了自己的协程库,才能应对海量并发的需求。我们每天用微信,觉得它总是会实时响应,其实后端的挑战是巨大的。你想想看就知道,成亿的用户,加上成亿的并发,绝对是顶级的技术挑战。

Java、Go等典型的后端语言都不能满足这种场景的需求。Java的内存占用太大自带的并发机制只有线程。虽然Go语言好一点但它和Java都有一个致命的弱点就是垃圾回收导致的停顿是不可控的。这对于微信这种大型的、高并发的平台会带来灾难性的后果。但是让普通的技术团队用C++开发应用,门槛有点高。

而且我对现有后端语言提供的可靠性也不满意。在这方面我比较喜欢Erlang它的并发机制和其他特性结合起来能提供9个9的可靠性。我觉得如果每个应用都能实现这么高的可靠性就好了。但是我对Erlang的性能又不满意而且它的语法对于大多数程序员来说也不是太友好。

实现一门高可靠性的语言其实有隐含一个需求就是语言中的功能是能够在运行时被动态替换的因为你没有办法停下整个系统。所以我们不能仅仅实现AOT的功能还要有JIT、动态优化、动态部署、动态Dispatch的功能。

所以,我理想的后端语言,是能够用比较低的成本,开发出高并发、高可靠性、资源消耗低的应用。不知道你是不是也有类似的需求呢?

第二,我对前端编程环境也不太满意。

我感觉现在的前端编程环境太碎片化了包括浏览器、Android、IOS、Windows、macOS、Linux等不同的平台而且国内还有好几个不同的小程序平台。

所以,我想要是有一个语言或者工具,能够开发一次,部署到很多客户端,那就好了。

第三,我对企业应用的编程语言也不满意。

我曾经参与过很多企业应用的开发工作。在企业应用的开发中,很多时候我们要更关注业务逻辑。但是,现在很多应用的业务逻辑和技术逻辑都是混杂的,企业应用的开发成本太高了。

我一直认为如果你是做企业软件的厂商那应该有相应的开发语言才好比如德国的SAP就有自己的ABAP语言。不过这个语言在现代的应用架构下已经过时了用ABAP开发不出很容易横向扩展的应用。再有一点这个语言是企业私有的不是公共的。

当然,现在我们已经迎来了低代码开发的一波浪潮。但是,如果低代码工具的开发者不是像微软这么有实力的厂商,很难维护一个完全私有的生态。这样的情况下,客户在你私有的平台上开发应用,就是比较有风险的,所以还是应该有一个公共开放的语言。

而且就算是低代码开发我也希望是基于某个语言的而不是仅仅提供一些图形化的定制工具。最好呢是语言可以转化为图形图形也可以转化为代码。这种代码和图形化表达双向转化的能力我在华为的HarmonyOS开发工具上看到了感觉很喜欢。其实我觉得低代码开发工具也应该实现类似的功能才算合格这种能把应用表达为代码的能力是保证应用的可移植性、保护企业投资的关键。

第四,如有可能,我希望能让物联网应用的开发变得更简单一点。

我在之前的一篇加餐里介绍过工控领域软件开发的情况。工控领域的技术原来叫做OT它们的技术跟IT是不一样的。但在我看来现在很多IT技术可以进入OT领域。比如现在儿童编程都可以用一个图形化开发工具控制机器人这跟控制发电机、控制高铁其实没有本质的区别。所以我们应该也可以把这两个领域的开发工具打通才对。就算是OT强调高可靠性但其实IT里现在就有更高可靠性的技术比如我前面提到过的Erlang的9个9的可靠性。

另外OT的技术生态原来都把控在少数外国的企业手里。我觉得如有可能我们最好可以搞搞破坏把它搞成一个开放的生态。

最后现在工控应用和消费级的物联网应用开发成本还是太高了。我觉得我们应该可以用TypeScript这样简单的语言就能生成footprint很小、性能很高、能够方便地跟硬件接口打交道的应用才对。

上面就是我对新的语言和开发工具的一些需求也是我对现有的语言和工具不满意的地方。但我目前只把PlayScript作为一个开源的兴趣项目不一定能完全实现这些目标。我现在只是把观点和期待在这里分享一下。

PlayScript的项目地址在这里https://gitee.com/richard-gong/PlayScript。我会把代码整理一下放进去,并保持更新。如果你对哪方面的特性感兴趣,也欢迎你一起参与。

最后,还有一个重要的观点需要说明一下:实现一门语言和设计一门语言是两码事。你也看到了,我们这门课讨论的都是如何实现语言,并没有讨论怎么去设计一门语言。一方面,我自己对设计一门语言并没有把握。另一方面,我觉得我的这些需求,似乎也并不需要设计一门全新的语言,而是对现有的某个语言重新做一个实现就行了。使用大家都已经熟悉的语法,对我来说似乎是更好的选择。

课程小结

今天这节课,我把我们这门课的内容做了一下总结,也梳理了我们后续要继续实现的功能。所以,这节课并没有什么需要你记住的硬知识点。

不过,通过对这门课程的学习,以及我们今天的梳理,我希望你能产生一个信心,就是实现一门语言所需要的技术,其实并没有那么高不可攀。其实到现在,我们真的已经涉猎了几乎所有的技术点,现在已经没有哪个技术点是真正有难度的、能够挡住你的。

在这个信心的基础上我们再讨论PlayScript未来的演化讨论需要一些新的语言或者开发工具的场景才有意义。因为你要知道你其实是有能力完成这些工作的。我们在中国的软件领域可以做很多类似的、基础性的甚至开创性的工作不用什么事情都仰人鼻息。

思考题

今天,我分享了我的职业生涯中,对现有的语言和工具各种不满意,并且,我也分享了希望有人能够站出来改进的一些方面。你有没有类似的需求和感受呢?请你也跟我们分享一下吧!

欢迎你把这节课分享给更多感兴趣的朋友。我是宫文学,我们下节课见。