设为首页 收藏本站
查看: 794|回复: 0

[经验分享] [ZT]Mac OS X 背后的故事(八)三好学生Chris Lattner的LLVM编译工具链

[复制链接]
发表于 2015-12-31 00:23:32 | 显示全部楼层 |阅读模式
  2011年12月3日,LLVM 3.0正式版发布,完整支持所有ISO C++标准和大部分C++ 0x的新特性, 这对于一个短短几年的全新项目来说非常不易。
  开发者的惊愕
  在2011年WWDC(苹果全球开发者大会)的一场与Objective-C相关的讲座上,开发者的人生观被颠覆了。
  作为一个开发者,管理好自己程序所使用的内存是天经地义的事,好比人们在溜狗时必须清理狗的排泄物一样(美国随处可见“Clean up after your dogs”的标志)。在本科阶段上C语言的课程时,教授们会向学生反复强调:如果使用malloc函数申请了一块内存,使用完后必须再使用free函数把 申请的内存还给系统——如果不还,会造成“内存泄漏”的结果。这对于Hello World可能还不算严重,但对于庞大的程序或是长时间运行的服务器程序,泄内存是致命的。如果没记住,自己还清理了两次,造成的结果则严重得多——直接导致程序崩溃。
  Objective-C有类似malloc/free的对子,叫alloc/dealloc,这种原始的方式如同管理C内存一样困难。所以 Objective-C中的内存管理又增加了“引用计数”的方法,也就是如果一个物件被别的物件引用一次,则引用计数加一;如果不再被该物件引用,则引用 计数减一;当引用计数减至零时,则系统自动清掉该物件所占的内存。具体来说,如果我们有一个字符串,当建立时,需要使用alloc方法来申请内存,引用计 数则变成了一;然后被其他物件引用时,需要用retain方法去增加它的引用计数,变成二。当它和刚才引用的物件脱离关联时,需使release方法减少 引用计数,又变回了一;最后,使用完这个字符串时,再用release方法减少其引用计数,这时,运行库发现其引用计数变为零了,则回收走它的内存。这是手动的方式。
  这种方式自然很麻烦,所以又设计出一种叫做autorelease的机制(不是类似Java的自动垃圾回收)。在Objective-C中,设计了 一个叫做NSAutoReleasePool的池,当开发者需要完成一个任务时(比如每开启一个线程,或者开始一个函数),可以手动创立一个这样的池子, 然后通过显式申明把物件扔进自动回收池中。NSAutoReleasePool内有一个数组来保存声明为autorelease的所有对象。如果一个对象 声明为autorelease,则会自动加到池子里。如果完成了一个任务(结束线程了,或者退出那个函数),则开发者需对这个池子发送一个drain消 息。这时,NSAutoReleasePool会对池子中所有的物件发送release消息,把它们的引用计数都减一 ——这就好比游泳池关门时通知所有客人都“滚蛋”一样。所以开发者无需显式声明release,所有的物件也会在池子清空时自动呼叫release函数, 如果引用计数变成零了,系统才回收那块内存。所以这是个半自动、半手动的方式。
  Objective-C的这种方式虽然比起C来进了一大步,我刚才花了几分钟就和读者讲明白了。只要遵守上面这两个简单的规则,就可以保证不犯任何 错误。但这和后来的Java自动垃圾回收相比则是非常繁琐的,哪怕是再熟练的开发者,一不小心就会弄错。而且,哪怕很简单的代码,比如物件的 getter/setter函数,都需要用户写上一堆的代码来管理接收来的物件的内存。
  经典教材《Cocoa Programming for Mac OS X》用了整整一章节的篇幅,来讲解Objective-C中内存管理相关的内容,但初学者们看得还是一头雾水。所以,在2007年10.5发布 时,Objective-C做出了有史以来最大的更新,最大的亮点是它的运行库libobjc 2.0正式支持自动垃圾回收,也就是由运行库在运行时随时侦测哪些物件需要被释放。听上去很不错,可惜使用这个技术的项目却少之又少。原因很简单,使用这 个特性,会有很大的性能损失,使Objective-C的内存管理效率低得和Java一样,而且一旦有一个模块启用了这个特性,这个进程中所有的地方都要 启用这个特性——因此如果你写了一个使用垃圾回收的库,那所有引用你库的程序就都得被迫使用垃圾回收。所以Apple自己也不使用这项技术,大量的第三方 库也不使用它。
  这个问题随Apple在移动市场的一炮走红而变得更加严峻。不过这次,Apple和与会的开发者讲,他们找到了一个解决问题的终极方法,这个方法把 从世界各地专程赶来聆听圣谕的开发者惊得目瞪口呆——你不用写任何内存管理代码,也不需要使用自动垃圾回收。因为我们的编译器已经学会了上面所介绍的内存 管理规则,会自动在编译程序时把这些代码插进去。
  这个编译器,一直是Apple公开的秘密——LLVM。说它公开,是因为它自始至终都是一个开源项目;而秘密,则是因为它从来没公开在WWDC的Keynote演讲上亮相过 。
  一直关注这系列连载的读者一定还记得,在第二篇《Linus Torvalds的短视》介绍Apple和GPL社区的不合时,提到过“自以为是但代码又写得差的开源项目,Apple事后也遇到不少,比如GCC编译器 项目组。虽然大把钞票扔进去,在先期能够解决一些问题,但时间长了这群人总和Apple过不去,并以自己在开源世界的地位恫吓之,最终Apple由于受不 了这些项目组的态度、协议、代码质量,觉得还不如自己造轮子来得方便。”LLVM则是Apple造的这个轮子,它的目的是完全替代掉GCC那条编译链。它 的主要作者,则是现在就职于Apple的Chris Lattner。
  编译器高材生Chris Lattner
  2000年,本科毕业的Chris Lattner像中国多数大学生一样,按部就班地考了GRE,最终前往UIUC(伊利诺伊大学厄巴纳香槟分校),开始了艰苦读计算机硕士和博士的生涯。在这阶段,他不仅周游美国各大景点,更是努力学习科学文化知识,翻烂了“龙书”(《Compilers: Principles, Techniques, and Tools》),成了GPA牛人【注:最终学分积4.0满分】,以及不断地研究探索关于编译器的未知领域,发表了一篇又一篇的论文,是中国传统观念里的“三好学生”。他的硕士毕业论文提出了一套完整的在编译时、链接时、运行时甚至是在闲置时优化程序的编译思想,直接奠定了LLVM的基础。
