vlambda博客
学习文章列表

涨知识!关注方舟编译器的攻城狮看过来!

舟编译器的诞生经历了漫长的过程,凝聚了许许多多工程师的心血与多年的酝酿和磨砺。方舟编译器今天的成就,是必然的产物。”
——资深编译器专家 叶寒栋
2009年下半年,第一批编译器工程师的加入,标志着华为自研编译器及工具链历程的开始。 初创时期团队人员少,一个巴掌数得过来,但是幸运的是,这个团队经过十余年的积淀,留下了一批编译器精英。他们平均从事编译器相关行业 20+年,相应的平均年龄40+ 我认为这是 作为IT行业研发人员的黄金岁月——对技术的理解最透彻、精纯。 同样幸运的是,华为持续不懈对基础技术投入,孕育了一批高达数百人的编译、虚拟机相关团队,聚集了很多顶尖人才。
编译器团队的业务从零起步,经历的产品包括多核芯片仿真、基于GDB的调试器、GCCLLVM基础上的编译器设计,为公司主航道提供了大量的工具链,也创造了很多性能优化的奇迹。 可是我一直认为,最大的收获在于培养了大量的人才。
在经历过这些产品之后,更多愈发纷繁复杂的产品需求也在不断涌现。拥有一套自主的软件编程体系,也显得尤为重要。单纯凭借零散的开源软件来定制不同的业务需求,不仅效率低下,而且不论是对编译 /虚拟机等系统软件还是应用软件团队来说,都没有积累,是很大的浪费。 从不同国际大公司聚拢在一起的我们,都很清楚对于一个具有相当规模的科技企业来说,拥有自己的编程体系是多么重要,我们也因此非常渴望做这个事情。
最终促使我们下决心的是一个设计编程语言的项目。它的需求是,有没有可能开发一种新的高级语言,类似 matlab,同时又能够发挥DSP的硬件能力。 传统 DSP软件的开发严重依赖于类似汇编的intrinsic,当芯片换代时,这样的软件不得不重新设计和实现。 这更为直观的向我们提出了新的语言需求——兼顾产能和性能。这就是我们的高级语言 Cm的诞生。 这是一个在 C语言基础之上融合了类似matlab数据类型和操作的语言。 为了追求性能,大量的优化需要在高级 IR就开始,在语法树上进行彻底优化和深入改造,是我们的工作重点。
GCC没有被我们选中,因为它的前端过于晦涩,需要依赖lex/yacc这类工具自动生成一些代码,这些代码很难用人工去理解,而新语言的开发需要频繁的改动前端。 Clang的前端是手写的,非常方便阅读,因此我们就以此为基础进行Cm的设计和实现。 Cm取得了巨大的成功。 但是,暴露出的问题是,在别人的开源编译器上进行深入的改造,效率太低。有很多具体问题,这里不展开。
至此,那个自主编程体系的问题又进入我们脑海,挥之不去。首先,从纯技术角度来看,当业务从最初的 CT发展到今天的ICT,编译器工程师面对的芯片从DSPCPUGPUNPU,面对的计算类型和数据访问类型种类繁多,编译器要适配的优化和实现也种类繁多,在各种差异存在的同时又有共性存在。 依靠开源的项目实现上述目标,几乎是不可能的,或者说效率是非常低下的。最大的原因在于每个改动尤其是 IR以及基础优化的改动,在他人的项目基础之上是很难顺利完成的。 其次,从公司的业务来看,内部庞大的软件开发队伍以及无数的软件项目,其实是存在很多共性的,一个略微通用的软件开发全栈,从编程语言开始往下直至内核,对于多数项目是有很大收益的,特别在版本维护、演进和优化上。
基于上述现实,我们一直在思考的是,应该有一个自主设计的中间语言,也就是编译器的IR,作为一切软件编译和运行的基础。 从而避免在舶来品上进行基础性的改动带来的时间和精力的巨大浪费,也不需要考虑是否需要跟随舶来品的演进而制定自己的技术路线。庞大的软件开发体量,迫使我们必须有自主控制独立演进的软件编译分析框架。在此,我们要求这个 IR具有很强的伸缩性,能够消化多数常见的语言特性,动态的或者静态(当然,很可能一下子达不到这个要求,但我们愿意也能够大幅修改)。 在此基础上,编译器的输出应该具有多样性,既可以直接编译成 binary,也可以输出不同层次的IR,即以类似Java字节码的中间代码形式打包。 既可以直接输送给硬件,也可以中间码进行多种格式的分发。下图可以表达我们的这个想法。    
基于此,走自主研发的道路是无法回避的,特别是当前的GCCLLVMOpen64IR的设计是没有考虑Java这类语言的特性,包括对象模型以及各种动态特性的描述,与其在他人基础上不停打补丁,不如从头开始设计一个全新理念的IR 这也就是上图中红色的 Maple IR 对于技术人员来说,拥有一个每个细胞都具备自己基因的编译器,是多么惬意的事情。这也是大家的一点技术情结。
方舟编译器原先的名字叫MAPLEMultiple Architecture Programming Language & Environment),这也是吻合了我们上图的意思。 在开源的代码中可以看到很多 maple字眼,以及文件的后缀名为.mpl以及.mplt等。 MapleIR是重新设计的,跟传统编译器最大的区别在于我们在IR中引入对象的概念,也引入异常处理、同步操作等,因此,它能表达的语义比LLVMGCC更贴近高级语言且囊括了语言的动态特性。
从上图的描述可以看出,方舟编译器输出的是中间码格式的软件包,中间码是与平台无关的。这意味着未来的规划中,我们能够服务于一个分布式、多设备的系统,各设备接收的软件包可以是二进制或者中间码格式。根据设备的能力大小,中间码所包含的信息可以不同。为了实现这个能力,方舟引擎应运而生。这是针对中间码的执行引擎,类似于 Java虚拟机。 所以一个设备既可以采用直接运行机器码,也可以采用方舟引擎运行中间码。它与 Java虚拟机的有两个方面的区别——一是,方舟引擎输入的Maple IR,是瞄准多语言的,因此,它比Java字节码所能表达的范围大了很多。 只要能编译成 MapleIR,引擎就可以执行; 二是,方舟引擎未来要包含仿真能力,也就是说,开发人员在没有实际硬件的条件下,可以仿真一些设备并运行方舟软件。
方舟最早的产品可以追溯到2016年,是JavaScript程序的编译器以及虚拟机 它的目标设备是 IoT,配合华为的LiteOS,可以支撑多数的IoT小设备。
最近火热的方舟编译器的产品是针对安卓系统下面的Java程序的静态编译 Java代码直接编译成机器码,所有的动态语义都通过静态方法来解决。 这样的话, Java虚拟机就不用存在了。 在安卓系统中, ARTAndroid Runtime)也不需要了。 最常见的卡顿原因,即 GC,可通过Reference Counting解决(环引用通过一个按需触发的backup GC搞定,触发频率低到可以忽略不计)。 这也是方舟取得流畅度提升的一个主要原因。综合来讲,做静态编译的最强大的地方在于看到更多的程序信息,这其实是为我们将来更宏伟的计划做准备。大家常见的虚拟机 JIT编译可以得到profiling信息的优点,在静态编译通过训练一样可以获得,只是两者在信息类型和数据量上各有千秋。
前面图中描述的MapleIR,其核心设计思想是能够兼容常见的几种语言。 这个要求背后隐藏的一个长远的设想就是消除跨语言的障碍。举个例子,在 Java编程中如果想调用C库,则必须通过JNI机制实现。 这个实现的成本是很高的,因为两种不同语言的数据类型、调用约定完全不同,又牵涉到跨语言的异常传播和内存管理,不得不通过虚拟机进行昂贵的处理,效率十分低下。如果能够把两种语言都翻译到 MapleIR,在IR上进行数据类型的融合,并在相当大程度上实现调用约定的统一,那么则可以极大的提高效率。 我们把这个计划称为 拆墙行动 在拆墙行动基础之上,在我们长远的构思中是要做全程序优化的,这是一个跨语言的全程序优化,是对我们自己的巨大挑战。
显然,我们的理想不是做编译器,而是做软件开发系统。这是一个相当大的概念,可以理解为软件开发的生态。我们今天在做的每一件事情,包括 Java静态编译,都是为这个目标做准备。 它的未来已经着眼于建立完整的软件开发体系。在这整个生态中不得不提的就是编程语言和编程框架。软件开发人员看到的就是编程语言和相关框架,因此,这是生态的入口,方舟的未来必然在语言上做尝试。基于此,一个强大的具有相对自动适配能力的编译器前端是必须的。这个前端最大的特点必须是能够最大程度上的自动化,降低语言设计过程中的各种反复带来的开发成本。
除此之外,大量的基于一致的IR的开发工具,包括调试、调优等,必定会应运而生,为此,我们愿意和业界的共同爱好者一起努力,构建一个完整的方舟体系,在编程语言、编译、分布式异构编程框架、分布式运行系统等多个领域奠定基础。