LLVM在他念博士时更加成熟,使用GCC作为前端来对用户程序进行语义分析产生IF(Intermidiate Format),然后LLVM使用分析结果完成代码优化和生成。这项研究让他在2005年毕业时,成为小有名气的编译器专家,他也因此早早地被Apple 相中,成为其编译器项目的骨干。
  Apple相中Chris Lattner主要是看中LLVM能摆脱GCC束缚。Apple(包括中后期的NeXT) 一直使用GCC作为官方的编译器。GCC作为开源世界的编译器标准一直做得不错,但Apple对编译工具会提出更高的要求。
  一方面,是Apple对Objective-C语言(甚至后来对C语言)新增很多特性,但GCC开发者并不买Apple的帐——不给实现,因此索性 后来两者分成两条分支分别开发,这也造成Apple的编译器版本远落后于GCC的官方版本。另一方面,GCC的代码耦合度太高,不好独立,而且越是后期的版本,代码质量越差,但Apple想做的很多功能(比如更好的IDE支持)需要模块化的方式来调用GCC,但GCC一直不给做。甚至最近,《GCC运行环境豁免条款 (英文版)》从根本上限制了LLVM-GCC的开发。 所以,这种不和让Apple一直在寻找一个高效的、模块化的、协议更放松的开源替代品,Chris Lattner的LLVM显然是一个很棒的选择。
  刚进入Apple,Chris Lattner就大展身手:首先在OpenGL小组做代码优化,把LLVM运行时的编译架在OpenGL栈上,这样OpenGL栈能够产出更高效率的图形 代码。如果显卡足够高级,这些代码会直接扔入GPU执行。但对于一些不支持全部OpenGL特性的显卡(比如当时的Intel GMA卡),LLVM则能够把这些指令优化成高效的CPU指令,使程序依然能够正常运行。这个强大的OpenGL实现被用在了后来发布的Mac OS X 10.5上。同时,LLVM的链接优化被直接加入到Apple的代码链接器上,而LLVM-GCC也被同步到使用GCC4代码。
  LLVM真正的发迹,则得等到Mac OS X 10.6 Snow Leopard登上舞台。可以说, Snow Leopard的新功能,完全得益于LLVM的技术。而这一个版本,也是将LLVM推向真正成熟的重大机遇。
  关于Snow Leopard的三项主推技术(64位支持、OpenCL,以及Grand Central Dispatch)的细节,我们会在下一次有整整一期篇幅仔细讨论,这次只是点到为止——我们告诉读者,这些技术,不但需要语言层面的支持(比如Grand Centrual Dispatch所用到的“代码块”语法, 这被很多人看作是带lambda的C),也需要底层代码生成和优化(比如OpenCL是在运行时编译为GPU或CPU代码并发执行的)。而这些需求得以实现,归功于LLVM自身的新前端——Clang。
  优异的答卷——Clang
  前文提到,Apple吸收Chris Lattner的目的要比改进GCC代码优化宏大得多——GCC系统庞大而笨重,而Apple大量使用的Objective-C在GCC中优先级很低。此 外GCC作为一个纯粹的编译系统,与IDE配合得很差。加之许可证方面的要求,Apple无法使用LLVM 继续改进GCC的代码质量。于是,Apple决定从零开始写 C、C++、Objective-C语言的前端 Clang,完全替代掉GCC。
  正像名字所写的那样,Clang只支持C,C++和Objective-C三种C家族语言。2007年开始开发,C编译器最早完成,而由于Objective-C相对简单,只是C语言的一个简单扩展,很多情况下甚至可以等价地改写为C语言对Objective-C运行库的函数调用,因此在2009年时,已经完全可以用于生产环境。C++的支持也热火朝天地进行着。
  Clang的加入代表着LLVM真正走向成熟和全能,Chris Lattner以影响他最大的“龙书”封面【注:见http://en.wikipedia.org/wiki/Dragon_Book_(computer_science)】为灵感,为项目选定了图标——一条张牙舞爪的飞龙。
  Clang一个重要的特性是编译快速,占内存少,而代码质量还比GCC来得高。测试结果表明Clang编译Objective-C代码时速度为 GCC的3倍【注:http://llvm.org/pubs/2007-07-25-LLVM-2.0-and-Beyond.pdf】,而语法树 (AST)内存占用则为被编译源码的1.3倍,而GCC则可以轻易地可以超过10倍。Clang不但编译代码快,对于用户犯下的错误,也能够更准确地给出建议。使用过GCC的读者应该熟悉,GCC给出的错误提示基本都不是给人看的。
  比如最简单的:
  struct foo { int x; }
typedef int bar;
  如果使用GCC编译,它将告诉你:
t.c:3: error: two or more data types in declaration specifiers
  但是Clang给出的出错提示则显得人性化得多:
t.c:1:22: error: expected ‘;’ after struct
  甚至,Clang可以根据语境,像拼写检查程序一样地告诉你可能的替代方案。
比如这个程序:
  #include <inttypes.h>
int64 x;
  GCC一样给出乱码似的出错提示:
  t.c:2: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘x’
  而优雅的Clang则用彩色的提示告诉你是不是拼错了,并给出可能的变量名:
  t.c:2:1: error: unknown type name ‘int64&prime;; did you mean ‘int64_t’?
int64 x;^~~~~int64_t
  更多的例子可以参考http://blog.llvm.org/2010/04/amazing-feats-of-clang-error- recovery.html。 而同时又因为Clang是高度模块化的一个前端,很容易实现代码的高度重用。所以比如Xcode 4.0的集成编程环境就使用Clang的模块来实现代码的自动加亮、代码出错的提示和自动的代码补全。开发者使用Xcode 4.0以后的版本,可以极大地提高编程效率,尽可能地降低编译错误的发生率。
  支持C++也是Clang的一项重要使命。C++是一门非常复杂的语言,大多编译器(如GCC、MSVC)用了十多年甚至二十多年来完善对C++的 支持,但效果依然不很理想。Clang的C++支持却一直如火如荼地展开着。2010年2月4日,Clang已经成熟到能自举(即使用Clang编译 Clang,到我发稿时,LLVM 3.0发布已完整支持所有ISO C++标准,以及大部分C++ 0x的新特性。
  这对于一个短短几年的全新项目来说是非常不易的。得益于本身健壮的架构和Apple的大力支持,Clang越来越全能,从FreeBSD【注:http://lists.freebsd.org/pipermail/freebsd-current/2009-February/003743.html】 到Linux Kernel【注:http://lists.cs.uiuc.edu/pipermail/cfe-dev/2010-October/011711.html】, 从Boost【注:http://blog.llvm.org/2010/05/clang-builds-boost.html】 到Java虚拟机, Clang支持的项目越来越多。
  Apple的Mac OS X以及iOS也成了Clang和LLVM的主要试验场——10.6时代,很多需要高效运行的程序比如OpenSSL和Hotspot就由LLVM-GCC 编译来加速的。而10.6时代的Xcode 3.2诸多图形界面开发程序如Xcode、Interface Builder等,皆由Clang编译。到了Mac OS X 10.7,整个系统的的代码都由Clang或LLVM-GCC编译【注:http://llvm.org/Users.html】。
  LLVM周边工具
  由于受到Clang项目的威胁,GCC也不得不软下来,让自己变得稍微模块化一些,推出插件的支持,而LLVM项目则顺水推舟,索性废掉了出道时就一直作为看家本领的LLVM-GCC,改为一个GCC的插件DragonEgg。 Apple也于Xcode 4.2彻底抛弃了GCC工具链。
  而Clang的一个重要衍生项目,则是静态分析工具,能够通过自动分折程序的逻辑,在编译时就找出程序可能的bug。在Mac OS X 10.6时,静态分析被集成进Xcode 3.2,帮助用户查找自己犯下的错误。其中一个功能,就是告诉用户内存管理的Bug,比如alloc了一个物件却忘记使用release回收。这已经是一项很可怕的技术,而Apple自己一定使用它来发现并改正Mac OS X整个系统各层面的问题。但许多开发者还不满足——既然你能发现我漏写了release,你为什么不能帮我自动加上呢?于是ARC被集成进Clang,发生了文章开头开发者们的惊愕——从来没有人觉得这件事是可以做成的。
  除LLVM核心和Clang以外,LLVM还包括一些重要的子项目,比如一个原生支持调试多线程程序的调试器LLDB,和一个C++的标准库libc++, 这些项目由于是从零重写的,因此要比先前的很多项目站得更高,比如先前GNU、Apache、STLport等C++标准库在设计时,C++0x标准还未 公布,所以大多不支持这些新标准或者需要通过一些肮脏的改动才能支持,而libc++则原生支持C++0x。而且在现代架构上,这些项目能动用多核把事情 处理得更好。
  不单单是Apple,诸多的项目和编程语言都从LLVM里取得了关键性的技术。Haskell语言编译器GHC使用LLVM作为后端,实现了高质量的代码编译。很多动态语言实现也使用LLVM作为运行时的编译工具,较著名的有Google的Unladen Swallow【注:Python实现,后夭折】、PyPy【注:Python实现】,以及MacRuby【注:Ruby实现】。例如 MacRuby 后端改为LLVM后,速度不但有了显著的提高,更是支持Grand Central Dispatch来实现高度的并行运行。由于LLVM高度的模块化,很方便重用其中的组件来作为一个实现的重要组成部分,因此类似的项目会越来越多。
  LLVM的成熟也给其他痛恨GCC的开发项目出了一口恶气。其中最重要的,恐怕是以FreeBSD为代表的BSD社区。BSD社区和Apple的联 系一向很紧密,而且由于代码相似,很多Apple的技术如Grand Central Dispatch也是最早移植到FreeBSD上。BSD社区很早就在找GCC的替代品,无奈大多都很差(如Portable C Compiler产生的代码质量和gcc不能同日而语)。
  一方面是因为不满意GCC的代码品质【注:BSD代码整体要比GNU的高一些,GNU代码永无休止地出现各种严重的安全问题】,更重要的是协议问题。BSD开发者有洁癖的居多,大多都不喜欢GPL代码,尤其是GPL协议第三版发布时,和FreeBSD的协议甚至是冲突的。这也正是为什么FreeBSD中包含的GNU的C++运行库还是2007年以GPLv2发布的老版本,而不是支持C++0x的但依GPLv3协议发布的新版本。 因此历时两年的开发后,2012年初发布的FreeBSD 9.0中,Clang被加入到FreeBSD的基础系统。 但这只是第一步,因为FreeBSD中依然使用GNU的C++ STL 库、C++运行库、GDB调试器、libgcc/libgcc_s编译库都是和编译相关的重要底层技术,先前全被GNU垄断,而现在LLVM子项目 lldb、libc++、compiler-rt等项目的出现,使BSD社区有机会向GNU说“不”,因此一个把GNU组件移出FreeBSD的计划被构想出来, 并完成了很大一部分。编写过《Cocoa Programming Developer’s Handbook》的著名Objective-C牛人David Chisnall也被吸收入FreeBSD开发组完成这个计划的关键部分。 预计在FreeBSD 10发布时,将不再包含GNU代码。
  LLVM在短短五年内取得的快速发展充分反映了Apple对于产品技术的远见和处理争端的决心和手腕,并一跃成为最领先的开源软件技术。而 Chris Lattner在2010年也赢得了他应有的荣誉——Programming Languages Software Award(程序设计语言软件奖)。
  作者王越,美国宾西法尼亚大学计算机系研究生,中国著名TeX开发者,非著名OpenFOAM开发者。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-158582-1-1.html 上篇帖子: Mac OS X 10.10安装PyQt流程1.0 下篇帖子: Mac os下换行符导致发布到npm里的命令行模块不能使用问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表